1/*
2 SPDX-FileCopyrightText: 2008 Erlend Hamberg <ehamberg@gmail.com>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6#include "katedocument.h"
7#include "kateviinputmode.h"
8
9#include "kateview.h"
10#include <utils/kateconfig.h>
11#include <view/kateviewinternal.h>
12#include <vimode/emulatedcommandbar/emulatedcommandbar.h>
13#include <vimode/inputmodemanager.h>
14#include <vimode/marks.h>
15#include <vimode/modes/replacevimode.h>
16
17using namespace KateVi;
18
19ReplaceViMode::ReplaceViMode(InputModeManager *viInputModeManager, KTextEditor::ViewPrivate *view, KateViewInternal *viewInternal)
20 : ModeBase()
21{
22 m_view = view;
23 m_viewInternal = viewInternal;
24 m_viInputModeManager = viInputModeManager;
25 m_count = 1;
26}
27
28bool ReplaceViMode::commandInsertFromLine(int offset)
29{
30 KTextEditor::Cursor c(m_view->cursorPosition());
31
32 if (c.line() + offset >= doc()->lines() || c.line() + offset < 0) {
33 return false;
34 }
35
36 // Fetch the new character from the specified line.
37 KTextEditor::Cursor target(c.line() + offset, c.column());
38 QChar ch = doc()->characterAt(position: target);
39 if (ch == QChar::Null) {
40 return false;
41 }
42
43 // The cursor is at the end of the line: just append the char.
44 if (c.column() == doc()->lineLength(line: c.line())) {
45 return doc()->insertText(position: c, s: ch);
46 }
47
48 // We can replace the current one with the fetched character.
49 KTextEditor::Cursor next(c.line(), c.column() + 1);
50 QChar removed = doc()->line(line: c.line()).at(i: c.column());
51 if (doc()->replaceText(range: KTextEditor::Range(c, next), s: ch)) {
52 overwrittenChar(s: removed);
53 return true;
54 }
55 return false;
56}
57
58bool ReplaceViMode::commandMoveOneWordLeft()
59{
60 KTextEditor::Cursor c(m_view->cursorPosition());
61 c = findPrevWordStart(fromLine: c.line(), fromColumn: c.column());
62
63 if (!c.isValid()) {
64 c = KTextEditor::Cursor(0, 0);
65 }
66
67 updateCursor(c);
68 return true;
69}
70
71bool ReplaceViMode::commandMoveOneWordRight()
72{
73 KTextEditor::Cursor c(m_view->cursorPosition());
74 c = findNextWordStart(fromLine: c.line(), fromColumn: c.column());
75
76 if (!c.isValid()) {
77 c = doc()->documentEnd();
78 }
79
80 updateCursor(c);
81 return true;
82}
83
84bool ReplaceViMode::handleKeypress(const QKeyEvent *e)
85{
86 // backspace should work even if the shift key is down
87 if (e->modifiers() != CONTROL_MODIFIER && e->key() == Qt::Key_Backspace) {
88 backspace();
89 return true;
90 }
91
92 // on macOS the KeypadModifier is set for the arrow keys too
93 if (e->modifiers() == Qt::NoModifier || e->modifiers() == Qt::KeypadModifier) {
94 switch (e->key()) {
95 case Qt::Key_Escape:
96 m_overwritten.clear();
97 leaveReplaceMode();
98 return true;
99 case Qt::Key_Left:
100 m_overwritten.clear();
101 m_view->cursorLeft();
102 return true;
103 case Qt::Key_Right:
104 m_overwritten.clear();
105 m_view->cursorRight();
106 return true;
107 case Qt::Key_Up:
108 m_overwritten.clear();
109 m_view->up();
110 return true;
111 case Qt::Key_Down:
112 m_overwritten.clear();
113 m_view->down();
114 return true;
115 case Qt::Key_Home:
116 m_overwritten.clear();
117 m_view->home();
118 return true;
119 case Qt::Key_End:
120 m_overwritten.clear();
121 m_view->end();
122 return true;
123 case Qt::Key_PageUp:
124 m_overwritten.clear();
125 m_view->pageUp();
126 return true;
127 case Qt::Key_PageDown:
128 m_overwritten.clear();
129 m_view->pageDown();
130 return true;
131 case Qt::Key_Delete:
132 m_view->keyDelete();
133 return true;
134 case Qt::Key_Insert:
135 startInsertMode();
136 return true;
137 case Qt::Key_Enter:
138 case Qt::Key_Return:
139 if (m_viInputModeManager->inputAdapter()->viModeEmulatedCommandBar()->isSendingSyntheticSearchCompletedKeypress()) {
140 // BUG #451076, Do not record/send return for a newline when doing a search via Ctrl+F/Edit->Find menu
141 m_viInputModeManager->doNotLogCurrentKeypress();
142 return true;
143 }
144 Q_FALLTHROUGH();
145 default:
146 return false;
147 }
148 } else if (e->modifiers() == CONTROL_MODIFIER) {
149 switch (e->key()) {
150 case Qt::Key_BracketLeft:
151 case Qt::Key_C:
152 startNormalMode();
153 return true;
154 case Qt::Key_E:
155 commandInsertFromLine(offset: 1);
156 return true;
157 case Qt::Key_Y:
158 commandInsertFromLine(offset: -1);
159 return true;
160 case Qt::Key_W:
161 commandBackWord();
162 return true;
163 case Qt::Key_U:
164 commandBackLine();
165 return true;
166 case Qt::Key_Left:
167 m_overwritten.clear();
168 commandMoveOneWordLeft();
169 return true;
170 case Qt::Key_Right:
171 m_overwritten.clear();
172 commandMoveOneWordRight();
173 return true;
174 default:
175 return false;
176 }
177 }
178
179 return false;
180}
181
182void ReplaceViMode::backspace()
183{
184 KTextEditor::Cursor c1(m_view->cursorPosition());
185 KTextEditor::Cursor c2(c1.line(), c1.column() - 1);
186
187 if (c1.column() > 0) {
188 if (!m_overwritten.isEmpty()) {
189 doc()->removeText(range: KTextEditor::Range(c1.line(), c1.column() - 1, c1.line(), c1.column()));
190 doc()->insertText(position: c2, s: m_overwritten.right(n: 1));
191 m_overwritten.remove(i: m_overwritten.length() - 1, len: 1);
192 }
193 updateCursor(c: c2);
194 }
195}
196
197void ReplaceViMode::commandBackWord()
198{
199 KTextEditor::Cursor current(m_view->cursorPosition());
200 KTextEditor::Cursor to(findPrevWordStart(fromLine: current.line(), fromColumn: current.column()));
201
202 if (!to.isValid()) {
203 return;
204 }
205
206 while (current.isValid() && current != to) {
207 backspace();
208 current = m_view->cursorPosition();
209 }
210}
211
212void ReplaceViMode::commandBackLine()
213{
214 const int column = m_view->cursorPosition().column();
215
216 for (int i = column; i >= 0 && !m_overwritten.isEmpty(); i--) {
217 backspace();
218 }
219}
220
221void ReplaceViMode::leaveReplaceMode()
222{
223 // Redo replacement operation <count> times
224 m_view->abortCompletion();
225
226 if (m_count > 1) {
227 // Look at added text so that we can repeat the addition
228 const QString added = doc()->text(range: KTextEditor::Range(m_viInputModeManager->marks()->getStartEditYanked(), m_view->cursorPosition()));
229 for (unsigned int i = 0; i < m_count - 1; i++) {
230 KTextEditor::Cursor c(m_view->cursorPosition());
231 KTextEditor::Cursor c2(c.line(), c.column() + added.length());
232 doc()->replaceText(range: KTextEditor::Range(c, c2), s: added);
233 }
234 }
235
236 startNormalMode();
237}
238

source code of ktexteditor/src/vimode/modes/replacevimode.cpp