File indexing completed on 2024-05-12 11:58:35

0001 /*
0002     SPDX-FileCopyrightText: 2008 Erlend Hamberg <ehamberg@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 #include "katedocument.h"
0007 #include "kateviinputmode.h"
0008 
0009 #include <utils/kateconfig.h>
0010 #include <view/kateviewinternal.h>
0011 #include <vimode/emulatedcommandbar/emulatedcommandbar.h>
0012 #include <vimode/inputmodemanager.h>
0013 #include <vimode/marks.h>
0014 #include <vimode/modes/replacevimode.h>
0015 
0016 using namespace KateVi;
0017 
0018 ReplaceViMode::ReplaceViMode(InputModeManager *viInputModeManager, KTextEditor::ViewPrivate *view, KateViewInternal *viewInternal)
0019     : ModeBase()
0020 {
0021     m_view = view;
0022     m_viewInternal = viewInternal;
0023     m_viInputModeManager = viInputModeManager;
0024     m_count = 1;
0025 }
0026 
0027 bool ReplaceViMode::commandInsertFromLine(int offset)
0028 {
0029     KTextEditor::Cursor c(m_view->cursorPosition());
0030 
0031     if (c.line() + offset >= doc()->lines() || c.line() + offset < 0) {
0032         return false;
0033     }
0034 
0035     // Fetch the new character from the specified line.
0036     KTextEditor::Cursor target(c.line() + offset, c.column());
0037     QChar ch = doc()->characterAt(target);
0038     if (ch == QChar::Null) {
0039         return false;
0040     }
0041 
0042     // The cursor is at the end of the line: just append the char.
0043     if (c.column() == doc()->lineLength(c.line())) {
0044         return doc()->insertText(c, ch);
0045     }
0046 
0047     // We can replace the current one with the fetched character.
0048     KTextEditor::Cursor next(c.line(), c.column() + 1);
0049     QChar removed = doc()->line(c.line()).at(c.column());
0050     if (doc()->replaceText(KTextEditor::Range(c, next), ch)) {
0051         overwrittenChar(removed);
0052         return true;
0053     }
0054     return false;
0055 }
0056 
0057 bool ReplaceViMode::commandMoveOneWordLeft()
0058 {
0059     KTextEditor::Cursor c(m_view->cursorPosition());
0060     c = findPrevWordStart(c.line(), c.column());
0061 
0062     if (!c.isValid()) {
0063         c = KTextEditor::Cursor(0, 0);
0064     }
0065 
0066     updateCursor(c);
0067     return true;
0068 }
0069 
0070 bool ReplaceViMode::commandMoveOneWordRight()
0071 {
0072     KTextEditor::Cursor c(m_view->cursorPosition());
0073     c = findNextWordStart(c.line(), c.column());
0074 
0075     if (!c.isValid()) {
0076         c = doc()->documentEnd();
0077     }
0078 
0079     updateCursor(c);
0080     return true;
0081 }
0082 
0083 bool ReplaceViMode::handleKeypress(const QKeyEvent *e)
0084 {
0085     // backspace should work even if the shift key is down
0086     if (e->modifiers() != CONTROL_MODIFIER && e->key() == Qt::Key_Backspace) {
0087         backspace();
0088         return true;
0089     }
0090 
0091     // on macOS the KeypadModifier is set for the arrow keys too
0092     if (e->modifiers() == Qt::NoModifier || e->modifiers() == Qt::KeypadModifier) {
0093         switch (e->key()) {
0094         case Qt::Key_Escape:
0095             m_overwritten.clear();
0096             leaveReplaceMode();
0097             return true;
0098         case Qt::Key_Left:
0099             m_overwritten.clear();
0100             m_view->cursorLeft();
0101             return true;
0102         case Qt::Key_Right:
0103             m_overwritten.clear();
0104             m_view->cursorRight();
0105             return true;
0106         case Qt::Key_Up:
0107             m_overwritten.clear();
0108             m_view->up();
0109             return true;
0110         case Qt::Key_Down:
0111             m_overwritten.clear();
0112             m_view->down();
0113             return true;
0114         case Qt::Key_Home:
0115             m_overwritten.clear();
0116             m_view->home();
0117             return true;
0118         case Qt::Key_End:
0119             m_overwritten.clear();
0120             m_view->end();
0121             return true;
0122         case Qt::Key_PageUp:
0123             m_overwritten.clear();
0124             m_view->pageUp();
0125             return true;
0126         case Qt::Key_PageDown:
0127             m_overwritten.clear();
0128             m_view->pageDown();
0129             return true;
0130         case Qt::Key_Delete:
0131             m_view->keyDelete();
0132             return true;
0133         case Qt::Key_Insert:
0134             startInsertMode();
0135             return true;
0136         case Qt::Key_Enter:
0137         case Qt::Key_Return:
0138             if (m_viInputModeManager->inputAdapter()->viModeEmulatedCommandBar()->isSendingSyntheticSearchCompletedKeypress()) {
0139                 // BUG #451076, Do not record/send return for a newline when doing a search via Ctrl+F/Edit->Find menu
0140                 m_viInputModeManager->doNotLogCurrentKeypress();
0141                 return true;
0142             }
0143             Q_FALLTHROUGH();
0144         default:
0145             return false;
0146         }
0147     } else if (e->modifiers() == CONTROL_MODIFIER) {
0148         switch (e->key()) {
0149         case Qt::Key_BracketLeft:
0150         case Qt::Key_C:
0151             startNormalMode();
0152             return true;
0153         case Qt::Key_E:
0154             commandInsertFromLine(1);
0155             return true;
0156         case Qt::Key_Y:
0157             commandInsertFromLine(-1);
0158             return true;
0159         case Qt::Key_W:
0160             commandBackWord();
0161             return true;
0162         case Qt::Key_U:
0163             commandBackLine();
0164             return true;
0165         case Qt::Key_Left:
0166             m_overwritten.clear();
0167             commandMoveOneWordLeft();
0168             return true;
0169         case Qt::Key_Right:
0170             m_overwritten.clear();
0171             commandMoveOneWordRight();
0172             return true;
0173         default:
0174             return false;
0175         }
0176     }
0177 
0178     return false;
0179 }
0180 
0181 void ReplaceViMode::backspace()
0182 {
0183     KTextEditor::Cursor c1(m_view->cursorPosition());
0184     KTextEditor::Cursor c2(c1.line(), c1.column() - 1);
0185 
0186     if (c1.column() > 0) {
0187         if (!m_overwritten.isEmpty()) {
0188             doc()->removeText(KTextEditor::Range(c1.line(), c1.column() - 1, c1.line(), c1.column()));
0189             doc()->insertText(c2, m_overwritten.right(1));
0190             m_overwritten.remove(m_overwritten.length() - 1, 1);
0191         }
0192         updateCursor(c2);
0193     }
0194 }
0195 
0196 void ReplaceViMode::commandBackWord()
0197 {
0198     KTextEditor::Cursor current(m_view->cursorPosition());
0199     KTextEditor::Cursor to(findPrevWordStart(current.line(), current.column()));
0200 
0201     if (!to.isValid()) {
0202         return;
0203     }
0204 
0205     while (current.isValid() && current != to) {
0206         backspace();
0207         current = m_view->cursorPosition();
0208     }
0209 }
0210 
0211 void ReplaceViMode::commandBackLine()
0212 {
0213     const int column = m_view->cursorPosition().column();
0214 
0215     for (int i = column; i >= 0 && !m_overwritten.isEmpty(); i--) {
0216         backspace();
0217     }
0218 }
0219 
0220 void ReplaceViMode::leaveReplaceMode()
0221 {
0222     // Redo replacement operation <count> times
0223     m_view->abortCompletion();
0224 
0225     if (m_count > 1) {
0226         // Look at added text so that we can repeat the addition
0227         const QString added = doc()->text(KTextEditor::Range(m_viInputModeManager->marks()->getStartEditYanked(), m_view->cursorPosition()));
0228         for (unsigned int i = 0; i < m_count - 1; i++) {
0229             KTextEditor::Cursor c(m_view->cursorPosition());
0230             KTextEditor::Cursor c2(c.line(), c.column() + added.length());
0231             doc()->replaceText(KTextEditor::Range(c, c2), added);
0232         }
0233     }
0234 
0235     startNormalMode();
0236 }