KTextEditor

commandrangeexpressionparser.cpp
1/*
2 SPDX-FileCopyrightText: 2008-2009 Erlend Hamberg <ehamberg@gmail.com>
3 SPDX-FileCopyrightText: 2011 Svyatoslav Kuzmich <svatoslav1@gmail.com>
4 SPDX-FileCopyrightText: 2012 Vegard Øye
5 SPDX-FileCopyrightText: 2013 Simon St James <kdedevel@etotheipiplusone.com>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "commandrangeexpressionparser.h"
11
12#include "katedocument.h"
13#include "kateview.h"
14#include "marks.h"
15#include <vimode/inputmodemanager.h>
16
17#include <QRegularExpression>
18#include <QStringList>
19
20using namespace KateVi;
21
22#define RegExp(name, pattern) \
23 inline const QRegularExpression &name() \
24 { \
25 static const QRegularExpression regex(QStringLiteral(pattern), QRegularExpression::UseUnicodePropertiesOption); \
26 return regex; \
27 }
28
29namespace
30{
31#define RE_MARK "\\'[0-9a-z><\\+\\*\\_]"
32#define RE_THISLINE "\\."
33#define RE_LASTLINE "\\$"
34#define RE_LINE "\\d+"
35#define RE_FORWARDSEARCH "/[^/]*/?"
36#define RE_BACKWARDSEARCH "\\?[^?]*\\??"
37#define RE_BASE "(?:" RE_MARK ")|(?:" RE_LINE ")|(?:" RE_THISLINE ")|(?:" RE_LASTLINE ")|(?:" RE_FORWARDSEARCH ")|(?:" RE_BACKWARDSEARCH ")"
38#define RE_OFFSET "[+-](?:" RE_BASE ")?"
39#define RE_POSITION "(" RE_BASE ")((?:" RE_OFFSET ")*)"
40
41RegExp(RE_Line, RE_LINE) RegExp(RE_LastLine, RE_LASTLINE) RegExp(RE_ThisLine, RE_THISLINE) RegExp(RE_Mark, RE_MARK) RegExp(RE_ForwardSearch, "^/([^/]*)/?$")
42 RegExp(RE_BackwardSearch, "^\\?([^?]*)\\??$") RegExp(RE_CalculatePositionSplit, "[-+](?!([+-]|$))")
43 // The range regexp contains seven groups: the first is the start position, the second is
44 // the base of the start position, the third is the offset of the start position, the
45 // fourth is the end position including a leading comma, the fifth is end position
46 // without the comma, the sixth is the base of the end position, and the seventh is the
47 // offset of the end position. The third and fourth groups may be empty, and the
48 // fifth, sixth and seventh groups are contingent on the fourth group.
49 inline const QRegularExpression &RE_CmdRange()
50{
51 static const QRegularExpression regex(QStringLiteral("^(" RE_POSITION ")((?:,(" RE_POSITION "))?)"));
52 return regex;
53}
54}
55
56CommandRangeExpressionParser::CommandRangeExpressionParser(InputModeManager *vimanager)
57 : m_viInputModeManager(vimanager)
58{
59}
60
61QString CommandRangeExpressionParser::parseRangeString(const QString &command)
62{
63 if (command.isEmpty()) {
64 return QString();
65 }
66
67 if (command.at(0) == QLatin1Char('%')) {
68 return QStringLiteral("%");
69 }
70
71 QRegularExpressionMatch rangeMatch = RE_CmdRange().match(command);
72
73 return rangeMatch.hasMatch() ? rangeMatch.captured() : QString();
74}
75
76KTextEditor::Range CommandRangeExpressionParser::parseRange(const QString &command, QString &destTransformedCommand) const
77{
78 if (command.isEmpty()) {
80 }
81
82 QString commandTmp = command;
83
84 // expand '%' to '1,$' ("all lines") if at the start of the line
85 if (commandTmp.at(0) == QLatin1Char('%')) {
86 commandTmp.replace(0, 1, QStringLiteral("1,$"));
87 }
88
89 QRegularExpressionMatch rangeMatch = RE_CmdRange().match(commandTmp);
90
91 if (!rangeMatch.hasMatch()) {
93 }
94
95 QString position_string1 = rangeMatch.captured(1);
96 QString position_string2 = rangeMatch.captured(4);
97 int position1 = calculatePosition(position_string1);
98 int position2 = (position_string2.isEmpty()) ? position1 : calculatePosition(rangeMatch.captured(5));
99
100 commandTmp.remove(RE_CmdRange());
101
102 // Vi indexes lines starting from 1; however, it does accept 0 as a valid line index
103 // and treats it as 1
104 position1 = (position1 == 0) ? 1 : position1;
105 position2 = (position2 == 0) ? 1 : position2;
106
107 // special case: if the command is just a number with an optional +/- prefix, rewrite to "goto"
108 if (commandTmp.isEmpty()) {
109 destTransformedCommand = QStringLiteral("goto %1").arg(position1);
111 } else {
112 destTransformedCommand = commandTmp;
113 return KTextEditor::Range(KTextEditor::Range(position1 - 1, 0, position2 - 1, 0));
114 }
115}
116
117int CommandRangeExpressionParser::calculatePosition(const QString &string) const
118{
119 int pos = 0;
120 QList<bool> operators_list;
121 const QStringList split = string.split(RE_CalculatePositionSplit());
122 QList<int> values;
123
124 for (const QString &line : split) {
125 pos += line.size();
126
127 if (pos < string.size()) {
128 if (string.at(pos) == QLatin1Char('+')) {
129 operators_list.push_back(true);
130 } else if (string.at(pos) == QLatin1Char('-')) {
131 operators_list.push_back(false);
132 } else {
133 Q_ASSERT(false);
134 }
135 }
136
137 ++pos;
138
139 matchLineNumber(line, values) || matchLastLine(line, values) || matchThisLine(line, values) || matchMark(line, values)
140 || matchForwardSearch(line, values) || matchBackwardSearch(line, values);
141 }
142
143 if (values.isEmpty()) {
144 return -1;
145 }
146
147 int result = values.at(0);
148 for (int i = 0; i < operators_list.size(); ++i) {
149 if (operators_list.at(i)) {
150 result += values.at(i + 1);
151 } else {
152 result -= values.at(i + 1);
153 }
154 }
155
156 return result;
157}
158
159bool CommandRangeExpressionParser::matchLineNumber(const QString &line, QList<int> &values)
160{
161 QRegularExpressionMatch match = RE_Line().match(line);
162
163 if (!match.hasMatch() || match.capturedLength() != line.length()) {
164 return false;
165 }
166
167 values.push_back(line.toInt());
168 return true;
169}
170
171bool CommandRangeExpressionParser::matchLastLine(const QString &line, QList<int> &values) const
172{
173 QRegularExpressionMatch match = RE_LastLine().match(line);
174
175 if (!match.hasMatch() || match.capturedLength() != line.length()) {
176 return false;
177 }
178
179 values.push_back(m_viInputModeManager->view()->doc()->lines());
180 return true;
181}
182
183bool CommandRangeExpressionParser::matchThisLine(const QString &line, QList<int> &values) const
184{
185 QRegularExpressionMatch match = RE_ThisLine().match(line);
186
187 if (!match.hasMatch() || match.capturedLength() != line.length()) {
188 return false;
189 }
190
191 values.push_back(m_viInputModeManager->view()->cursorPosition().line() + 1);
192 return true;
193}
194
195bool CommandRangeExpressionParser::matchMark(const QString &line, QList<int> &values) const
196{
197 QRegularExpressionMatch match = RE_Mark().match(line);
198
199 if (!match.hasMatch() || match.capturedLength() != line.length()) {
200 return false;
201 }
202
203 values.push_back(m_viInputModeManager->marks()->getMarkPosition(line.at(1)).line() + 1);
204 return true;
205}
206
207bool CommandRangeExpressionParser::matchForwardSearch(const QString &line, QList<int> &values) const
208{
209 QRegularExpressionMatch match = RE_ForwardSearch().match(line);
210
211 if (!match.hasMatch()) {
212 return false;
213 }
214
215 QString pattern = match.captured(1);
216 KTextEditor::Range range(m_viInputModeManager->view()->cursorPosition(), m_viInputModeManager->view()->doc()->documentEnd());
217 QList<KTextEditor::Range> matchingLines = m_viInputModeManager->view()->doc()->searchText(range, pattern, KTextEditor::Regex);
218
219 if (matchingLines.isEmpty()) {
220 return true;
221 }
222
223 int lineNumber = matchingLines.first().start().line();
224
225 values.push_back(lineNumber + 1);
226 return true;
227}
228
229bool CommandRangeExpressionParser::matchBackwardSearch(const QString &line, QList<int> &values) const
230{
231 QRegularExpressionMatch match = RE_BackwardSearch().match(line);
232
233 if (!match.hasMatch()) {
234 return false;
235 }
236
237 QString pattern = match.captured(1);
238 KTextEditor::Range range(KTextEditor::Cursor(0, 0), m_viInputModeManager->view()->cursorPosition());
239 QList<KTextEditor::Range> matchingLines = m_viInputModeManager->view()->doc()->searchText(range, pattern, KTextEditor::Regex);
240
241 if (matchingLines.isEmpty()) {
242 return true;
243 }
244
245 int lineNumber = matchingLines.first().start().line();
246
247 values.push_back(lineNumber + 1);
248 return true;
249}
The Cursor represents a position in a Document.
Definition cursor.h:75
An object representing a section of text, from one Cursor to another.
static constexpr Range invalid() noexcept
Returns an invalid range.
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
const T & at(int i) const const
T & first()
bool isEmpty() const const
void push_back(const T &value)
int size() const const
QString captured(int nth) const const
bool hasMatch() const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
const QChar at(int position) const const
bool isEmpty() const const
int length() const const
QString & remove(int position, int n)
QString & replace(int position, int n, QChar after)
int toInt(bool *ok, int base) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Feb 24 2024 20:00:58 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.