std::string CursorPlaceholder;
};
+// The two functions below, columnWidth() and columnWidthWithTabs(), were
+// adapted from similar functions in clang/lib/Format/Encoding.h.
+// FIXME: Move those functions to clang/include/clang/Format.h and reuse them?
+
+// Helper function for columnWidthWithTabs().
+inline unsigned columnWidth(StringRef Text) {
+ int ContentWidth = llvm::sys::unicode::columnWidthUTF8(Text);
+ if (ContentWidth < 0)
+ return Text.size(); // fallback for unprintable characters
+ return ContentWidth;
+}
+
+// Returns the number of columns required to display the \p Text on a terminal
+// with the \p TabWidth.
+inline unsigned columnWidthWithTabs(StringRef Text, unsigned TabWidth) {
+ unsigned TotalWidth = 0;
+ StringRef Tail = Text;
+ for (;;) {
+ StringRef::size_type TabPos = Tail.find('\t');
+ if (TabPos == StringRef::npos)
+ return TotalWidth + columnWidth(Tail);
+ TotalWidth += columnWidth(Tail.substr(0, TabPos));
+ if (TabWidth)
+ TotalWidth += TabWidth - TotalWidth % TabWidth;
+ Tail = Tail.substr(TabPos + 1);
+ }
+}
+
// After a newline:
// - we continue any line-comment that was split
// - we format the old line in addition to the cursor
// - we represent the cursor with a line comment to preserve the newline
IncrementalChanges getIncrementalChangesAfterNewline(llvm::StringRef Code,
- unsigned Cursor) {
+ unsigned Cursor,
+ unsigned TabWidth) {
IncrementalChanges Result;
// Before newline, code looked like:
// leading^trailing
if (!CommentMarker.empty() &&
(NewLineIsComment || !commentMarker(NextLine).empty() ||
(!TrailingTrim.empty() && !TrailingTrim.startswith("//")))) {
- using llvm::sys::unicode::columnWidthUTF8;
// We indent the new comment to match the previous one.
StringRef PreComment =
Leading.take_front(CommentMarker.data() - Leading.data());
std::string IndentAndComment =
- (std::string(columnWidthUTF8(PreComment), ' ') + CommentMarker + " ")
+ (std::string(columnWidthWithTabs(PreComment, TabWidth), ' ') +
+ CommentMarker + " ")
.str();
cantFail(
Result.Changes.add(replacement(Code, Indentation, IndentAndComment)));
}
IncrementalChanges getIncrementalChanges(llvm::StringRef Code, unsigned Cursor,
- llvm::StringRef InsertedText) {
+ llvm::StringRef InsertedText,
+ unsigned TabWidth) {
IncrementalChanges Result;
if (InsertedText == "\n")
- return getIncrementalChangesAfterNewline(Code, Cursor);
+ return getIncrementalChangesAfterNewline(Code, Cursor, TabWidth);
Result.CursorPlaceholder = " /**/";
return Result;
std::vector<tooling::Replacement>
formatIncremental(llvm::StringRef OriginalCode, unsigned OriginalCursor,
llvm::StringRef InsertedText, format::FormatStyle Style) {
- IncrementalChanges Incremental =
- getIncrementalChanges(OriginalCode, OriginalCursor, InsertedText);
+ IncrementalChanges Incremental = getIncrementalChanges(
+ OriginalCode, OriginalCursor, InsertedText, Style.TabWidth);
// Never *remove* lines in response to pressing enter! This annoys users.
if (InsertedText == "\n") {
Style.MaxEmptyLinesToKeep = 1000;
namespace clangd {
namespace {
-std::string afterTyped(llvm::StringRef CodeWithCursor,
- llvm::StringRef Typed) {
+std::string afterTyped(llvm::StringRef CodeWithCursor, llvm::StringRef Typed,
+ clang::format::FormatStyle Style) {
Annotations Code(CodeWithCursor);
unsigned Cursor = llvm::cantFail(positionToOffset(Code.code(), Code.point()));
- auto Changes =
- formatIncremental(Code.code(), Cursor, Typed,
- format::getGoogleStyle(format::FormatStyle::LK_Cpp));
+ auto Changes = formatIncremental(Code.code(), Cursor, Typed, Style);
tooling::Replacements Merged;
for (const auto& R : Changes)
if (llvm::Error E = Merged.add(R))
}
// We can't pass raw strings directly to EXPECT_EQ because of gcc bugs.
-void expectAfterNewline(const char *Before, const char *After) {
- EXPECT_EQ(After, afterTyped(Before, "\n")) << Before;
+void expectAfterNewline(const char *Before, const char *After,
+ format::FormatStyle Style = format::getGoogleStyle(
+ format::FormatStyle::LK_Cpp)) {
+ EXPECT_EQ(After, afterTyped(Before, "\n", Style)) << Before;
}
-void expectAfter(const char *Typed, const char *Before, const char *After) {
- EXPECT_EQ(After, afterTyped(Before, Typed)) << Before;
+void expectAfter(const char *Typed, const char *Before, const char *After,
+ format::FormatStyle Style =
+ format::getGoogleStyle(format::FormatStyle::LK_Cpp)) {
+ EXPECT_EQ(After, afterTyped(Before, Typed, Style)) << Before;
}
TEST(FormatIncremental, SplitComment) {
^
}
)cpp",
- R"cpp(
+ R"cpp(
void foo() {
if (x)
return; // All spelled tokens are accounted for.
^
}
)cpp");
+
+ // Handle tab character in leading indentation
+ format::FormatStyle TabStyle =
+ format::getGoogleStyle(format::FormatStyle::LK_Cpp);
+ TabStyle.UseTab = format::FormatStyle::UT_Always;
+ TabStyle.TabWidth = 4;
+ TabStyle.IndentWidth = 4;
+ // Do not use raw strings, otherwise '\t' will be interpreted literally.
+ expectAfterNewline("void foo() {\n\t// this comment was\n^split\n}\n",
+ "void foo() {\n\t// this comment was\n\t// ^split\n}\n",
+ TabStyle);
}
TEST(FormatIncremental, Indentation) {