/src/keystone/llvm/lib/Support/SourceMgr.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //===- SourceMgr.cpp - Manager for Simple Source Buffers & Diagnostics ----===// |
2 | | // |
3 | | // The LLVM Compiler Infrastructure |
4 | | // |
5 | | // This file is distributed under the University of Illinois Open Source |
6 | | // License. See LICENSE.TXT for details. |
7 | | // |
8 | | //===----------------------------------------------------------------------===// |
9 | | // |
10 | | // This file implements the SourceMgr class. This class is used as a simple |
11 | | // substrate for diagnostics, #include handling, and other low level things for |
12 | | // simple parsers. |
13 | | // |
14 | | //===----------------------------------------------------------------------===// |
15 | | |
16 | | #include "llvm/Support/SourceMgr.h" |
17 | | #include "llvm/ADT/Twine.h" |
18 | | #include "llvm/Support/MemoryBuffer.h" |
19 | | #include "llvm/Support/Path.h" |
20 | | #include "llvm/Support/raw_ostream.h" |
21 | | using namespace llvm_ks; |
22 | | |
23 | | static const size_t TabStop = 8; |
24 | | |
25 | | namespace { |
26 | | struct LineNoCacheTy { |
27 | | unsigned LastQueryBufferID; |
28 | | const char *LastQuery; |
29 | | unsigned LineNoOfQuery; |
30 | | }; |
31 | | } |
32 | | |
33 | 2.80M | static LineNoCacheTy *getCache(void *Ptr) { |
34 | 2.80M | return (LineNoCacheTy*)Ptr; |
35 | 2.80M | } |
36 | | |
37 | | |
38 | 130k | SourceMgr::~SourceMgr() { |
39 | | // Delete the line # cache if allocated. |
40 | 130k | if (LineNoCacheTy *Cache = getCache(LineNoCache)) |
41 | 18.8k | delete Cache; |
42 | 130k | } |
43 | | |
44 | | unsigned SourceMgr::AddIncludeFile(const std::string &Filename, |
45 | | SMLoc IncludeLoc, |
46 | 42 | std::string &IncludedFile) { |
47 | 42 | IncludedFile = Filename; |
48 | 42 | ErrorOr<std::unique_ptr<MemoryBuffer>> NewBufOrErr = |
49 | 42 | MemoryBuffer::getFile(IncludedFile); |
50 | | |
51 | | // If the file didn't exist directly, see if it's in an include path. |
52 | 42 | for (unsigned i = 0, e = IncludeDirectories.size(); i != e && !NewBufOrErr; |
53 | 42 | ++i) { |
54 | 0 | IncludedFile = |
55 | 0 | IncludeDirectories[i] + sys::path::get_separator().data() + Filename; |
56 | 0 | NewBufOrErr = MemoryBuffer::getFile(IncludedFile); |
57 | 0 | } |
58 | | |
59 | 42 | if (!NewBufOrErr) |
60 | 42 | return 0; |
61 | | |
62 | 0 | return AddNewSourceBuffer(std::move(*NewBufOrErr), IncludeLoc); |
63 | 42 | } |
64 | | |
65 | 5.17M | unsigned SourceMgr::FindBufferContainingLoc(SMLoc Loc) const { |
66 | 784M | for (unsigned i = 0, e = Buffers.size(); i != e; ++i) |
67 | 783M | if (Loc.getPointer() >= Buffers[i].Buffer->getBufferStart() && |
68 | | // Use <= here so that a pointer to the null at the end of the buffer |
69 | | // is included as part of the buffer. |
70 | 783M | Loc.getPointer() <= Buffers[i].Buffer->getBufferEnd()) |
71 | 3.96M | return i + 1; |
72 | 1.20M | return 0; |
73 | 5.17M | } |
74 | | |
75 | | std::pair<unsigned, unsigned> |
76 | 1.33M | SourceMgr::getLineAndColumn(SMLoc Loc, unsigned BufferID) const { |
77 | 1.33M | if (!BufferID) |
78 | 0 | BufferID = FindBufferContainingLoc(Loc); |
79 | 1.33M | assert(BufferID && "Invalid Location!"); |
80 | | |
81 | 1.33M | const MemoryBuffer *Buff = getMemoryBuffer(BufferID); |
82 | | |
83 | | // Count the number of \n's between the start of the file and the specified |
84 | | // location. |
85 | 1.33M | unsigned LineNo = 1; |
86 | | |
87 | 1.33M | const char *BufStart = Buff->getBufferStart(); |
88 | 1.33M | const char *Ptr = BufStart; |
89 | | |
90 | | // If we have a line number cache, and if the query is to a later point in the |
91 | | // same file, start searching from the last query location. This optimizes |
92 | | // for the case when multiple diagnostics come out of one file in order. |
93 | 1.33M | if (LineNoCacheTy *Cache = getCache(LineNoCache)) |
94 | 1.31M | if (Cache->LastQueryBufferID == BufferID && |
95 | 1.31M | Cache->LastQuery <= Loc.getPointer()) { |
96 | 164k | Ptr = Cache->LastQuery; |
97 | 164k | LineNo = Cache->LineNoOfQuery; |
98 | 164k | } |
99 | | |
100 | | // Scan for the location being queried, keeping track of the number of lines |
101 | | // we see. |
102 | 5.12G | for (; SMLoc::getFromPointer(Ptr) != Loc; ++Ptr) |
103 | 5.12G | if (*Ptr == '\n') ++LineNo; |
104 | | |
105 | | // Allocate the line number cache if it doesn't exist. |
106 | 1.33M | if (!LineNoCache) |
107 | 18.8k | LineNoCache = new LineNoCacheTy(); |
108 | | |
109 | | // Update the line # cache. |
110 | 1.33M | LineNoCacheTy &Cache = *getCache(LineNoCache); |
111 | 1.33M | Cache.LastQueryBufferID = BufferID; |
112 | 1.33M | Cache.LastQuery = Ptr; |
113 | 1.33M | Cache.LineNoOfQuery = LineNo; |
114 | | |
115 | 1.33M | size_t NewlineOffs = StringRef(BufStart, Ptr-BufStart).find_last_of("\n\r"); |
116 | 1.33M | if (NewlineOffs == StringRef::npos) NewlineOffs = ~(size_t)0; |
117 | 1.33M | return std::make_pair(LineNo, Ptr-BufStart-NewlineOffs); |
118 | 1.33M | } |
119 | | |
120 | 953k | void SourceMgr::PrintIncludeStack(SMLoc IncludeLoc, raw_ostream &OS) const { |
121 | 953k | if (IncludeLoc == SMLoc()) return; // Top of stack. |
122 | | |
123 | 0 | unsigned CurBuf = FindBufferContainingLoc(IncludeLoc); |
124 | 0 | assert(CurBuf && "Invalid or unspecified location!"); |
125 | | |
126 | 0 | PrintIncludeStack(getBufferInfo(CurBuf).IncludeLoc, OS); |
127 | |
|
128 | 0 | OS << "Included from " |
129 | 0 | << getBufferInfo(CurBuf).Buffer->getBufferIdentifier() |
130 | 0 | << ":" << FindLineNumber(IncludeLoc, CurBuf) << ":\n"; |
131 | 0 | } |
132 | | |
133 | | |
134 | | SMDiagnostic SourceMgr::GetMessage(SMLoc Loc, SourceMgr::DiagKind Kind, |
135 | | const Twine &Msg, |
136 | | ArrayRef<SMRange> Ranges, |
137 | 1.29M | ArrayRef<SMFixIt> FixIts) const { |
138 | | |
139 | | // First thing to do: find the current buffer containing the specified |
140 | | // location to pull out the source line. |
141 | 1.29M | SmallVector<std::pair<unsigned, unsigned>, 4> ColRanges; |
142 | 1.29M | std::pair<unsigned, unsigned> LineAndCol; |
143 | 1.29M | const char *BufferID = "<unknown>"; |
144 | 1.29M | std::string LineStr; |
145 | | |
146 | 1.29M | if (Loc.isValid()) { |
147 | 1.28M | unsigned CurBuf = FindBufferContainingLoc(Loc); |
148 | 1.28M | assert(CurBuf && "Invalid or unspecified location!"); |
149 | | |
150 | 1.28M | const MemoryBuffer *CurMB = getMemoryBuffer(CurBuf); |
151 | 1.28M | BufferID = CurMB->getBufferIdentifier(); |
152 | | |
153 | | // Scan backward to find the start of the line. |
154 | 1.28M | const char *LineStart = Loc.getPointer(); |
155 | 1.28M | const char *BufStart = CurMB->getBufferStart(); |
156 | 53.1M | while (LineStart != BufStart && LineStart[-1] != '\n' && |
157 | 53.1M | LineStart[-1] != '\r') |
158 | 51.8M | --LineStart; |
159 | | |
160 | | // Get the end of the line. |
161 | 1.28M | const char *LineEnd = Loc.getPointer(); |
162 | 1.28M | const char *BufEnd = CurMB->getBufferEnd(); |
163 | 69.1M | while (LineEnd != BufEnd && LineEnd[0] != '\n' && LineEnd[0] != '\r') |
164 | 67.9M | ++LineEnd; |
165 | 1.28M | LineStr = std::string(LineStart, LineEnd); |
166 | | |
167 | | // Convert any ranges to column ranges that only intersect the line of the |
168 | | // location. |
169 | 1.28M | for (unsigned i = 0, e = Ranges.size(); i != e; ++i) { |
170 | 1.89k | SMRange R = Ranges[i]; |
171 | 1.89k | if (!R.isValid()) continue; |
172 | | |
173 | | // If the line doesn't contain any part of the range, then ignore it. |
174 | 1.89k | if (R.Start.getPointer() > LineEnd || R.End.getPointer() < LineStart) |
175 | 0 | continue; |
176 | | |
177 | | // Ignore pieces of the range that go onto other lines. |
178 | 1.89k | if (R.Start.getPointer() < LineStart) |
179 | 0 | R.Start = SMLoc::getFromPointer(LineStart); |
180 | 1.89k | if (R.End.getPointer() > LineEnd) |
181 | 66 | R.End = SMLoc::getFromPointer(LineEnd); |
182 | | |
183 | | // Translate from SMLoc ranges to column ranges. |
184 | | // FIXME: Handle multibyte characters. |
185 | 1.89k | ColRanges.push_back(std::make_pair(R.Start.getPointer()-LineStart, |
186 | 1.89k | R.End.getPointer()-LineStart)); |
187 | 1.89k | } |
188 | | |
189 | 1.28M | LineAndCol = getLineAndColumn(Loc, CurBuf); |
190 | 1.28M | } |
191 | | |
192 | 1.29M | return SMDiagnostic(*this, Loc, BufferID, LineAndCol.first, |
193 | 1.29M | LineAndCol.second-1, Kind, Msg.str(), |
194 | 1.29M | LineStr, ColRanges, FixIts); |
195 | 1.29M | } |
196 | | |
197 | | void SourceMgr::PrintMessage(raw_ostream &OS, const SMDiagnostic &Diagnostic, |
198 | 1.29M | bool ShowColors) const { |
199 | | // Report the message with the diagnostic handler if present. |
200 | 1.29M | if (DiagHandler) { |
201 | 1.29M | DiagHandler(Diagnostic, DiagContext); |
202 | 1.29M | return; |
203 | 1.29M | } |
204 | | |
205 | 0 | if (Diagnostic.getLoc().isValid()) { |
206 | 0 | unsigned CurBuf = FindBufferContainingLoc(Diagnostic.getLoc()); |
207 | 0 | assert(CurBuf && "Invalid or unspecified location!"); |
208 | 0 | PrintIncludeStack(getBufferInfo(CurBuf).IncludeLoc, OS); |
209 | 0 | } |
210 | | |
211 | 0 | Diagnostic.print(nullptr, OS, ShowColors); |
212 | 0 | } |
213 | | |
214 | | void SourceMgr::PrintMessage(raw_ostream &OS, SMLoc Loc, |
215 | | SourceMgr::DiagKind Kind, |
216 | | const Twine &Msg, ArrayRef<SMRange> Ranges, |
217 | 1.29M | ArrayRef<SMFixIt> FixIts, bool ShowColors) const { |
218 | 1.29M | PrintMessage(OS, GetMessage(Loc, Kind, Msg, Ranges, FixIts), ShowColors); |
219 | 1.29M | } |
220 | | |
221 | | void SourceMgr::PrintMessage(SMLoc Loc, SourceMgr::DiagKind Kind, |
222 | | const Twine &Msg, ArrayRef<SMRange> Ranges, |
223 | 1.29M | ArrayRef<SMFixIt> FixIts, bool ShowColors) const { |
224 | 1.29M | PrintMessage(llvm_ks::errs(), Loc, Kind, Msg, Ranges, FixIts, ShowColors); |
225 | 1.29M | } |
226 | | |
227 | | //===----------------------------------------------------------------------===// |
228 | | // SMDiagnostic Implementation |
229 | | //===----------------------------------------------------------------------===// |
230 | | |
231 | | SMDiagnostic::SMDiagnostic(const SourceMgr &sm, SMLoc L, StringRef FN, |
232 | | int Line, int Col, SourceMgr::DiagKind Kind, |
233 | | StringRef Msg, StringRef LineStr, |
234 | | ArrayRef<std::pair<unsigned,unsigned> > Ranges, |
235 | | ArrayRef<SMFixIt> Hints) |
236 | 1.32M | : SM(&sm), Loc(L), Filename(FN), LineNo(Line), ColumnNo(Col), Kind(Kind), |
237 | 1.32M | Message(Msg), LineContents(LineStr), Ranges(Ranges.vec()), |
238 | 1.32M | FixIts(Hints.begin(), Hints.end()) { |
239 | 1.32M | std::sort(FixIts.begin(), FixIts.end()); |
240 | 1.32M | } |
241 | | |
242 | | static void buildFixItLine(std::string &CaretLine, std::string &FixItLine, |
243 | 1.14M | ArrayRef<SMFixIt> FixIts, ArrayRef<char> SourceLine){ |
244 | 1.14M | if (FixIts.empty()) |
245 | 1.14M | return; |
246 | | |
247 | 1.28k | const char *LineStart = SourceLine.begin(); |
248 | 1.28k | const char *LineEnd = SourceLine.end(); |
249 | | |
250 | 1.28k | size_t PrevHintEndCol = 0; |
251 | | |
252 | 1.28k | for (ArrayRef<SMFixIt>::iterator I = FixIts.begin(), E = FixIts.end(); |
253 | 2.57k | I != E; ++I) { |
254 | | // If the fixit contains a newline or tab, ignore it. |
255 | 1.28k | if (I->getText().find_first_of("\n\r\t") != StringRef::npos) |
256 | 0 | continue; |
257 | | |
258 | 1.28k | SMRange R = I->getRange(); |
259 | | |
260 | | // If the line doesn't contain any part of the range, then ignore it. |
261 | 1.28k | if (R.Start.getPointer() > LineEnd || R.End.getPointer() < LineStart) |
262 | 0 | continue; |
263 | | |
264 | | // Translate from SMLoc to column. |
265 | | // Ignore pieces of the range that go onto other lines. |
266 | | // FIXME: Handle multibyte characters in the source line. |
267 | 1.28k | unsigned FirstCol; |
268 | 1.28k | if (R.Start.getPointer() < LineStart) |
269 | 0 | FirstCol = 0; |
270 | 1.28k | else |
271 | 1.28k | FirstCol = R.Start.getPointer() - LineStart; |
272 | | |
273 | | // If we inserted a long previous hint, push this one forwards, and add |
274 | | // an extra space to show that this is not part of the previous |
275 | | // completion. This is sort of the best we can do when two hints appear |
276 | | // to overlap. |
277 | | // |
278 | | // Note that if this hint is located immediately after the previous |
279 | | // hint, no space will be added, since the location is more important. |
280 | 1.28k | unsigned HintCol = FirstCol; |
281 | 1.28k | if (HintCol < PrevHintEndCol) |
282 | 0 | HintCol = PrevHintEndCol + 1; |
283 | | |
284 | | // This relies on one byte per column in our fixit hints. |
285 | 1.28k | unsigned LastColumnModified = HintCol + I->getText().size(); |
286 | 1.28k | if (LastColumnModified > FixItLine.size()) |
287 | 1.28k | FixItLine.resize(LastColumnModified, ' '); |
288 | | |
289 | 1.28k | std::copy(I->getText().begin(), I->getText().end(), |
290 | 1.28k | FixItLine.begin() + HintCol); |
291 | | |
292 | 1.28k | PrevHintEndCol = LastColumnModified; |
293 | | |
294 | | // For replacements, mark the removal range with '~'. |
295 | | // FIXME: Handle multibyte characters in the source line. |
296 | 1.28k | unsigned LastCol; |
297 | 1.28k | if (R.End.getPointer() >= LineEnd) |
298 | 104 | LastCol = LineEnd - LineStart; |
299 | 1.18k | else |
300 | 1.18k | LastCol = R.End.getPointer() - LineStart; |
301 | | |
302 | 1.28k | std::fill(&CaretLine[FirstCol], &CaretLine[LastCol], '~'); |
303 | 1.28k | } |
304 | 1.28k | } |
305 | | |
306 | 1.28M | static void printSourceLine(raw_ostream &S, StringRef LineContents) { |
307 | | // Print out the source line one character at a time, so we can expand tabs. |
308 | 120M | for (unsigned i = 0, e = LineContents.size(), OutCol = 0; i != e; ++i) { |
309 | 119M | if (LineContents[i] != '\t') { |
310 | 118M | S << LineContents[i]; |
311 | 118M | ++OutCol; |
312 | 118M | continue; |
313 | 118M | } |
314 | | |
315 | | // If we have a tab, emit at least one space, then round up to 8 columns. |
316 | 4.36M | do { |
317 | 4.36M | S << ' '; |
318 | 4.36M | ++OutCol; |
319 | 4.36M | } while ((OutCol % TabStop) != 0); |
320 | 792k | } |
321 | 1.28M | S << '\n'; |
322 | 1.28M | } |
323 | | |
324 | 96.5M | static bool isNonASCII(char c) { |
325 | 96.5M | return c & 0x80; |
326 | 96.5M | } |
327 | | |
328 | | void SMDiagnostic::print(const char *ProgName, raw_ostream &S, bool ShowColors, |
329 | 1.29M | bool ShowKindLabel) const { |
330 | | // Display colors only if OS supports colors. |
331 | 1.29M | ShowColors &= S.has_colors(); |
332 | | |
333 | 1.29M | if (ShowColors) |
334 | 1.29M | S.changeColor(raw_ostream::SAVEDCOLOR, true); |
335 | | |
336 | 1.29M | if (ProgName && ProgName[0]) |
337 | 0 | S << ProgName << ": "; |
338 | | |
339 | 1.29M | if (!Filename.empty()) { |
340 | 987k | if (Filename == "-") |
341 | 1.27k | S << "<stdin>"; |
342 | 986k | else |
343 | 986k | S << Filename; |
344 | | |
345 | 987k | if (LineNo != -1) { |
346 | 987k | S << ':' << LineNo; |
347 | 987k | if (ColumnNo != -1) |
348 | 973k | S << ':' << (ColumnNo+1); |
349 | 987k | } |
350 | 987k | S << ": "; |
351 | 987k | } |
352 | | |
353 | 1.29M | if (ShowKindLabel) { |
354 | 1.29M | switch (Kind) { |
355 | 190k | case SourceMgr::DK_Error: |
356 | 190k | if (ShowColors) |
357 | 190k | S.changeColor(raw_ostream::RED, true); |
358 | 190k | S << "error: "; |
359 | 190k | break; |
360 | 183k | case SourceMgr::DK_Warning: |
361 | 183k | if (ShowColors) |
362 | 183k | S.changeColor(raw_ostream::MAGENTA, true); |
363 | 183k | S << "warning: "; |
364 | 183k | break; |
365 | 922k | case SourceMgr::DK_Note: |
366 | 922k | if (ShowColors) |
367 | 922k | S.changeColor(raw_ostream::BLACK, true); |
368 | 922k | S << "note: "; |
369 | 922k | break; |
370 | 1.29M | } |
371 | | |
372 | 1.29M | if (ShowColors) { |
373 | 1.29M | S.resetColor(); |
374 | 1.29M | S.changeColor(raw_ostream::SAVEDCOLOR, true); |
375 | 1.29M | } |
376 | 1.29M | } |
377 | | |
378 | 1.29M | S << Message << '\n'; |
379 | | |
380 | 1.29M | if (ShowColors) |
381 | 1.29M | S.resetColor(); |
382 | | |
383 | 1.29M | if (LineNo == -1 || ColumnNo == -1) |
384 | 14.5k | return; |
385 | | |
386 | | // FIXME: If there are multibyte or multi-column characters in the source, all |
387 | | // our ranges will be wrong. To do this properly, we'll need a byte-to-column |
388 | | // map like Clang's TextDiagnostic. For now, we'll just handle tabs by |
389 | | // expanding them later, and bail out rather than show incorrect ranges and |
390 | | // misaligned fixits for any other odd characters. |
391 | 1.28M | if (std::find_if(LineContents.begin(), LineContents.end(), isNonASCII) != |
392 | 1.28M | LineContents.end()) { |
393 | 136k | printSourceLine(S, LineContents); |
394 | 136k | return; |
395 | 136k | } |
396 | 1.14M | size_t NumColumns = LineContents.size(); |
397 | | |
398 | | // Build the line with the caret and ranges. |
399 | 1.14M | std::string CaretLine(NumColumns+1, ' '); |
400 | | |
401 | | // Expand any ranges. |
402 | 1.14M | for (unsigned r = 0, e = Ranges.size(); r != e; ++r) { |
403 | 1.41k | std::pair<unsigned, unsigned> R = Ranges[r]; |
404 | 1.41k | std::fill(&CaretLine[R.first], |
405 | 1.41k | &CaretLine[std::min((size_t)R.second, CaretLine.size())], |
406 | 1.41k | '~'); |
407 | 1.41k | } |
408 | | |
409 | | // Add any fix-its. |
410 | | // FIXME: Find the beginning of the line properly for multibyte characters. |
411 | 1.14M | std::string FixItInsertionLine; |
412 | 1.14M | buildFixItLine(CaretLine, FixItInsertionLine, FixIts, |
413 | 1.14M | makeArrayRef(Loc.getPointer() - ColumnNo, |
414 | 1.14M | LineContents.size())); |
415 | | |
416 | | // Finally, plop on the caret. |
417 | 1.14M | if (unsigned(ColumnNo) <= NumColumns) |
418 | 1.14M | CaretLine[ColumnNo] = '^'; |
419 | 0 | else |
420 | 0 | CaretLine[NumColumns] = '^'; |
421 | | |
422 | | // ... and remove trailing whitespace so the output doesn't wrap for it. We |
423 | | // know that the line isn't completely empty because it has the caret in it at |
424 | | // least. |
425 | 1.14M | CaretLine.erase(CaretLine.find_last_not_of(' ')+1); |
426 | | |
427 | 1.14M | printSourceLine(S, LineContents); |
428 | | |
429 | 1.14M | if (ShowColors) |
430 | 1.14M | S.changeColor(raw_ostream::GREEN, true); |
431 | | |
432 | | // Print out the caret line, matching tabs in the source line. |
433 | 44.6M | for (unsigned i = 0, e = CaretLine.size(), OutCol = 0; i != e; ++i) { |
434 | 43.5M | if (i >= LineContents.size() || LineContents[i] != '\t') { |
435 | 43.2M | S << CaretLine[i]; |
436 | 43.2M | ++OutCol; |
437 | 43.2M | continue; |
438 | 43.2M | } |
439 | | |
440 | | // Okay, we have a tab. Insert the appropriate number of characters. |
441 | 1.86M | do { |
442 | 1.86M | S << CaretLine[i]; |
443 | 1.86M | ++OutCol; |
444 | 1.86M | } while ((OutCol % TabStop) != 0); |
445 | 311k | } |
446 | 1.14M | S << '\n'; |
447 | | |
448 | 1.14M | if (ShowColors) |
449 | 1.14M | S.resetColor(); |
450 | | |
451 | | // Print out the replacement line, matching tabs in the source line. |
452 | 1.14M | if (FixItInsertionLine.empty()) |
453 | 1.14M | return; |
454 | | |
455 | 254k | for (size_t i = 0, e = FixItInsertionLine.size(), OutCol = 0; i < e; ++i) { |
456 | 253k | if (i >= LineContents.size() || LineContents[i] != '\t') { |
457 | 252k | S << FixItInsertionLine[i]; |
458 | 252k | ++OutCol; |
459 | 252k | continue; |
460 | 252k | } |
461 | | |
462 | | // Okay, we have a tab. Insert the appropriate number of characters. |
463 | 3.05k | do { |
464 | 3.05k | S << FixItInsertionLine[i]; |
465 | | // FIXME: This is trying not to break up replacements, but then to re-sync |
466 | | // with the tabs between replacements. This will fail, though, if two |
467 | | // fix-it replacements are exactly adjacent, or if a fix-it contains a |
468 | | // space. Really we should be precomputing column widths, which we'll |
469 | | // need anyway for multibyte chars. |
470 | 3.05k | if (FixItInsertionLine[i] != ' ') |
471 | 56 | ++i; |
472 | 3.05k | ++OutCol; |
473 | 3.05k | } while (((OutCol % TabStop) != 0) && i != e); |
474 | 450 | } |
475 | 1.28k | S << '\n'; |
476 | 1.28k | } |