10 #include "clang/Basic/SourceManager.h" 11 #include "clang/Format/Format.h" 12 #include "clang/Lex/Lexer.h" 13 #include "clang/Tooling/Core/Replacement.h" 14 #include "llvm/Support/Unicode.h" 25 SourceManagerForFile FileSM(
"dummy.cpp", Code);
26 auto &SM = FileSM.get();
27 FileID FID = SM.getMainFileID();
28 Lexer Lex(FID, SM.getBuffer(FID), SM, format::getFormattingLangOpts(Style));
30 std::vector<char> Brackets;
31 while (!Lex.LexFromRawLexer(Tok)) {
32 switch(Tok.getKind()) {
34 Brackets.push_back(
')');
37 Brackets.push_back(
'}');
40 Brackets.push_back(
']');
43 if (!Brackets.empty() && Brackets.back() ==
')')
47 if (!Brackets.empty() && Brackets.back() ==
'}')
51 if (!Brackets.empty() && Brackets.back() ==
']')
59 Code.append(
"\n// */\n");
60 Code.append(Brackets.rbegin(), Brackets.rend());
63 static StringRef commentMarker(llvm::StringRef
Line) {
64 for (StringRef Marker : {
"///",
"//"}){
65 auto I = Line.rfind(Marker);
66 if (I != StringRef::npos)
67 return Line.substr(I, Marker.size());
72 llvm::StringRef firstLine(llvm::StringRef Code) {
73 return Code.take_until([](
char C) {
return C ==
'\n'; });
76 llvm::StringRef lastLine(llvm::StringRef Code) {
77 llvm::StringRef Rest = Code;
78 while (!Rest.empty() && Rest.back() !=
'\n')
79 Rest = Rest.drop_back();
80 return Code.substr(Rest.size());
85 llvm::StringRef
Filename =
"<stdin>";
88 tooling::Replacement replacement(llvm::StringRef Code, llvm::StringRef From,
90 assert(From.begin() >= Code.begin() && From.end() <= Code.end());
92 return tooling::Replacement(Filename, From.data() - Code.data(),
106 struct IncrementalChanges {
122 IncrementalChanges getIncrementalChangesAfterNewline(llvm::StringRef Code,
124 IncrementalChanges
Result;
131 StringRef Trailing = firstLine(Code.substr(Cursor));
132 StringRef Indentation = lastLine(Code.take_front(Cursor));
133 if (Indentation.data() == Code.data()) {
134 vlog(
"Typed a newline, but we're still on the first line!");
138 lastLine(Code.take_front(Indentation.data() - Code.data() - 1));
139 StringRef NextLine = firstLine(Code.substr(Cursor + Trailing.size() + 1));
142 StringRef TrailingTrim = Trailing.ltrim();
143 if (
unsigned TrailWS = Trailing.size() - TrailingTrim.size())
144 cantFail(Result.Changes.add(
145 replacement(Code, StringRef(Trailing.begin(), TrailWS),
"")));
149 StringRef CommentMarker = commentMarker(Leading);
150 bool NewLineIsComment = !commentMarker(Indentation).empty();
151 if (!CommentMarker.empty() &&
152 (NewLineIsComment || !commentMarker(NextLine).empty() ||
153 (!TrailingTrim.empty() && !TrailingTrim.startswith(
"//")))) {
154 using llvm::sys::unicode::columnWidthUTF8;
156 StringRef PreComment =
157 Leading.take_front(CommentMarker.data() - Leading.data());
158 std::string IndentAndComment =
159 (std::string(columnWidthUTF8(PreComment),
' ') + CommentMarker +
" ")
162 Result.Changes.add(replacement(Code, Indentation, IndentAndComment)));
166 cantFail(Result.Changes.add(replacement(Code, Indentation,
"")));
170 if (CommentMarker.empty() && Leading.endswith(
"{") &&
171 Trailing.startswith(
"}")) {
173 Result.Changes.add(replacement(Code, Trailing.take_front(1),
"\n}")));
175 Result.FormatRanges.push_back(
180 Result.FormatRanges.push_back(
187 Result.CursorPlaceholder = !CommentMarker.empty() ?
"ident" :
"//==\nident";
192 IncrementalChanges getIncrementalChanges(llvm::StringRef Code,
unsigned Cursor,
193 llvm::StringRef InsertedText) {
194 IncrementalChanges
Result;
195 if (InsertedText ==
"\n")
196 return getIncrementalChangesAfterNewline(Code, Cursor);
198 Result.CursorPlaceholder =
" /**/";
205 std::vector<tooling::Replacement>
206 split(
const tooling::Replacements &Replacements,
unsigned OldCursor,
207 unsigned NewCursor) {
208 std::vector<tooling::Replacement>
Result;
209 int LengthChange = 0;
210 for (
const tooling::Replacement &R : Replacements) {
211 if (R.getOffset() + R.getLength() <= OldCursor) {
213 LengthChange += R.getReplacementText().size() - R.getLength();
214 }
else if (R.getOffset() < OldCursor) {
215 int ReplacementSplit = NewCursor - LengthChange - R.getOffset();
216 assert(ReplacementSplit >= 0 &&
217 ReplacementSplit <=
int(R.getReplacementText().size()) &&
218 "NewCursor incompatible with OldCursor!");
219 Result.push_back(tooling::Replacement(
220 R.getFilePath(), R.getOffset(), OldCursor - R.getOffset(),
221 R.getReplacementText().take_front(ReplacementSplit)));
222 Result.push_back(tooling::Replacement(
223 R.getFilePath(), OldCursor,
224 R.getLength() - (OldCursor - R.getOffset()),
225 R.getReplacementText().drop_front(ReplacementSplit)));
226 }
else if (R.getOffset() >= OldCursor) {
245 std::vector<tooling::Replacement>
249 getIncrementalChanges(OriginalCode, OriginalCursor, InsertedText);
251 if (InsertedText ==
"\n") {
252 Style.MaxEmptyLinesToKeep = 1000;
253 Style.KeepEmptyLinesAtTheStartOfBlocks =
true;
258 std::string CodeToFormat = cantFail(
259 tooling::applyAllReplacements(OriginalCode, Incremental.Changes));
260 unsigned Cursor = Incremental.Changes.getShiftedCodePosition(OriginalCursor);
262 unsigned FormatLimit = Cursor;
264 FormatLimit = std::max(FormatLimit, R.getOffset() + R.getLength());
265 CodeToFormat.resize(FormatLimit);
267 CodeToFormat.insert(Cursor, Incremental.CursorPlaceholder);
269 closeBrackets(CodeToFormat, Style);
272 std::vector<tooling::Range> RangesToFormat = Incremental.FormatRanges;
274 for (
auto &R : RangesToFormat) {
275 if (R.getOffset() > Cursor)
276 R =
tooling::Range(R.getOffset() + Incremental.CursorPlaceholder.size(),
280 RangesToFormat.push_back(
283 FormatLimit += Incremental.CursorPlaceholder.size();
286 tooling::Replacements FormattingChanges;
287 format::FormattingAttemptStatus Status;
288 for (
const tooling::Replacement &R : format::reformat(
289 Style, CodeToFormat, RangesToFormat, Filename, &Status)) {
290 if (R.getOffset() + R.getLength() <= FormatLimit)
291 cantFail(FormattingChanges.add(R));
292 else if(R.getOffset() < FormatLimit) {
293 if (R.getReplacementText().empty())
294 cantFail(FormattingChanges.add(tooling::Replacement(Filename,
295 R.getOffset(), FormatLimit - R.getOffset(),
"")));
298 elog(
"Incremental clang-format edit overlapping cursor @ {0}!\n{1}",
299 Cursor, CodeToFormat);
302 if (!Status.FormatComplete)
303 vlog(
"Incremental format incomplete at line {0}", Status.Line);
308 tooling::Replacements InsertCursorPlaceholder(
309 tooling::Replacement(Filename, Cursor, 0, Incremental.CursorPlaceholder));
310 unsigned FormattedCursorStart =
311 FormattingChanges.getShiftedCodePosition(Cursor),
312 FormattedCursorEnd = FormattingChanges.getShiftedCodePosition(
313 Cursor + Incremental.CursorPlaceholder.size());
314 tooling::Replacements RemoveCursorPlaceholder(
315 tooling::Replacement(Filename, FormattedCursorStart,
316 FormattedCursorEnd - FormattedCursorStart,
""));
326 tooling::Replacements Final;
327 unsigned FinalCursor = OriginalCursor;
329 std::string FinalCode = OriginalCode;
330 dlog(
"Initial code: {0}", FinalCode);
333 std::vector<std::pair<const char *, const tooling::Replacements *>>{
334 {
"Pre-formatting changes", &Incremental.Changes},
335 {
"Insert placeholder", &InsertCursorPlaceholder},
336 {
"clang-format", &FormattingChanges},
337 {
"Remove placeholder", &RemoveCursorPlaceholder}}) {
338 Final = Final.merge(*Pass.second);
339 FinalCursor = Pass.second->getShiftedCodePosition(FinalCursor);
342 cantFail(tooling::applyAllReplacements(FinalCode, *Pass.second));
343 dlog(
"After {0}:\n{1}^{2}", Pass.first,
344 StringRef(FinalCode).take_front(FinalCursor),
345 StringRef(FinalCode).drop_front(FinalCursor));
348 return split(Final, OriginalCursor, FinalCursor);
353 const std::vector<tooling::Replacement> &Replacements) {
354 unsigned OriginalOffset =
Offset;
355 for (
const auto &R : Replacements) {
356 if (R.getOffset() + R.getLength() <= OriginalOffset) {
358 Offset += R.getReplacementText().size();
359 Offset -= R.getLength();
360 }
else if (R.getOffset() < OriginalOffset) {
363 unsigned PositionWithinReplacement = Offset - R.getOffset();
364 if (PositionWithinReplacement > R.getReplacementText().size()) {
365 Offset += R.getReplacementText().size();
366 Offset -= PositionWithinReplacement;
std::vector< tooling::Replacement > formatIncremental(llvm::StringRef OriginalCode, unsigned OriginalCursor, llvm::StringRef InsertedText, format::FormatStyle Style)
Applies limited formatting around new InsertedText.
Documents are synced by sending the full content on open.
unsigned transformCursorPosition(unsigned Offset, const std::vector< tooling::Replacement > &Replacements)
Determine the new cursor position after applying Replacements.
void vlog(const char *Fmt, Ts &&... Vals)
void elog(const char *Fmt, Ts &&... Vals)
std::string Filename
Filename as a string.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
CharSourceRange Range
SourceRange for the file name.
llvm::Optional< llvm::Expected< tooling::AtomicChanges > > Result
static cl::opt< std::string > FormatStyle("format-style", cl::desc(R"(
Style for formatting code around applied fixes:
- 'none' (default) turns off formatting
- 'file' (literally 'file', not a placeholder)
uses .clang-format file in the closest parent
directory
- '{ <json> }' specifies options inline, e.g.
-format-style='{BasedOnStyle: llvm, IndentWidth: 8}'
- 'llvm', 'google', 'webkit', 'mozilla'
See clang-format documentation for the up-to-date
information about formatting styles and options.
This option overrides the 'FormatStyle` option in
.clang-tidy file, if any.
)"), cl::init("none"), cl::cat(ClangTidyCategory))