From 2a250b8b49e70f53b185749a94956d54d941ef2d Mon Sep 17 00:00:00 2001 From: Daniel Jasper Date: Tue, 21 May 2013 12:21:39 +0000 Subject: [PATCH] Let clang-format move the cursor appropriately. With this patch, clang-format will try to keep the cursor at the original code position in editor integrations (implemented for emacs and vim). This means, after formatting, clang-format will try to keep the cursor on the same character of the same token. llvm-svn: 182373 --- clang/include/clang/Tooling/Refactoring.h | 4 ++++ clang/lib/Tooling/Refactoring.cpp | 13 +++++++++++ clang/test/Format/cursor.cpp | 6 ++++++ clang/tools/clang-format/ClangFormat.cpp | 8 +++++++ clang/tools/clang-format/clang-format.el | 16 ++++++++++---- clang/tools/clang-format/clang-format.py | 24 +++++++++++++-------- clang/unittests/Tooling/RefactoringTest.cpp | 24 +++++++++++++++++++++ 7 files changed, 82 insertions(+), 13 deletions(-) create mode 100644 clang/test/Format/cursor.cpp diff --git a/clang/include/clang/Tooling/Refactoring.h b/clang/include/clang/Tooling/Refactoring.h index 10bae096144a..d1a9e1edd081 100644 --- a/clang/include/clang/Tooling/Refactoring.h +++ b/clang/include/clang/Tooling/Refactoring.h @@ -132,6 +132,10 @@ bool applyAllReplacements(Replacements &Replaces, Rewriter &Rewrite); /// 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 diff --git a/clang/lib/Tooling/Refactoring.cpp b/clang/lib/Tooling/Refactoring.cpp index f795def74ff5..db68f42725a6 100644 --- a/clang/lib/Tooling/Refactoring.cpp +++ b/clang/lib/Tooling/Refactoring.cpp @@ -166,6 +166,19 @@ std::string applyAllReplacements(StringRef Code, Replacements &Replaces) { 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 SourcePaths) : ClangTool(Compilations, SourcePaths) {} diff --git a/clang/test/Format/cursor.cpp b/clang/test/Format/cursor.cpp new file mode 100644 index 000000000000..d9aab5acb635 --- /dev/null +++ b/clang/test/Format/cursor.cpp @@ -0,0 +1,6 @@ +// 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; diff --git a/clang/tools/clang-format/ClangFormat.cpp b/clang/tools/clang-format/ClangFormat.cpp index fccd6bf25714..340ef5587ff9 100644 --- a/clang/tools/clang-format/ClangFormat.cpp +++ b/clang/tools/clang-format/ClangFormat.cpp @@ -77,6 +77,11 @@ static cl::opt cl::desc("Dump configuration options to stdout and exit.\n" "Can be used with -style option."), cl::cat(ClangFormatCategory)); +static cl::opt + 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 FileNames(cl::Positional, cl::desc("[ ...]"), cl::cat(ClangFormatCategory)); @@ -221,6 +226,9 @@ static bool format(std::string FileName) { Rewrite.getEditBuffer(ID).write(FileStream); FileStream.flush(); } else { + if (Cursor != 0) + outs() << "{ \"Cursor\": " << tooling::shiftedCodePosition( + Replaces, Cursor) << " }\n"; Rewrite.getEditBuffer(ID).write(outs()); } } diff --git a/clang/tools/clang-format/clang-format.el b/clang/tools/clang-format/clang-format.el index 57475ac72201..af69118aa2d5 100644 --- a/clang/tools/clang-format/clang-format.el +++ b/clang/tools/clang-format/clang-format.el @@ -9,6 +9,8 @@ ;; 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") @@ -38,8 +40,14 @@ (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))))))) diff --git a/clang/tools/clang-format/clang-format.py b/clang/tools/clang-format/clang-format.py index d90c62a5bf68..bc47fcbb7d2e 100644 --- a/clang/tools/clang-format/clang-format.py +++ b/clang/tools/clang-format/clang-format.py @@ -17,8 +17,9 @@ # 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' @@ -29,9 +30,10 @@ style = 'LLVM' # 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(' + @@ -39,7 +41,7 @@ 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) @@ -56,10 +58,14 @@ if stderr: 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)) diff --git a/clang/unittests/Tooling/RefactoringTest.cpp b/clang/unittests/Tooling/RefactoringTest.cpp index 3e0d7280b1d3..cea6c417c63c 100644 --- a/clang/unittests/Tooling/RefactoringTest.cpp +++ b/clang/unittests/Tooling/RefactoringTest.cpp @@ -151,6 +151,30 @@ TEST_F(ReplacementTest, ApplyAllFailsIfOneApplyFails) { 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() {