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

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