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"
24void closeBrackets(std::string &
Code,
const format::FormatStyle &Style) {
25 SourceManagerForFile FileSM(
"mock_file.cpp",
Code);
26 auto &SM = FileSM.get();
27 FileID FID = SM.getMainFileID();
28 LangOptions LangOpts = format::getFormattingLangOpts(Style);
29 Lexer Lex(FID, SM.getBufferOrFake(FID), SM, LangOpts);
31 std::vector<char> Brackets;
32 while (!Lex.LexFromRawLexer(Tok)) {
33 switch(Tok.getKind()) {
35 Brackets.push_back(
')');
38 Brackets.push_back(
'}');
41 Brackets.push_back(
']');
44 if (!Brackets.empty() && Brackets.back() ==
')')
48 if (!Brackets.empty() && Brackets.back() ==
'}')
52 if (!Brackets.empty() && Brackets.back() ==
']')
60 Code.append(
"\n// */\n");
61 Code.append(Brackets.rbegin(), Brackets.rend());
64static StringRef commentMarker(llvm::StringRef Line) {
65 for (StringRef Marker : {
"///",
"//"}){
66 auto I =
Line.rfind(Marker);
67 if (I != StringRef::npos)
68 return Line.substr(I, Marker.size());
73llvm::StringRef firstLine(llvm::StringRef
Code) {
74 return Code.take_until([](
char C) {
return C ==
'\n'; });
77llvm::StringRef lastLine(llvm::StringRef
Code) {
78 llvm::StringRef Rest =
Code;
79 while (!Rest.empty() && Rest.back() !=
'\n')
80 Rest = Rest.drop_back();
81 return Code.substr(Rest.size());
89tooling::Replacement replacement(llvm::StringRef
Code, llvm::StringRef From,
91 assert(From.begin() >=
Code.begin() && From.end() <=
Code.end());
93 return tooling::Replacement(
Filename, From.data() -
Code.data(),
107struct IncrementalChanges {
124inline unsigned columnWidth(StringRef
Text) {
125 int ContentWidth = llvm::sys::unicode::columnWidthUTF8(
Text);
126 if (ContentWidth < 0)
133inline unsigned columnWidthWithTabs(StringRef
Text,
unsigned TabWidth) {
134 unsigned TotalWidth = 0;
137 StringRef::size_type TabPos =
Tail.find(
'\t');
138 if (TabPos == StringRef::npos)
139 return TotalWidth + columnWidth(
Tail);
140 TotalWidth += columnWidth(
Tail.substr(0, TabPos));
142 TotalWidth += TabWidth - TotalWidth % TabWidth;
151IncrementalChanges getIncrementalChangesAfterNewline(llvm::StringRef
Code,
154 IncrementalChanges Result;
161 StringRef Trailing = firstLine(
Code.substr(Cursor));
162 StringRef Indentation = lastLine(
Code.take_front(Cursor));
163 if (Indentation.data() ==
Code.data()) {
164 vlog(
"Typed a newline, but we're still on the first line!");
168 lastLine(
Code.take_front(Indentation.data() -
Code.data() - 1));
169 StringRef NextLine = firstLine(
Code.substr(Cursor + Trailing.size() + 1));
172 StringRef TrailingTrim = Trailing.ltrim();
173 if (
unsigned TrailWS = Trailing.size() - TrailingTrim.size())
174 cantFail(Result.Changes.add(
175 replacement(
Code, StringRef(Trailing.begin(), TrailWS),
"")));
179 StringRef CommentMarker = commentMarker(Leading);
180 bool NewLineIsComment = !commentMarker(Indentation).empty();
181 if (!CommentMarker.empty() &&
182 (NewLineIsComment || !commentMarker(NextLine).empty() ||
183 (!TrailingTrim.empty() && !TrailingTrim.starts_with(
"//")))) {
185 StringRef PreComment =
186 Leading.take_front(CommentMarker.data() - Leading.data());
187 std::string IndentAndComment =
188 (std::string(columnWidthWithTabs(PreComment, TabWidth),
' ') +
192 Result.Changes.add(replacement(
Code, Indentation, IndentAndComment)));
196 cantFail(Result.Changes.add(replacement(
Code, Indentation,
"")));
200 if (CommentMarker.empty() && Leading.ends_with(
"{") &&
201 Trailing.starts_with(
"}")) {
203 Result.Changes.add(replacement(
Code, Trailing.take_front(1),
"\n}")));
205 Result.FormatRanges.push_back(
206 tooling::Range(Trailing.data() -
Code.data() + 1, 1));
210 Result.FormatRanges.push_back(
211 tooling::Range(Leading.data() -
Code.data(), Leading.size()));
217 Result.CursorPlaceholder = !CommentMarker.empty() ?
"ident" :
"//==\nident";
222IncrementalChanges getIncrementalChanges(llvm::StringRef
Code,
unsigned Cursor,
223 llvm::StringRef InsertedText,
225 IncrementalChanges Result;
226 if (InsertedText ==
"\n")
227 return getIncrementalChangesAfterNewline(
Code, Cursor, TabWidth);
229 Result.CursorPlaceholder =
" /**/";
236std::vector<tooling::Replacement>
237split(
const tooling::Replacements &Replacements,
unsigned OldCursor,
238 unsigned NewCursor) {
239 std::vector<tooling::Replacement> Result;
240 int LengthChange = 0;
241 for (
const tooling::Replacement &R : Replacements) {
242 if (R.getOffset() + R.getLength() <= OldCursor) {
244 LengthChange += R.getReplacementText().size() - R.getLength();
245 }
else if (R.getOffset() < OldCursor) {
246 int ReplacementSplit = NewCursor - LengthChange - R.getOffset();
247 assert(ReplacementSplit >= 0 &&
248 ReplacementSplit <=
int(R.getReplacementText().size()) &&
249 "NewCursor incompatible with OldCursor!");
250 Result.push_back(tooling::Replacement(
251 R.getFilePath(), R.getOffset(), OldCursor - R.getOffset(),
252 R.getReplacementText().take_front(ReplacementSplit)));
253 Result.push_back(tooling::Replacement(
254 R.getFilePath(), OldCursor,
255 R.getLength() - (OldCursor - R.getOffset()),
256 R.getReplacementText().drop_front(ReplacementSplit)));
257 }
else if (R.getOffset() >= OldCursor) {
276std::vector<tooling::Replacement>
278 llvm::StringRef InsertedText, format::FormatStyle Style) {
279 IncrementalChanges
Incremental = getIncrementalChanges(
280 OriginalCode, OriginalCursor, InsertedText, Style.TabWidth);
282 if (InsertedText ==
"\n") {
283 Style.MaxEmptyLinesToKeep = 1000;
284 Style.KeepEmptyLines.AtStartOfBlock =
true;
289 std::string CodeToFormat = cantFail(
290 tooling::applyAllReplacements(OriginalCode,
Incremental.Changes));
291 unsigned Cursor =
Incremental.Changes.getShiftedCodePosition(OriginalCursor);
293 unsigned FormatLimit = Cursor;
295 FormatLimit = std::max(FormatLimit, R.getOffset() + R.getLength());
296 CodeToFormat.resize(FormatLimit);
298 CodeToFormat.insert(Cursor,
Incremental.CursorPlaceholder);
300 closeBrackets(CodeToFormat, Style);
303 std::vector<tooling::Range> RangesToFormat =
Incremental.FormatRanges;
305 for (
auto &R : RangesToFormat) {
306 if (R.getOffset() > Cursor)
307 R = tooling::Range(R.getOffset() +
Incremental.CursorPlaceholder.size(),
311 RangesToFormat.push_back(
312 tooling::Range(Cursor,
Incremental.CursorPlaceholder.size()));
314 FormatLimit +=
Incremental.CursorPlaceholder.size();
317 tooling::Replacements FormattingChanges;
318 format::FormattingAttemptStatus Status;
319 for (
const tooling::Replacement &R : format::reformat(
320 Style, CodeToFormat, RangesToFormat,
Filename, &Status)) {
321 if (R.getOffset() + R.getLength() <= FormatLimit)
322 cantFail(FormattingChanges.add(R));
323 else if(R.getOffset() < FormatLimit) {
324 if (R.getReplacementText().empty())
325 cantFail(FormattingChanges.add(tooling::Replacement(
Filename,
326 R.getOffset(), FormatLimit - R.getOffset(),
"")));
329 elog(
"Incremental clang-format edit overlapping cursor @ {0}!\n{1}",
330 Cursor, CodeToFormat);
333 if (!Status.FormatComplete)
334 vlog(
"Incremental format incomplete at line {0}", Status.Line);
339 tooling::Replacements InsertCursorPlaceholder(
341 unsigned FormattedCursorStart =
342 FormattingChanges.getShiftedCodePosition(Cursor),
343 FormattedCursorEnd = FormattingChanges.getShiftedCodePosition(
345 tooling::Replacements RemoveCursorPlaceholder(
346 tooling::Replacement(
Filename, FormattedCursorStart,
347 FormattedCursorEnd - FormattedCursorStart,
""));
357 tooling::Replacements Final;
358 unsigned FinalCursor = OriginalCursor;
360 std::string FinalCode = std::string(OriginalCode);
361 dlog(
"Initial code: {0}", FinalCode);
364 std::vector<std::pair<const char *, const tooling::Replacements *>>{
366 {
"Insert placeholder", &InsertCursorPlaceholder},
367 {
"clang-format", &FormattingChanges},
368 {
"Remove placeholder", &RemoveCursorPlaceholder}}) {
369 Final = Final.merge(*Pass.second);
370 FinalCursor = Pass.second->getShiftedCodePosition(FinalCursor);
373 cantFail(tooling::applyAllReplacements(FinalCode, *Pass.second));
374 dlog(
"After {0}:\n{1}^{2}", Pass.first,
375 StringRef(FinalCode).take_front(FinalCursor),
376 StringRef(FinalCode).drop_front(FinalCursor));
379 return split(Final, OriginalCursor, FinalCursor);
384 const std::vector<tooling::Replacement> &Replacements) {
385 unsigned OriginalOffset =
Offset;
386 for (
const auto &R : Replacements) {
387 if (R.getOffset() + R.getLength() <= OriginalOffset) {
389 Offset += R.getReplacementText().size();
391 }
else if (R.getOffset() < OriginalOffset) {
394 unsigned PositionWithinReplacement =
Offset - R.getOffset();
395 if (PositionWithinReplacement > R.getReplacementText().size()) {
396 Offset += R.getReplacementText().size();
397 Offset -= PositionWithinReplacement;
std::string Filename
Filename as a string.
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)
std::vector< tooling::Replacement > formatIncremental(llvm::StringRef OriginalCode, unsigned OriginalCursor, llvm::StringRef InsertedText, format::FormatStyle Style)
Applies limited formatting around new InsertedText.
@ Incremental
Documents are synced by sending the full content on open.
void elog(const char *Fmt, Ts &&... Vals)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//