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

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