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 }