File indexing completed on 2024-05-12 15:46:28

0001 /*
0002     SPDX-FileCopyrightText: 2008 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 <document/katedocument.h>
0010 #include <inputmode/kateviinputmode.h>
0011 
0012 #include <vimode/inputmodemanager.h>
0013 #include <vimode/marks.h>
0014 #include <vimode/modes/visualvimode.h>
0015 #include <vimode/motion.h>
0016 #include <vimode/range.h>
0017 
0018 using namespace KateVi;
0019 
0020 VisualViMode::VisualViMode(InputModeManager *viInputModeManager, KTextEditor::ViewPrivate *view, KateViewInternal *viewInternal)
0021     : NormalViMode(viInputModeManager, view, viewInternal)
0022 {
0023     m_start.setPosition(-1, -1);
0024     m_mode = ViMode::VisualMode;
0025 
0026     connect(m_view, &KTextEditor::ViewPrivate::selectionChanged, this, &VisualViMode::updateSelection);
0027 }
0028 
0029 void VisualViMode::selectInclusive(const KTextEditor::Cursor c1, const KTextEditor::Cursor c2)
0030 {
0031     if (c1 >= c2) {
0032         m_view->setSelection(KTextEditor::Range(c1.line(), c1.column() + 1, c2.line(), c2.column()));
0033     } else {
0034         m_view->setSelection(KTextEditor::Range(c1.line(), c1.column(), c2.line(), c2.column() + 1));
0035     }
0036 }
0037 
0038 void VisualViMode::selectBlockInclusive(const KTextEditor::Cursor c1, const KTextEditor::Cursor c2)
0039 {
0040     m_view->setBlockSelection(true);
0041 
0042     if (c1.column() >= c2.column()) {
0043         m_view->setSelection(KTextEditor::Range(c1.line(), c1.column() + 1, c2.line(), c2.column()));
0044     } else {
0045         m_view->setSelection(KTextEditor::Range(c1.line(), c1.column(), c2.line(), c2.column() + 1));
0046     }
0047 }
0048 
0049 void VisualViMode::selectLines(KTextEditor::Range range)
0050 {
0051     int sline = qMin(range.start().line(), range.end().line());
0052     int eline = qMax(range.start().line(), range.end().line());
0053     int ecol = m_view->doc()->lineLength(eline) + 1;
0054 
0055     m_view->setSelection(KTextEditor::Range(KTextEditor::Cursor(sline, 0), KTextEditor::Cursor(eline, ecol)));
0056 }
0057 
0058 void VisualViMode::goToPos(const Range &r)
0059 {
0060     KTextEditor::Cursor c = m_view->cursorPosition();
0061 
0062     if (r.startLine != -1 && r.startColumn != -1 && c == m_start) {
0063         m_start.setLine(r.startLine);
0064         m_start.setColumn(r.startColumn);
0065         c.setLine(r.endLine);
0066         c.setColumn(r.endColumn);
0067     } else if (r.startLine != -1 && r.startColumn != -1 && m_motionCanChangeWholeVisualModeSelection) {
0068         const KTextEditor::Cursor textObjectBegin(r.startLine, r.startColumn);
0069         if (textObjectBegin < m_start) {
0070             m_start.setLine(r.startLine);
0071             m_start.setColumn(r.startColumn);
0072             c.setLine(r.endLine);
0073             c.setColumn(r.endColumn);
0074         }
0075     } else {
0076         c.setLine(r.endLine);
0077         c.setColumn(r.endColumn);
0078     }
0079 
0080     if (c.line() >= doc()->lines()) {
0081         c.setLine(doc()->lines() - 1);
0082     }
0083 
0084     updateCursor(c);
0085 
0086     // Setting range for a command
0087     m_commandRange = Range(m_start, c, m_commandRange.motionType);
0088 
0089     // If visual mode is blockwise
0090     if (isVisualBlock()) {
0091         selectBlockInclusive(m_start, c);
0092 
0093         // Need to correct command range to make it inclusive.
0094         if ((c.line() < m_start.line() && c.column() > m_start.column()) || (c.line() > m_start.line() && c.column() < m_start.column())) {
0095             qSwap(m_commandRange.endColumn, m_commandRange.startColumn);
0096         }
0097         return;
0098     } else {
0099         m_view->setBlockSelection(false);
0100     }
0101 
0102     // If visual mode is linewise
0103     if (isVisualLine()) {
0104         selectLines(KTextEditor::Range(m_start, c));
0105         return;
0106     }
0107 
0108     // If visual mode is charwise
0109     selectInclusive(m_start, c);
0110 }
0111 
0112 void VisualViMode::reset()
0113 {
0114     m_mode = ViMode::VisualMode;
0115 
0116     // only switch to normal mode if still in visual mode. commands like c, s, ...
0117     // can have switched to insert mode
0118     if (m_viInputModeManager->isAnyVisualMode()) {
0119         saveRangeMarks();
0120         m_lastVisualMode = m_viInputModeManager->getCurrentViMode();
0121 
0122         // Return the cursor back to start of selection after.
0123         if (!m_pendingResetIsDueToExit) {
0124             KTextEditor::Cursor c = m_view->cursorPosition();
0125             if (m_start.line() != -1 && m_start.column() != -1) {
0126                 if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualLineMode) {
0127                     if (m_start.line() < c.line()) {
0128                         updateCursor(KTextEditor::Cursor(m_start.line(), 0));
0129                         m_stickyColumn = -1;
0130                     }
0131                 } else {
0132                     updateCursor(qMin(m_start, c));
0133                     m_stickyColumn = -1;
0134                 }
0135             }
0136         }
0137 
0138         if (m_viInputModeManager->getPreviousViMode() == ViMode::InsertMode) {
0139             startInsertMode();
0140         } else {
0141             startNormalMode();
0142         }
0143     }
0144 
0145     if (!m_commandShouldKeepSelection) {
0146         m_view->removeSelection();
0147     } else {
0148         m_commandShouldKeepSelection = false;
0149     }
0150 
0151     m_start.setPosition(-1, -1);
0152     m_pendingResetIsDueToExit = false;
0153 }
0154 
0155 void VisualViMode::saveRangeMarks()
0156 {
0157     // DO NOT save these marks if the
0158     // action that exited visual mode deleted the selection
0159     if (m_deleteCommand == false) {
0160         m_viInputModeManager->marks()->setSelectionStart(m_start);
0161         m_viInputModeManager->marks()->setSelectionFinish(m_view->cursorPosition());
0162     }
0163 }
0164 
0165 void VisualViMode::init()
0166 {
0167     // when using "gv" we already have a start position
0168     if (!m_start.isValid()) {
0169         m_start = m_view->cursorPosition();
0170     }
0171 
0172     if (isVisualLine()) {
0173         KTextEditor::Cursor c = m_view->cursorPosition();
0174         selectLines(KTextEditor::Range(c, c));
0175     }
0176 
0177     m_commandRange = Range(m_start, m_start, m_commandRange.motionType);
0178 }
0179 
0180 void VisualViMode::setVisualModeType(ViMode mode)
0181 {
0182     Q_ASSERT(mode == ViMode::VisualMode || mode == ViMode::VisualLineMode || mode == ViMode::VisualBlockMode);
0183     m_mode = mode;
0184 }
0185 
0186 void VisualViMode::switchStartEnd()
0187 {
0188     KTextEditor::Cursor c = m_start;
0189     m_start = m_view->cursorPosition();
0190 
0191     updateCursor(c);
0192 
0193     m_stickyColumn = -1;
0194 }
0195 
0196 void VisualViMode::goToPos(const KTextEditor::Cursor c)
0197 {
0198     Range r(c, InclusiveMotion);
0199     goToPos(r);
0200 }
0201 
0202 void VisualViMode::updateSelection()
0203 {
0204     if (!m_viInputModeManager->inputAdapter()->isActive()) {
0205         return;
0206     }
0207     if (m_viInputModeManager->isHandlingKeypress() && !m_isUndo) {
0208         return;
0209     }
0210 
0211     // If we are there it's already not VisualBlock mode.
0212     m_view->setBlockSelection(false);
0213 
0214     // If not valid going back to normal mode
0215     KTextEditor::Range r = m_view->selectionRange();
0216     if (!r.isValid()) {
0217         // Don't screw up the cursor's position. See BUG #337286.
0218         m_pendingResetIsDueToExit = true;
0219         reset();
0220         return;
0221     }
0222 
0223     // If already not in visual mode, it's time to go there.
0224     if (m_viInputModeManager->getCurrentViMode() != ViMode::VisualMode) {
0225         commandEnterVisualMode();
0226     }
0227 
0228     // Set range for commands
0229     m_start = (m_view->cursorPosition() == r.start()) ? r.end() : r.start();
0230     m_commandRange = Range(r.start(), r.end(), m_commandRange.motionType);
0231     // The end of the range seems to be one space forward of where it should be.
0232     m_commandRange.endColumn--;
0233 }
0234 
0235 #define ADDCMD(STR, FUNC, FLGS) Command(QStringLiteral(STR), &NormalViMode::FUNC, FLGS)
0236 
0237 #define ADDMOTION(STR, FUNC, FLGS) Motion(QStringLiteral(STR), &NormalViMode::FUNC, FLGS)
0238 
0239 const std::vector<Command> &VisualViMode::commands()
0240 {
0241     // init once, is expensive
0242     static std::vector<Command> global{
0243         ADDCMD("J", commandJoinLines, IS_CHANGE),
0244         ADDCMD("c", commandChange, IS_CHANGE),
0245         ADDCMD("s", commandChange, IS_CHANGE),
0246         ADDCMD("C", commandChangeToEOL, IS_CHANGE),
0247         ADDCMD("S", commandChangeToEOL, IS_CHANGE),
0248         ADDCMD("d", commandDelete, IS_CHANGE),
0249         ADDCMD("<delete>", commandDelete, IS_CHANGE),
0250         ADDCMD("D", commandDeleteToEOL, IS_CHANGE),
0251         ADDCMD("x", commandDeleteChar, IS_CHANGE),
0252         ADDCMD("X", commandDeleteCharBackward, IS_CHANGE),
0253         ADDCMD("gu", commandMakeLowercase, IS_CHANGE),
0254         ADDCMD("u", commandMakeLowercase, IS_CHANGE),
0255         ADDCMD("gU", commandMakeUppercase, IS_CHANGE),
0256         ADDCMD("g~", commandChangeCaseRange, IS_CHANGE),
0257         ADDCMD("U", commandMakeUppercase, IS_CHANGE),
0258         ADDCMD("y", commandYank, 0),
0259         ADDCMD("Y", commandYankToEOL, 0),
0260         ADDCMD("p", commandPaste, IS_CHANGE),
0261         ADDCMD("P", commandPasteBefore, IS_CHANGE),
0262         ADDCMD("r.", commandReplaceCharacter, IS_CHANGE | REGEX_PATTERN),
0263         ADDCMD(":", commandSwitchToCmdLine, SHOULD_NOT_RESET),
0264         ADDCMD("m.", commandSetMark, REGEX_PATTERN | SHOULD_NOT_RESET),
0265         ADDCMD(">", commandIndentLines, IS_CHANGE),
0266         ADDCMD("<", commandUnindentLines, IS_CHANGE),
0267         ADDCMD("<c-c>", commandAbort, 0),
0268         ADDCMD("<c-[>", commandAbort, 0),
0269         ADDCMD("ga", commandPrintCharacterCode, SHOULD_NOT_RESET),
0270         ADDCMD("v", commandEnterVisualMode, SHOULD_NOT_RESET),
0271         ADDCMD("V", commandEnterVisualLineMode, SHOULD_NOT_RESET),
0272         ADDCMD("o", commandToOtherEnd, SHOULD_NOT_RESET | CAN_LAND_INSIDE_FOLDING_RANGE),
0273         ADDCMD("=", commandAlignLines, SHOULD_NOT_RESET),
0274         ADDCMD("~", commandChangeCase, IS_CHANGE),
0275         ADDCMD("I", commandPrependToBlock, IS_CHANGE),
0276         ADDCMD("A", commandAppendToBlock, IS_CHANGE),
0277         ADDCMD("gq", commandFormatLines, IS_CHANGE),
0278         ADDCMD("q.", commandStartRecordingMacro, REGEX_PATTERN | SHOULD_NOT_RESET),
0279         ADDCMD("@.", commandReplayMacro, REGEX_PATTERN | SHOULD_NOT_RESET),
0280         ADDCMD("z.", commandCenterViewOnNonBlank, 0),
0281         ADDCMD("zz", commandCenterViewOnCursor, 0),
0282         ADDCMD("z<return>", commandTopViewOnNonBlank, 0),
0283         ADDCMD("zt", commandTopViewOnCursor, 0),
0284         ADDCMD("z-", commandBottomViewOnNonBlank, 0),
0285         ADDCMD("zb", commandBottomViewOnCursor, 0),
0286     };
0287     return global;
0288 }
0289 
0290 const std::vector<Motion> &VisualViMode::motions()
0291 {
0292     // init once, is expensive
0293     static std::vector<Motion> global{
0294         // regular motions
0295         ADDMOTION("h", motionLeft, 0),
0296         ADDMOTION("<left>", motionLeft, 0),
0297         ADDMOTION("<backspace>", motionLeft, 0),
0298         ADDMOTION("j", motionDown, 0),
0299         ADDMOTION("<down>", motionDown, 0),
0300         ADDMOTION("k", motionUp, 0),
0301         ADDMOTION("<up>", motionUp, 0),
0302         ADDMOTION("l", motionRight, 0),
0303         ADDMOTION("<right>", motionRight, 0),
0304         ADDMOTION(" ", motionRight, 0),
0305         ADDMOTION("$", motionToEOL, 0),
0306         ADDMOTION("<end>", motionToEOL, 0),
0307         ADDMOTION("0", motionToColumn0, 0),
0308         ADDMOTION("<home>", motionToColumn0, 0),
0309         ADDMOTION("^", motionToFirstCharacterOfLine, 0),
0310         ADDMOTION("f.", motionFindChar, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
0311         ADDMOTION("F.", motionFindCharBackward, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
0312         ADDMOTION("t.", motionToChar, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
0313         ADDMOTION("T.", motionToCharBackward, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
0314         ADDMOTION(";", motionRepeatlastTF, CAN_LAND_INSIDE_FOLDING_RANGE),
0315         ADDMOTION(",", motionRepeatlastTFBackward, CAN_LAND_INSIDE_FOLDING_RANGE),
0316         ADDMOTION("n", motionFindNext, CAN_LAND_INSIDE_FOLDING_RANGE),
0317         ADDMOTION("N", motionFindPrev, CAN_LAND_INSIDE_FOLDING_RANGE),
0318         ADDMOTION("gg", motionToLineFirst, 0),
0319         ADDMOTION("G", motionToLineLast, 0),
0320         ADDMOTION("w", motionWordForward, CAN_LAND_INSIDE_FOLDING_RANGE),
0321         ADDMOTION("W", motionWORDForward, CAN_LAND_INSIDE_FOLDING_RANGE),
0322         ADDMOTION("<c-right>", motionWordForward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
0323         ADDMOTION("<c-left>", motionWordBackward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
0324         ADDMOTION("b", motionWordBackward, CAN_LAND_INSIDE_FOLDING_RANGE),
0325         ADDMOTION("B", motionWORDBackward, CAN_LAND_INSIDE_FOLDING_RANGE),
0326         ADDMOTION("e", motionToEndOfWord, CAN_LAND_INSIDE_FOLDING_RANGE),
0327         ADDMOTION("E", motionToEndOfWORD, CAN_LAND_INSIDE_FOLDING_RANGE),
0328         ADDMOTION("ge", motionToEndOfPrevWord, CAN_LAND_INSIDE_FOLDING_RANGE),
0329         ADDMOTION("gE", motionToEndOfPrevWORD, CAN_LAND_INSIDE_FOLDING_RANGE),
0330         ADDMOTION("|", motionToScreenColumn, 0),
0331         ADDMOTION("%", motionToMatchingItem, CAN_LAND_INSIDE_FOLDING_RANGE),
0332         ADDMOTION("`.", motionToMark, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
0333         ADDMOTION("'.", motionToMarkLine, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
0334         ADDMOTION("[[", motionToPreviousBraceBlockStart, CAN_LAND_INSIDE_FOLDING_RANGE),
0335         ADDMOTION("]]", motionToNextBraceBlockStart, CAN_LAND_INSIDE_FOLDING_RANGE),
0336         ADDMOTION("[]", motionToPreviousBraceBlockEnd, CAN_LAND_INSIDE_FOLDING_RANGE),
0337         ADDMOTION("][", motionToNextBraceBlockEnd, CAN_LAND_INSIDE_FOLDING_RANGE),
0338         ADDMOTION("*", motionToNextOccurrence, CAN_LAND_INSIDE_FOLDING_RANGE),
0339         ADDMOTION("#", motionToPrevOccurrence, CAN_LAND_INSIDE_FOLDING_RANGE),
0340         ADDMOTION("<c-f>", motionPageDown, 0),
0341         ADDMOTION("<pagedown>", motionPageDown, 0),
0342         ADDMOTION("<c-b>", motionPageUp, 0),
0343         ADDMOTION("<pageup>", motionPageUp, 0),
0344         ADDMOTION("gj", motionToNextVisualLine, 0),
0345         ADDMOTION("g<down>", motionToNextVisualLine, 0),
0346         ADDMOTION("gk", motionToPrevVisualLine, 0),
0347         ADDMOTION("g<up>", motionToPrevVisualLine, 0),
0348         ADDMOTION("(", motionToPreviousSentence, CAN_LAND_INSIDE_FOLDING_RANGE),
0349         ADDMOTION(")", motionToNextSentence, CAN_LAND_INSIDE_FOLDING_RANGE),
0350         ADDMOTION("{", motionToBeforeParagraph, CAN_LAND_INSIDE_FOLDING_RANGE),
0351         ADDMOTION("}", motionToAfterParagraph, CAN_LAND_INSIDE_FOLDING_RANGE),
0352         ADDMOTION("<c-u>", motionHalfPageUp, 0),
0353         ADDMOTION("<c-d>", motionHalfPageDown, 0),
0354 
0355         // text objects
0356         ADDMOTION("iw", textObjectInnerWord, 0),
0357         ADDMOTION("aw", textObjectAWord, 0),
0358         ADDMOTION("iW", textObjectInnerWORD, 0),
0359         ADDMOTION("aW", textObjectAWORD, IS_NOT_LINEWISE),
0360         ADDMOTION("is", textObjectInnerSentence, IS_NOT_LINEWISE | CAN_CHANGE_WHOLE_VISUAL_MODE_SELECTION | CAN_LAND_INSIDE_FOLDING_RANGE),
0361         ADDMOTION("as", textObjectASentence, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE | CAN_LAND_INSIDE_FOLDING_RANGE),
0362         ADDMOTION("ip", textObjectInnerParagraph, IS_NOT_LINEWISE | CAN_CHANGE_WHOLE_VISUAL_MODE_SELECTION | CAN_LAND_INSIDE_FOLDING_RANGE),
0363         ADDMOTION("ap", textObjectAParagraph, IS_NOT_LINEWISE | CAN_CHANGE_WHOLE_VISUAL_MODE_SELECTION | CAN_LAND_INSIDE_FOLDING_RANGE),
0364         ADDMOTION("i\"", textObjectInnerQuoteDouble, CAN_CHANGE_WHOLE_VISUAL_MODE_SELECTION | CAN_LAND_INSIDE_FOLDING_RANGE),
0365         ADDMOTION("a\"", textObjectAQuoteDouble, CAN_LAND_INSIDE_FOLDING_RANGE),
0366         ADDMOTION("i'", textObjectInnerQuoteSingle, CAN_CHANGE_WHOLE_VISUAL_MODE_SELECTION | CAN_LAND_INSIDE_FOLDING_RANGE),
0367         ADDMOTION("a'", textObjectAQuoteSingle, CAN_LAND_INSIDE_FOLDING_RANGE),
0368         ADDMOTION("i[()b]", textObjectInnerParen, REGEX_PATTERN | CAN_CHANGE_WHOLE_VISUAL_MODE_SELECTION | CAN_LAND_INSIDE_FOLDING_RANGE),
0369         ADDMOTION("a[()b]", textObjectAParen, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
0370         ADDMOTION("i[{}B]",
0371                   textObjectInnerCurlyBracket,
0372                   REGEX_PATTERN | IS_NOT_LINEWISE | CAN_CHANGE_WHOLE_VISUAL_MODE_SELECTION | CAN_LAND_INSIDE_FOLDING_RANGE),
0373         ADDMOTION("a[{}B]", textObjectACurlyBracket, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
0374         ADDMOTION("i[><]",
0375                   textObjectInnerInequalitySign,
0376                   REGEX_PATTERN | IS_NOT_LINEWISE | CAN_CHANGE_WHOLE_VISUAL_MODE_SELECTION | CAN_LAND_INSIDE_FOLDING_RANGE),
0377         ADDMOTION("i[\\[\\]]", textObjectInnerBracket, REGEX_PATTERN | CAN_CHANGE_WHOLE_VISUAL_MODE_SELECTION | CAN_LAND_INSIDE_FOLDING_RANGE),
0378         ADDMOTION("a[\\[\\]]", textObjectABracket, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
0379         ADDMOTION("i,", textObjectInnerComma, CAN_LAND_INSIDE_FOLDING_RANGE),
0380         ADDMOTION("a,", textObjectAComma, CAN_LAND_INSIDE_FOLDING_RANGE),
0381 
0382         ADDMOTION("/<enter>", motionToIncrementalSearchMatch, CAN_LAND_INSIDE_FOLDING_RANGE),
0383         ADDMOTION("?<enter>", motionToIncrementalSearchMatch, CAN_LAND_INSIDE_FOLDING_RANGE),
0384     };
0385     return global;
0386 }
0387 
0388 #include "moc_visualvimode.cpp"