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 "katecommandrangeexpressionparser.h"
11
12#include "katedocument.h"
13#include "kateview.h"
14
15#include <QStringList>
16
17using KTextEditor::Cursor;
18using KTextEditor::Range;
19
20CommandRangeExpressionParser::CommandRangeExpressionParser()
21{
22 m_line = QStringLiteral("\\d+");
23 m_lastLine = QStringLiteral("\\$");
24 m_thisLine = QStringLiteral("\\.");
25 m_mark = QStringLiteral("\\'[0-9a-z><\\+\\*\\_]");
26
27 m_forwardSearch = QStringLiteral("/([^/]*)/?");
28 m_forwardSearch2 = QStringLiteral("/[^/]*/?"); // no group
29 m_backwardSearch = QStringLiteral("\\?([^?]*)\\??");
30 m_backwardSearch2 = QStringLiteral("\\?[^?]*\\??"); // no group
31 m_base = QLatin1String("(?:%1)").arg(args&: m_mark) + QLatin1String("|(?:%1)").arg(args&: m_line) + QLatin1String("|(?:%1)").arg(args&: m_thisLine)
32 + QLatin1String("|(?:%1)").arg(args&: m_lastLine) + QLatin1String("|(?:%1)").arg(args&: m_forwardSearch2) + QLatin1String("|(?:%1)").arg(args&: m_backwardSearch2);
33 m_offset = QLatin1String("[+-](?:%1)?").arg(args&: m_base);
34
35 // The position regexp contains two groups: the base and the offset.
36 // The offset may be empty.
37 m_position = QStringLiteral("(%1)((?:%2)*)").arg(args&: m_base, args&: m_offset);
38
39 // The range regexp contains seven groups: the first is the start position, the second is
40 // the base of the start position, the third is the offset of the start position, the
41 // fourth is the end position including a leading comma, the fifth is end position
42 // without the comma, the sixth is the base of the end position, and the seventh is the
43 // offset of the end position. The third and fourth groups may be empty, and the
44 // fifth, sixth and seventh groups are contingent on the fourth group.
45 m_cmdRangeRegex.setPattern(QStringLiteral("^(%1)((?:,(%1))?)").arg(a: m_position));
46}
47
48Range CommandRangeExpressionParser::parseRangeExpression(const QString &command,
49 KTextEditor::ViewPrivate *view,
50 QString &destRangeExpression,
51 QString &destTransformedCommand)
52{
53 CommandRangeExpressionParser rangeExpressionParser;
54 return rangeExpressionParser.parseRangeExpression(command, destRangeExpression, destTransformedCommand, view);
55}
56
57Range CommandRangeExpressionParser::parseRangeExpression(const QString &command,
58 QString &destRangeExpression,
59 QString &destTransformedCommand,
60 KTextEditor::ViewPrivate *view)
61{
62 Range parsedRange(0, -1, 0, -1);
63 if (command.isEmpty()) {
64 return parsedRange;
65 }
66 QString commandTmp = command;
67 bool leadingRangeWasPercent = false;
68 // expand '%' to '1,$' ("all lines") if at the start of the line
69 if (commandTmp.at(i: 0) == QLatin1Char('%')) {
70 commandTmp.replace(i: 0, len: 1, QStringLiteral("1,$"));
71 leadingRangeWasPercent = true;
72 }
73
74 const auto match = m_cmdRangeRegex.match(subject: commandTmp);
75 if (match.hasMatch() && match.capturedLength(nth: 0) > 0) {
76 commandTmp.remove(re: m_cmdRangeRegex);
77
78 const QString position_string1 = match.captured(nth: 1);
79 QString position_string2 = match.captured(nth: 4);
80 int position1 = calculatePosition(string: position_string1, view);
81
82 int position2;
83 if (!position_string2.isEmpty()) {
84 // remove the comma
85 position_string2 = match.captured(nth: 5);
86 position2 = calculatePosition(string: position_string2, view);
87 } else {
88 position2 = position1;
89 }
90
91 // special case: if the command is just a number with an optional +/- prefix, rewrite to "goto"
92 if (commandTmp.isEmpty()) {
93 commandTmp = QStringLiteral("goto %1").arg(a: position1);
94 } else {
95 parsedRange.setRange(KTextEditor::Range(position1 - 1, 0, position2 - 1, 0));
96 }
97
98 destRangeExpression = leadingRangeWasPercent ? QStringLiteral("%") : match.captured(nth: 0);
99 destTransformedCommand = commandTmp;
100 }
101
102 return parsedRange;
103}
104
105int CommandRangeExpressionParser::calculatePosition(const QString &string, KTextEditor::ViewPrivate *view)
106{
107 int pos = 0;
108 std::vector<bool> operators_list;
109 const QStringList split = string.split(sep: QRegularExpression(QStringLiteral("[-+](?!([+-]|$))")));
110 std::vector<int> values;
111
112 for (const QString &line : split) {
113 pos += line.size();
114
115 if (pos < string.size()) {
116 if (string.at(i: pos) == QLatin1Char('+')) {
117 operators_list.push_back(x: true);
118 } else if (string.at(i: pos) == QLatin1Char('-')) {
119 operators_list.push_back(x: false);
120 } else {
121 Q_ASSERT(false);
122 }
123 }
124
125 ++pos;
126
127 static const auto lineRe = QRegularExpression(QRegularExpression::anchoredPattern(expression: m_line));
128 static const auto lastLineRe = QRegularExpression(QRegularExpression::anchoredPattern(expression: m_lastLine));
129 static const auto thisLineRe = QRegularExpression(QRegularExpression::anchoredPattern(expression: m_thisLine));
130 static const auto forwardSearchRe = QRegularExpression(QRegularExpression::anchoredPattern(expression: m_forwardSearch));
131 static const auto backwardSearchRe = QRegularExpression(QRegularExpression::anchoredPattern(expression: m_backwardSearch));
132
133 QRegularExpressionMatch rmatch;
134 if (lineRe.match(subject: line).hasMatch()) {
135 values.push_back(x: line.toInt());
136 } else if (lastLineRe.match(subject: line).hasMatch()) {
137 values.push_back(x: view->doc()->lines());
138 } else if (thisLineRe.match(subject: line).hasMatch()) {
139 values.push_back(x: view->cursorPosition().line() + 1);
140 } else if (line.contains(re: forwardSearchRe, rmatch: &rmatch)) {
141 const QString pattern = rmatch.captured(nth: 1);
142 const int matchLine =
143 view->doc()->searchText(range: Range(view->cursorPosition(), view->doc()->documentEnd()), pattern, options: KTextEditor::Regex).first().start().line();
144 values.push_back(x: (matchLine < 0) ? -1 : matchLine + 1);
145 } else if (line.contains(re: backwardSearchRe, rmatch: &rmatch)) {
146 const QString pattern = rmatch.captured(nth: 1);
147 const int matchLine = view->doc()->searchText(range: Range(Cursor(0, 0), view->cursorPosition()), pattern, options: KTextEditor::Regex).first().start().line();
148 values.push_back(x: (matchLine < 0) ? -1 : matchLine + 1);
149 }
150 }
151
152 if (values.empty()) {
153 return -1;
154 }
155
156 int result = values.at(n: 0);
157 for (size_t i = 0; i < operators_list.size(); ++i) {
158 if (operators_list.at(n: i) == true) {
159 result += values.at(n: i + 1);
160 } else {
161 result -= values.at(n: i + 1);
162 }
163 }
164
165 return result;
166}
167

source code of ktexteditor/src/utils/katecommandrangeexpressionparser.cpp