Commit 80b73725 authored by Lukas Holecek's avatar Lukas Holecek Committed by hjk
Browse files

fakevim: Fix substitute command



Command separator (bar character) shouldn't break substitute command.

Escape substitution text.

Change-Id: I9a793ab2df167d5d8a2b4de22329d061fc169785
Reviewed-by: default avatarhjk <qthjk@ovi.com>
parent f6d99b34
......@@ -1235,6 +1235,62 @@ void FakeVimPlugin::test_vim_code_folding()
// Opening folds recursively isn't supported (previous position in fold isn't restored).
}
void FakeVimPlugin::test_vim_substitute()
{
TestData data;
setup(&data);
data.setText("abcabc");
COMMAND("s/abc/123/", X "123abc");
COMMAND("u", X "abcabc");
COMMAND("s/abc/123/g", X "123123");
COMMAND("u", X "abcabc");
data.setText("abc" N "def");
COMMAND("%s/^/ -- /", " -- abc" N " " X "-- def");
COMMAND("u", X "abc" N "def");
data.setText(" abc" N " def");
COMMAND("%s/$/./", " abc." N " " X "def.");
data.setText("abc" N "def");
COMMAND("%s/.*/(&)", "(abc)" N X "(def)");
COMMAND("u", X "abc" N "def");
COMMAND("%s/.*/X/g", "X" N X "X");
data.setText("abc" N "" N "def");
COMMAND("%s/^\\|$/--", "--abc" N "--" N X "--def");
COMMAND("u", X "abc" N "" N "def");
COMMAND("%s/^\\|$/--/g", "--abc--" N "--" N X "--def--");
// captures
data.setText("abc def ghi");
COMMAND("s/\\w\\+/'&'/g", X "'abc' 'def' 'ghi'");
COMMAND("u", X "abc def ghi");
COMMAND("s/\\w\\+/'\\&'/g", X "'&' '&' '&'");
COMMAND("u", X "abc def ghi");
COMMAND("s/\\(\\w\\{3}\\)/(\\1)/g", X "(abc) (def) (ghi)");
COMMAND("u", X "abc def ghi");
COMMAND("s/\\(\\w\\{3}\\) \\(\\w\\{3\\}\\)/\\2 \\1 \\\\1/g", X "def abc \\1 ghi");
// case-insensitive
data.setText("abc ABC abc");
COMMAND("s/ABC/123/gi", X "123 123 123");
// replace on a line
data.setText("abc" N "def" N "ghi");
COMMAND("2s/^/ + /", "abc" N " " X "+ def" N "ghi");
COMMAND("1s/^/ * /", " " X "* abc" N " + def" N "ghi");
COMMAND("$s/^/ - /", " * abc" N " + def" N " " X "- ghi");
// replace on lines
data.setText("abc" N "def" N "ghi");
COMMAND("2,$s/^/ + /", "abc" N " + def" N " " X "+ ghi");
COMMAND("1,2s/^/ * /", " * abc" N " " X "* + def" N " + ghi");
COMMAND("3,3s/^/ - /", " * abc" N " * + def" N " " X "- + ghi");
COMMAND("%s/\\( \\S \\)*//g", "abc" N "def" N X "ghi");
}
void FakeVimPlugin::test_advanced_commands()
{
TestData data;
......@@ -1250,6 +1306,10 @@ void FakeVimPlugin::test_advanced_commands()
// redundant characters
COMMAND("::: %s/\\S\\S\\S/ZZZ/g | :::: %s/ZZZ/XXX/g ", "XXX" N " XXX" N " XXX" N X "XXX");
// bar character in regular expression is not command separator
data.setText("abc");
COMMAND("%s/a\\|b\\||/X/g|%s/[^X]/Y/g", "XXY");
}
void FakeVimPlugin::test_map()
......
......@@ -420,6 +420,49 @@ static QRegExp vimPatternToQtPattern(QString needle, bool smartcase)
return QRegExp(pattern);
}
static bool substituteText(QString *text, QRegExp &pattern, const QString &replacement,
bool global)
{
bool substituted = false;
int pos = 0;
while (true) {
pos = pattern.indexIn(*text, pos, QRegExp::CaretAtZero);
if (pos == -1)
break;
substituted = true;
QString matched = text->mid(pos, pattern.cap(0).size());
QString repl;
bool escape = false;
// insert captured texts
for (int i = 0; i < replacement.size(); ++i) {
const QChar &c = replacement[i];
if (escape) {
escape = false;
if (c.isDigit()) {
if (c.digitValue() <= pattern.captureCount())
repl += pattern.cap(c.digitValue());
} else {
repl += c;
}
} else {
if (c == '\\')
escape = true;
else if (c == '&')
repl += pattern.cap(0);
else
repl += c;
}
}
text->replace(pos, matched.size(), repl);
pos += qMax(1, repl.size());
if (pos >= text->size() || !global)
break;
}
return substituted;
}
static void setClipboardData(const QString &content, RangeMode mode,
QClipboard::Mode clipboardMode)
{
......@@ -476,7 +519,34 @@ bool ExCommand::matches(const QString &min, const QString &full) const
void ExCommand::setContentsFromLine(const QString &line)
{
// split command to subcommands
subCommands = line.split('|');
QChar close;
bool subst = false;
int j = 0;
for (int i = 0; i < line.size(); ++i) {
const QChar c = line[i];
if (c == '\\') {
++i; // skip escaped character
} else if (close.isNull()) {
if (c == '|') {
// split on |
subCommands.append(line.mid(j, i - j));
j = i + 1;
} else if (c == '/') {
subst = i > 0 && (line[i - 1] == 's');
close = c;
} else if (c == '"' || c == '\'') {
close = c;
}
} else if (c == close) {
if (subst)
subst = false;
else
close = QChar();
}
}
if (j < line.size())
subCommands.append(line.mid(j));
}
bool ExCommand::nextSubcommand()
......@@ -4032,7 +4102,7 @@ bool FakeVimHandler::Private::handleExSubstituteCommand(const ExCommand &cmd)
QString flags;
QRegExp pattern;
QString replacement;
int count = 0;
int count = 1;
if (cmd.cmd.startsWith("&&")) {
flags = cmd.cmd.mid(2);
......@@ -4093,6 +4163,9 @@ bool FakeVimHandler::Private::handleExSubstituteCommand(const ExCommand &cmd)
replacement = line.mid(pos1 + 1, pos2 - pos1 - 1);
flags = line.mid(pos2 + 1);
if (flags.contains('i'))
needle.prepend("\\c");
pattern = vimPatternToQtPattern(needle, hasConfig(ConfigSmartCase));
m_lastSubstituteFlags = flags;
......@@ -4100,55 +4173,23 @@ bool FakeVimHandler::Private::handleExSubstituteCommand(const ExCommand &cmd)
m_lastSubstituteReplacement = replacement;
}
if (count == 0)
count = 1;
if (flags.contains('i'))
pattern.setCaseSensitivity(Qt::CaseInsensitive);
int lastLine = -1;
int firstLine = -1;
const bool global = flags.contains('g');
for (int a = 0; a != count; ++a) {
const Range range = cmd.range.endPos == 0 ? rangeFromCurrentLine() : cmd.range;
const Range range = cmd.range.endPos <= 0 ? rangeFromCurrentLine() : cmd.range;
const int beginLine = lineForPosition(range.beginPos);
const int endLine = lineForPosition(range.endPos);
for (int line = endLine; line >= beginLine; --line) {
QString origText = lineContents(line);
QString text = origText;
int pos = 0;
while (true) {
pos = pattern.indexIn(text, pos, QRegExp::CaretAtZero);
if (pos == -1)
break;
if (pattern.cap(0).isEmpty())
break;
QStringList caps = pattern.capturedTexts();
QString matched = text.mid(pos, caps.at(0).size());
QString repl = replacement;
for (int i = 1; i < caps.size(); ++i)
repl.replace("\\" + QString::number(i), caps.at(i));
for (int i = 0; i < repl.size(); ++i) {
if (repl.at(i) == '&' && (i == 0 || repl.at(i - 1) != '\\')) {
repl.replace(i, 1, caps.at(0));
i += caps.at(0).size();
}
}
repl.replace("\\&", "&");
text = text.left(pos) + repl + text.mid(pos + matched.size());
pos += repl.size();
QString text = lineContents(line);
if (substituteText(&text, pattern, replacement, global)) {
firstLine = line;
if (lastLine == -1) {
lastLine = line;
beginEditBlock();
}
if (!global)
break;
}
if (text != origText)
setLineContents(line, text);
}
}
}
......
......@@ -79,6 +79,7 @@ private slots:
void test_vim_letter_case();
void test_vim_code_autoindent();
void test_vim_code_folding();
void test_vim_substitute();
void test_advanced_commands();
void test_map();
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment