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

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