File indexing completed on 2024-04-28 03:58:06

0001 /*
0002     SPDX-FileCopyrightText: 2008-2011 Erlend Hamberg <ehamberg@gmail.com>
0003     SPDX-FileCopyrightText: 2011 Svyatoslav Kuzmich <svatoslav1@gmail.com>
0004     SPDX-FileCopyrightText: 2012-2013 Simon St James <kdedevel@etotheipiplusone.com>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "katecompletiontree.h"
0010 #include "katecompletionwidget.h"
0011 #include "kateconfig.h"
0012 #include "katedocument.h"
0013 #include "kateglobal.h"
0014 #include "katepartdebug.h"
0015 #include "katerenderer.h"
0016 #include "kateview.h"
0017 #include "kateviewinternal.h"
0018 #include "kateviinputmode.h"
0019 #include <vimode/completionrecorder.h>
0020 #include <vimode/completionreplayer.h>
0021 #include <vimode/emulatedcommandbar/emulatedcommandbar.h>
0022 #include <vimode/inputmodemanager.h>
0023 #include <vimode/keyparser.h>
0024 #include <vimode/lastchangerecorder.h>
0025 #include <vimode/macrorecorder.h>
0026 #include <vimode/marks.h>
0027 #include <vimode/modes/insertvimode.h>
0028 
0029 #include <KLocalizedString>
0030 
0031 using namespace KateVi;
0032 
0033 InsertViMode::InsertViMode(InputModeManager *viInputModeManager, KTextEditor::ViewPrivate *view, KateViewInternal *viewInternal)
0034     : ModeBase()
0035 {
0036     m_view = view;
0037     m_viewInternal = viewInternal;
0038     m_viInputModeManager = viInputModeManager;
0039 
0040     m_waitingRegister = false;
0041     m_blockInsert = None;
0042     m_eolPos = 0;
0043     m_count = 1;
0044     m_countedRepeatsBeginOnNewLine = false;
0045 
0046     m_isExecutingCompletion = false;
0047 
0048     connect(doc(), &KTextEditor::DocumentPrivate::textInsertedRange, this, &InsertViMode::textInserted);
0049 }
0050 
0051 InsertViMode::~InsertViMode() = default;
0052 
0053 bool InsertViMode::commandInsertFromAbove()
0054 {
0055     KTextEditor::Cursor c(m_view->cursorPosition());
0056 
0057     if (c.line() <= 0) {
0058         return false;
0059     }
0060 
0061     QString line = doc()->line(c.line() - 1);
0062     int tabWidth = doc()->config()->tabWidth();
0063     QChar ch = getCharAtVirtualColumn(line, m_view->virtualCursorColumn(), tabWidth);
0064 
0065     if (ch == QChar::Null) {
0066         return false;
0067     }
0068 
0069     return doc()->insertText(c, ch);
0070 }
0071 
0072 bool InsertViMode::commandInsertFromBelow()
0073 {
0074     KTextEditor::Cursor c(m_view->cursorPosition());
0075 
0076     if (c.line() >= doc()->lines() - 1) {
0077         return false;
0078     }
0079 
0080     QString line = doc()->line(c.line() + 1);
0081     int tabWidth = doc()->config()->tabWidth();
0082     QChar ch = getCharAtVirtualColumn(line, m_view->virtualCursorColumn(), tabWidth);
0083 
0084     if (ch == QChar::Null) {
0085         return false;
0086     }
0087 
0088     return doc()->insertText(c, ch);
0089 }
0090 
0091 bool InsertViMode::commandDeleteWord()
0092 {
0093     KTextEditor::Cursor c1(m_view->cursorPosition());
0094     KTextEditor::Cursor c2;
0095 
0096     c2 = findPrevWordStart(c1.line(), c1.column());
0097 
0098     if (c2.line() != c1.line()) {
0099         if (c1.column() == 0) {
0100             c2.setColumn(doc()->line(c2.line()).length());
0101         } else {
0102             c2.setColumn(0);
0103             c2.setLine(c2.line() + 1);
0104         }
0105     }
0106 
0107     Range r(c2, c1, ExclusiveMotion);
0108     return deleteRange(r, CharWise, false);
0109 }
0110 
0111 bool InsertViMode::commandDeleteLine()
0112 {
0113     KTextEditor::Cursor c(m_view->cursorPosition());
0114     Range r(c.line(), 0, c.line(), c.column(), ExclusiveMotion);
0115 
0116     if (c.column() == 0) {
0117         // Try to move the current line to the end of the previous line.
0118         if (c.line() == 0) {
0119             return true;
0120         } else {
0121             r.startColumn = doc()->line(c.line() - 1).length();
0122             r.startLine--;
0123         }
0124     } else {
0125         /*
0126          * Remove backwards until the first non-space character. If no
0127          * non-space was found, remove backwards to the first column.
0128          */
0129         static const QRegularExpression nonSpace(QStringLiteral("\\S"), QRegularExpression::UseUnicodePropertiesOption);
0130         r.startColumn = getLine().indexOf(nonSpace);
0131         if (r.startColumn == -1 || r.startColumn >= c.column()) {
0132             r.startColumn = 0;
0133         }
0134     }
0135     return deleteRange(r, CharWise, false);
0136 }
0137 
0138 bool InsertViMode::commandDeleteCharBackward()
0139 {
0140     KTextEditor::Cursor c(m_view->cursorPosition());
0141 
0142     Range r(c.line(), c.column() - getCount(), c.line(), c.column(), ExclusiveMotion);
0143 
0144     if (c.column() == 0) {
0145         if (c.line() == 0) {
0146             return true;
0147         } else {
0148             r.startColumn = doc()->line(c.line() - 1).length();
0149             r.startLine--;
0150         }
0151     }
0152 
0153     return deleteRange(r, CharWise);
0154 }
0155 
0156 bool InsertViMode::commandNewLine()
0157 {
0158     doc()->newLine(m_view);
0159     return true;
0160 }
0161 
0162 bool InsertViMode::commandIndent()
0163 {
0164     KTextEditor::Cursor c(m_view->cursorPosition());
0165     doc()->indent(KTextEditor::Range(c.line(), 0, c.line(), 0), 1);
0166     return true;
0167 }
0168 
0169 bool InsertViMode::commandUnindent()
0170 {
0171     KTextEditor::Cursor c(m_view->cursorPosition());
0172     doc()->indent(KTextEditor::Range(c.line(), 0, c.line(), 0), -1);
0173     return true;
0174 }
0175 
0176 bool InsertViMode::commandToFirstCharacterInFile()
0177 {
0178     KTextEditor::Cursor c(0, 0);
0179     updateCursor(c);
0180     return true;
0181 }
0182 
0183 bool InsertViMode::commandToLastCharacterInFile()
0184 {
0185     int lines = doc()->lines() - 1;
0186     KTextEditor::Cursor c(lines, doc()->line(lines).length());
0187     updateCursor(c);
0188     return true;
0189 }
0190 
0191 bool InsertViMode::commandMoveOneWordLeft()
0192 {
0193     KTextEditor::Cursor c(m_view->cursorPosition());
0194     c = findPrevWordStart(c.line(), c.column());
0195 
0196     if (!c.isValid()) {
0197         c = KTextEditor::Cursor(0, 0);
0198     }
0199 
0200     updateCursor(c);
0201     return true;
0202 }
0203 
0204 bool InsertViMode::commandMoveOneWordRight()
0205 {
0206     KTextEditor::Cursor c(m_view->cursorPosition());
0207     c = findNextWordStart(c.line(), c.column());
0208 
0209     if (!c.isValid()) {
0210         c = doc()->documentEnd();
0211     }
0212 
0213     updateCursor(c);
0214     return true;
0215 }
0216 
0217 bool InsertViMode::commandCompleteNext()
0218 {
0219     if (m_view->completionWidget()->isCompletionActive()) {
0220         const QModelIndex oldCompletionItem = m_view->completionWidget()->treeView()->selectionModel()->currentIndex();
0221         m_view->completionWidget()->cursorDown();
0222         const QModelIndex newCompletionItem = m_view->completionWidget()->treeView()->selectionModel()->currentIndex();
0223         if (newCompletionItem == oldCompletionItem) {
0224             // Wrap to top.
0225             m_view->completionWidget()->top();
0226         }
0227     } else {
0228         m_view->userInvokedCompletion();
0229     }
0230     return true;
0231 }
0232 
0233 bool InsertViMode::commandCompletePrevious()
0234 {
0235     if (m_view->completionWidget()->isCompletionActive()) {
0236         const QModelIndex oldCompletionItem = m_view->completionWidget()->treeView()->selectionModel()->currentIndex();
0237         m_view->completionWidget()->cursorUp();
0238         const QModelIndex newCompletionItem = m_view->completionWidget()->treeView()->selectionModel()->currentIndex();
0239         if (newCompletionItem == oldCompletionItem) {
0240             // Wrap to bottom.
0241             m_view->completionWidget()->bottom();
0242         }
0243     } else {
0244         m_view->userInvokedCompletion();
0245         m_view->completionWidget()->bottom();
0246     }
0247     return true;
0248 }
0249 
0250 bool InsertViMode::commandInsertContentOfRegister()
0251 {
0252     KTextEditor::Cursor c(m_view->cursorPosition());
0253     KTextEditor::Cursor cAfter = c;
0254     QChar reg = getChosenRegister(m_register);
0255 
0256     OperationMode m = getRegisterFlag(reg);
0257     QString textToInsert = getRegisterContent(reg);
0258 
0259     if (textToInsert.isNull()) {
0260         error(i18n("Nothing in register %1", reg));
0261         return false;
0262     }
0263 
0264     if (m == LineWise) {
0265         textToInsert.chop(1); // remove the last \n
0266         c.setColumn(doc()->lineLength(c.line())); // paste after the current line and ...
0267         textToInsert.prepend(QLatin1Char('\n')); // ... prepend a \n, so the text starts on a new line
0268 
0269         cAfter.setLine(cAfter.line() + 1);
0270         cAfter.setColumn(0);
0271     } else {
0272         cAfter.setColumn(cAfter.column() + textToInsert.length());
0273     }
0274 
0275     doc()->insertText(c, textToInsert, m == Block);
0276 
0277     updateCursor(cAfter);
0278 
0279     return true;
0280 }
0281 
0282 // Start Normal mode just for one command and return to Insert mode
0283 bool InsertViMode::commandSwitchToNormalModeForJustOneCommand()
0284 {
0285     m_viInputModeManager->setTemporaryNormalMode(true);
0286     m_viInputModeManager->changeViMode(ViMode::NormalMode);
0287     const KTextEditor::Cursor cursorPos = m_view->cursorPosition();
0288     // If we're at end of the line, move the cursor back one step, as in Vim.
0289     if (doc()->line(cursorPos.line()).length() == cursorPos.column()) {
0290         m_view->setCursorPosition(KTextEditor::Cursor(cursorPos.line(), cursorPos.column() - 1));
0291     }
0292     m_viInputModeManager->inputAdapter()->setCaretStyle(KTextEditor::caretStyles::Block);
0293     Q_EMIT m_view->viewModeChanged(m_view, m_view->viewMode());
0294     m_viewInternal->repaint();
0295     return true;
0296 }
0297 
0298 /**
0299  * checks if the key is a valid command
0300  * @return true if a command was completed and executed, false otherwise
0301  */
0302 bool InsertViMode::handleKeypress(const QKeyEvent *e)
0303 {
0304     // backspace should work even if the shift key is down
0305     if (e->modifiers() != CONTROL_MODIFIER && e->key() == Qt::Key_Backspace) {
0306         m_view->backspace();
0307         return true;
0308     }
0309 
0310     if (m_keys.isEmpty() && !m_waitingRegister) {
0311         // on macOS the KeypadModifier is set for arrow keys too
0312         if (e->modifiers() == Qt::NoModifier || e->modifiers() == Qt::KeypadModifier) {
0313             switch (e->key()) {
0314             case Qt::Key_Escape:
0315                 leaveInsertMode();
0316                 return true;
0317             case Qt::Key_Left:
0318                 m_view->cursorLeft();
0319                 return true;
0320             case Qt::Key_Right:
0321                 m_view->cursorRight();
0322                 return true;
0323             case Qt::Key_Up:
0324                 m_view->up();
0325                 return true;
0326             case Qt::Key_Down:
0327                 m_view->down();
0328                 return true;
0329             case Qt::Key_Insert:
0330                 startReplaceMode();
0331                 return true;
0332             case Qt::Key_Delete:
0333                 m_view->keyDelete();
0334                 return true;
0335             case Qt::Key_Home:
0336                 m_view->home();
0337                 return true;
0338             case Qt::Key_End:
0339                 m_view->end();
0340                 return true;
0341             case Qt::Key_PageUp:
0342                 m_view->pageUp();
0343                 return true;
0344             case Qt::Key_PageDown:
0345                 m_view->pageDown();
0346                 return true;
0347             case Qt::Key_Enter:
0348             case Qt::Key_Return:
0349             case Qt::Key_Tab:
0350                 if (m_view->completionWidget()->isCompletionActive() && !m_viInputModeManager->macroRecorder()->isReplaying()
0351                     && !m_viInputModeManager->lastChangeRecorder()->isReplaying()) {
0352                     m_isExecutingCompletion = true;
0353                     m_textInsertedByCompletion.clear();
0354                     const bool success = m_view->completionWidget()->execute();
0355                     m_isExecutingCompletion = false;
0356                     if (success) {
0357                         // Filter out Enter/ Return's that trigger a completion when recording macros/ last change stuff; they
0358                         // will be replaced with the special code "ctrl-space".
0359                         // (This is why there is a "!m_viInputModeManager->isReplayingMacro()" above.)
0360                         m_viInputModeManager->doNotLogCurrentKeypress();
0361                         completionFinished();
0362                         return true;
0363                     }
0364                 } else if (m_viInputModeManager->inputAdapter()->viModeEmulatedCommandBar()->isSendingSyntheticSearchCompletedKeypress()) {
0365                     // BUG #451076, Do not record/send return for a newline when doing a search via Ctrl+F/Edit->Find menu
0366                     m_viInputModeManager->doNotLogCurrentKeypress();
0367                     return true;
0368                 }
0369                 Q_FALLTHROUGH();
0370             default:
0371                 return false;
0372             }
0373         } else if (e->modifiers() == CONTROL_MODIFIER) {
0374             switch (e->key()) {
0375             case Qt::Key_BracketLeft:
0376             case Qt::Key_3:
0377                 leaveInsertMode();
0378                 return true;
0379             case Qt::Key_Space:
0380                 // We use Ctrl-space as a special code in macros/ last change, which means: if replaying
0381                 // a macro/ last change, fetch and execute the next completion for this macro/ last change ...
0382                 if (!m_viInputModeManager->macroRecorder()->isReplaying() && !m_viInputModeManager->lastChangeRecorder()->isReplaying()) {
0383                     commandCompleteNext();
0384                     // ... therefore, we should not record ctrl-space indiscriminately.
0385                     m_viInputModeManager->doNotLogCurrentKeypress();
0386                 } else {
0387                     m_viInputModeManager->completionReplayer()->replay();
0388                 }
0389                 return true;
0390             case Qt::Key_C:
0391                 leaveInsertMode(true);
0392                 return true;
0393             case Qt::Key_D:
0394                 commandUnindent();
0395                 return true;
0396             case Qt::Key_E:
0397                 commandInsertFromBelow();
0398                 return true;
0399             case Qt::Key_N:
0400                 if (!m_viInputModeManager->macroRecorder()->isReplaying()) {
0401                     commandCompleteNext();
0402                 }
0403                 return true;
0404             case Qt::Key_P:
0405                 if (!m_viInputModeManager->macroRecorder()->isReplaying()) {
0406                     commandCompletePrevious();
0407                 }
0408                 return true;
0409             case Qt::Key_T:
0410                 commandIndent();
0411                 return true;
0412             case Qt::Key_W:
0413                 commandDeleteWord();
0414                 return true;
0415             case Qt::Key_U:
0416                 return commandDeleteLine();
0417             case Qt::Key_J:
0418                 commandNewLine();
0419                 return true;
0420             case Qt::Key_H:
0421                 commandDeleteCharBackward();
0422                 return true;
0423             case Qt::Key_Y:
0424                 commandInsertFromAbove();
0425                 return true;
0426             case Qt::Key_O:
0427                 commandSwitchToNormalModeForJustOneCommand();
0428                 return true;
0429             case Qt::Key_Home:
0430                 commandToFirstCharacterInFile();
0431                 return true;
0432             case Qt::Key_R:
0433                 m_waitingRegister = true;
0434                 return true;
0435             case Qt::Key_End:
0436                 commandToLastCharacterInFile();
0437                 return true;
0438             case Qt::Key_Left:
0439                 commandMoveOneWordLeft();
0440                 return true;
0441             case Qt::Key_Right:
0442                 commandMoveOneWordRight();
0443                 return true;
0444             default:
0445                 return false;
0446             }
0447         }
0448 
0449         return false;
0450     } else if (m_waitingRegister) {
0451         // ignore modifier keys alone
0452         if (e->key() == Qt::Key_Shift || e->key() == Qt::Key_Control || e->key() == Qt::Key_Alt || e->key() == Qt::Key_Meta) {
0453             return false;
0454         }
0455 
0456         QChar key = KeyParser::self()->KeyEventToQChar(*e);
0457         key = key.toLower();
0458         m_waitingRegister = false;
0459 
0460         // is it register ?
0461         // TODO: add registers such as '/'. See :h <c-r>
0462         if ((key >= QLatin1Char('0') && key <= QLatin1Char('9')) || (key >= QLatin1Char('a') && key <= QLatin1Char('z')) || key == QLatin1Char('_')
0463             || key == QLatin1Char('-') || key == QLatin1Char('+') || key == QLatin1Char('*') || key == QLatin1Char('"')) {
0464             m_register = key;
0465         } else {
0466             return false;
0467         }
0468         commandInsertContentOfRegister();
0469         return true;
0470     }
0471     return false;
0472 }
0473 
0474 // leave insert mode when esc, etc, is pressed. if leaving block
0475 // prepend/append, the inserted text will be added to all block lines. if
0476 // ctrl-c is used to exit insert mode this is not done.
0477 void InsertViMode::leaveInsertMode(bool force)
0478 {
0479     m_view->abortCompletion();
0480     if (!force) {
0481         if (m_blockInsert != None) { // block append/prepend
0482 
0483             // make sure cursor haven't been moved
0484             if (m_blockRange.startLine == m_view->cursorPosition().line()) {
0485                 int start;
0486                 int len;
0487                 QString added;
0488                 KTextEditor::Cursor c;
0489 
0490                 switch (m_blockInsert) {
0491                 case Append:
0492                 case Prepend:
0493                     if (m_blockInsert == Append) {
0494                         start = m_blockRange.endColumn + 1;
0495                     } else {
0496                         start = m_blockRange.startColumn;
0497                     }
0498 
0499                     len = m_view->cursorPosition().column() - start;
0500                     added = getLine().mid(start, len);
0501 
0502                     c = KTextEditor::Cursor(m_blockRange.startLine, start);
0503                     for (int i = m_blockRange.startLine + 1; i <= m_blockRange.endLine; i++) {
0504                         c.setLine(i);
0505                         doc()->insertText(c, added);
0506                     }
0507                     break;
0508                 case AppendEOL:
0509                     start = m_eolPos;
0510                     len = m_view->cursorPosition().column() - start;
0511                     added = getLine().mid(start, len);
0512 
0513                     c = KTextEditor::Cursor(m_blockRange.startLine, start);
0514                     for (int i = m_blockRange.startLine + 1; i <= m_blockRange.endLine; i++) {
0515                         c.setLine(i);
0516                         c.setColumn(doc()->lineLength(i));
0517                         doc()->insertText(c, added);
0518                     }
0519                     break;
0520                 default:
0521                     error(QStringLiteral("not supported"));
0522                 }
0523             }
0524 
0525             m_blockInsert = None;
0526         } else {
0527             const QString added = doc()->text(KTextEditor::Range(m_viInputModeManager->marks()->getStartEditYanked(), m_view->cursorPosition()));
0528 
0529             if (m_count > 1) {
0530                 for (unsigned int i = 0; i < m_count - 1; i++) {
0531                     if (m_countedRepeatsBeginOnNewLine) {
0532                         doc()->newLine(m_view);
0533                     }
0534                     doc()->insertText(m_view->cursorPosition(), added);
0535                 }
0536             }
0537         }
0538     }
0539     m_countedRepeatsBeginOnNewLine = false;
0540     startNormalMode();
0541 }
0542 
0543 void InsertViMode::setBlockPrependMode(Range blockRange)
0544 {
0545     // ignore if not more than one line is selected
0546     if (blockRange.startLine != blockRange.endLine) {
0547         m_blockInsert = Prepend;
0548         m_blockRange = blockRange;
0549     }
0550 }
0551 
0552 void InsertViMode::setBlockAppendMode(Range blockRange, BlockInsert b)
0553 {
0554     Q_ASSERT(b == Append || b == AppendEOL);
0555 
0556     // ignore if not more than one line is selected
0557     if (blockRange.startLine != blockRange.endLine) {
0558         m_blockRange = blockRange;
0559         m_blockInsert = b;
0560         if (b == AppendEOL) {
0561             m_eolPos = doc()->lineLength(m_blockRange.startLine);
0562         }
0563     } else {
0564         qCDebug(LOG_KTE) << "cursor moved. ignoring block append/prepend";
0565     }
0566 }
0567 
0568 void InsertViMode::completionFinished()
0569 {
0570     Completion::CompletionType completionType = Completion::PlainText;
0571     if (m_view->cursorPosition() != m_textInsertedByCompletionEndPos) {
0572         completionType = Completion::FunctionWithArgs;
0573     } else if (m_textInsertedByCompletion.endsWith(QLatin1String("()")) || m_textInsertedByCompletion.endsWith(QLatin1String("();"))) {
0574         completionType = Completion::FunctionWithoutArgs;
0575     }
0576     m_viInputModeManager->completionRecorder()->logCompletionEvent(
0577         Completion(m_textInsertedByCompletion, KateViewConfig::global()->wordCompletionRemoveTail(), completionType));
0578 }
0579 
0580 void InsertViMode::textInserted(KTextEditor::Document *document, KTextEditor::Range range)
0581 {
0582     if (m_isExecutingCompletion) {
0583         m_textInsertedByCompletion += document->text(range);
0584         m_textInsertedByCompletionEndPos = range.end();
0585     }
0586 }