File indexing completed on 2024-05-12 15:46:24
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"