/// replacements cannot be applied, this returns an empty \c string.
std::string applyAllReplacements(StringRef Code, Replacements &Replaces);
+/// \brief Calculates how a code \p Position is shifted when \p Replaces are
+/// applied.
+unsigned shiftedCodePosition(const Replacements& Replaces, unsigned Position);
+
/// \brief A tool to run refactorings.
///
/// This is a refactoring specific version of \see ClangTool. FrontendActions
return Result;
}
+unsigned shiftedCodePosition(const Replacements &Replaces, unsigned Position) {
+ unsigned NewPosition = Position;
+ for (Replacements::iterator I = Replaces.begin(), E = Replaces.end(); I != E;
+ ++I) {
+ if (I->getOffset() >= Position)
+ break;
+ if (I->getOffset() + I->getLength() > Position)
+ NewPosition += I->getOffset() + I->getLength() - Position;
+ NewPosition += I->getReplacementText().size() - I->getLength();
+ }
+ return NewPosition;
+}
+
RefactoringTool::RefactoringTool(const CompilationDatabase &Compilations,
ArrayRef<std::string> SourcePaths)
: ClangTool(Compilations, SourcePaths) {}
--- /dev/null
+// RUN: grep -Ev "// *[A-Z-]+:" %s > %t2.cpp
+// RUN: clang-format -style=LLVM %t2.cpp -cursor=6 > %t.cpp
+// RUN: FileCheck -strict-whitespace -input-file=%t.cpp %s
+// CHECK: {{^\{ "Cursor": 4 \}$}}
+// CHECK: {{^int\ \i;$}}
+ int i;
cl::desc("Dump configuration options to stdout and exit.\n"
"Can be used with -style option."),
cl::cat(ClangFormatCategory));
+static cl::opt<unsigned>
+ Cursor("cursor",
+ cl::desc("The position of the cursor when invoking clang-format from"
+ " an editor integration"),
+ cl::init(0), cl::cat(ClangFormatCategory));
static cl::list<std::string> FileNames(cl::Positional, cl::desc("[<file> ...]"),
cl::cat(ClangFormatCategory));
Rewrite.getEditBuffer(ID).write(FileStream);
FileStream.flush();
} else {
+ if (Cursor != 0)
+ outs() << "{ \"Cursor\": " << tooling::shiftedCodePosition(
+ Replaces, Cursor) << " }\n";
Rewrite.getEditBuffer(ID).write(outs());
}
}
;; Depending on your configuration and coding style, you might need to modify
;; 'style' in clang-format, below.
+(require 'json)
+
;; *Location of the clang-format binary. If it is on your PATH, a full path name
;; need not be specified.
(defvar clang-format-binary "clang-format")
(call-process-region (point-min) (point-max) clang-format-binary t t nil
"-offset" (number-to-string (1- begin))
"-length" (number-to-string (- end begin))
+ "-cursor" (number-to-string (point))
"-style" style)
- (goto-char orig-point)
- (dotimes (index (length orig-windows))
- (set-window-start (nth index orig-windows)
- (nth index orig-window-starts))))))
+ (goto-char (point-min))
+ (let ((json-output (json-read-from-string
+ (buffer-substring-no-properties
+ (point-min) (line-beginning-position 2)))))
+ (delete-region (point-min) (line-beginning-position 2))
+ (goto-char (cdr (assoc 'Cursor json-output)))
+ (dotimes (index (length orig-windows))
+ (set-window-start (nth index orig-windows)
+ (nth index orig-window-starts)))))))
# It operates on the current, potentially unsaved buffer and does not create
# or save any files. To revert a formatting, just undo.
-import vim
+import json
import subprocess
+import vim
# Change this to the full path if clang-format is not on the path.
binary = 'clang-format'
# Get the current text.
buf = vim.current.buffer
-text = "\n".join(buf)
+text = '\n'.join(buf)
# Determine range to format.
+cursor = int(vim.eval('line2byte(line("."))+col(".")')) - 2
offset = int(vim.eval('line2byte(' +
str(vim.current.range.start + 1) + ')')) - 1
length = int(vim.eval('line2byte(' +
# Call formatter.
p = subprocess.Popen([binary, '-offset', str(offset), '-length', str(length),
- '-style', style],
+ '-style', style, '-cursor', str(cursor)],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
stdout, stderr = p.communicate(input=text)
if not stdout:
print ('No output from clang-format (crashed?).\n' +
'Please report to bugs.llvm.org.')
-elif stdout != text:
+else:
lines = stdout.split('\n')
- for i in range(min(len(buf), len(lines))):
- buf[i] = lines[i]
- for line in lines[len(buf):]:
- buf.append(line)
- del buf[len(lines):]
+ output = json.loads(lines[0])
+ lines = lines[1:]
+ if '\n'.join(lines) != text:
+ for i in range(min(len(buf), len(lines))):
+ buf[i] = lines[i]
+ for line in lines[len(buf):]:
+ buf.append(line)
+ del buf[len(lines):]
+ vim.command('goto %d' % (output['Cursor'] + 1))
EXPECT_EQ("z", Context.getRewrittenText(IDz));
}
+TEST(ShiftedCodePositionTest, FindsNewCodePosition) {
+ Replacements Replaces;
+ Replaces.insert(Replacement("", 0, 1, ""));
+ Replaces.insert(Replacement("", 4, 3, " "));
+ // Assume ' int i;' is turned into 'int i;' and cursor is located at '|'.
+ EXPECT_EQ(0u, shiftedCodePosition(Replaces, 0)); // |int i;
+ EXPECT_EQ(0u, shiftedCodePosition(Replaces, 1)); // |nt i;
+ EXPECT_EQ(1u, shiftedCodePosition(Replaces, 2)); // i|t i;
+ EXPECT_EQ(2u, shiftedCodePosition(Replaces, 3)); // in| i;
+ EXPECT_EQ(3u, shiftedCodePosition(Replaces, 4)); // int| i;
+ EXPECT_EQ(4u, shiftedCodePosition(Replaces, 5)); // int | i;
+ EXPECT_EQ(4u, shiftedCodePosition(Replaces, 6)); // int |i;
+ EXPECT_EQ(4u, shiftedCodePosition(Replaces, 7)); // int |;
+ EXPECT_EQ(5u, shiftedCodePosition(Replaces, 8)); // int i|
+}
+
+TEST(ShiftedCodePositionTest, FindsNewCodePositionWithInserts) {
+ Replacements Replaces;
+ Replaces.insert(Replacement("", 4, 0, "\"\n\""));
+ // Assume '"12345678"' is turned into '"1234"\n"5678"'.
+ EXPECT_EQ(4u, shiftedCodePosition(Replaces, 4)); // "123|5678"
+ EXPECT_EQ(8u, shiftedCodePosition(Replaces, 5)); // "1234|678"
+}
+
class FlushRewrittenFilesTest : public ::testing::Test {
public:
FlushRewrittenFilesTest() {