| 1 | //===- RewriteBuffer.h - Buffer rewriting interface -----------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "llvm/ADT/RewriteBuffer.h" |
| 10 | #include "llvm/Support/raw_ostream.h" |
| 11 | |
| 12 | using namespace llvm; |
| 13 | |
| 14 | raw_ostream &RewriteBuffer::write(raw_ostream &Stream) const { |
| 15 | // Walk RewriteRope chunks efficiently using MoveToNextPiece() instead of the |
| 16 | // character iterator. |
| 17 | for (RopePieceBTreeIterator I = begin(), E = end(); I != E; |
| 18 | I.MoveToNextPiece()) |
| 19 | Stream << I.piece(); |
| 20 | return Stream; |
| 21 | } |
| 22 | |
| 23 | /// Return true if this character is non-new-line whitespace: |
| 24 | /// ' ', '\\t', '\\f', '\\v', '\\r'. |
| 25 | static inline bool isWhitespaceExceptNL(unsigned char c) { |
| 26 | return c == ' ' || c == '\t' || c == '\f' || c == '\v' || c == '\r'; |
| 27 | } |
| 28 | |
| 29 | void RewriteBuffer::RemoveText(unsigned OrigOffset, unsigned Size, |
| 30 | bool removeLineIfEmpty) { |
| 31 | // Nothing to remove, exit early. |
| 32 | if (Size == 0) |
| 33 | return; |
| 34 | |
| 35 | unsigned RealOffset = getMappedOffset(OrigOffset, AfterInserts: true); |
| 36 | assert(RealOffset + Size <= Buffer.size() && "Invalid location" ); |
| 37 | |
| 38 | // Remove the dead characters. |
| 39 | Buffer.erase(Offset: RealOffset, NumBytes: Size); |
| 40 | |
| 41 | // Add a delta so that future changes are offset correctly. |
| 42 | AddReplaceDelta(OrigOffset, Change: -Size); |
| 43 | |
| 44 | if (removeLineIfEmpty) { |
| 45 | // Find the line that the remove occurred and if it is completely empty |
| 46 | // remove the line as well. |
| 47 | |
| 48 | iterator curLineStart = begin(); |
| 49 | unsigned curLineStartOffs = 0; |
| 50 | iterator posI = begin(); |
| 51 | for (unsigned i = 0; i != RealOffset; ++i) { |
| 52 | if (*posI == '\n') { |
| 53 | curLineStart = posI; |
| 54 | ++curLineStart; |
| 55 | curLineStartOffs = i + 1; |
| 56 | } |
| 57 | ++posI; |
| 58 | } |
| 59 | |
| 60 | unsigned lineSize = 0; |
| 61 | posI = curLineStart; |
| 62 | while (posI != end() && isWhitespaceExceptNL(c: *posI)) { |
| 63 | ++posI; |
| 64 | ++lineSize; |
| 65 | } |
| 66 | if (posI != end() && *posI == '\n') { |
| 67 | Buffer.erase(Offset: curLineStartOffs, NumBytes: lineSize + 1 /* + '\n'*/); |
| 68 | // FIXME: Here, the offset of the start of the line is supposed to be |
| 69 | // expressed in terms of the original input not the "real" rewrite |
| 70 | // buffer. How do we compute that reliably? It might be tempting to use |
| 71 | // curLineStartOffs + OrigOffset - RealOffset, but that assumes the |
| 72 | // difference between the original and real offset is the same at the |
| 73 | // removed text and at the start of the line, but that's not true if |
| 74 | // edits were previously made earlier on the line. This bug is also |
| 75 | // documented by a FIXME on the definition of |
| 76 | // clang::Rewriter::RewriteOptions::RemoveLineIfEmpty. A reproducer for |
| 77 | // the implementation below is the test RemoveLineIfEmpty in |
| 78 | // clang/unittests/Rewrite/RewriteBufferTest.cpp. |
| 79 | AddReplaceDelta(OrigOffset: curLineStartOffs, Change: -(lineSize + 1 /* + '\n'*/)); |
| 80 | } |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | void RewriteBuffer::InsertText(unsigned OrigOffset, StringRef Str, |
| 85 | bool InsertAfter) { |
| 86 | // Nothing to insert, exit early. |
| 87 | if (Str.empty()) |
| 88 | return; |
| 89 | |
| 90 | unsigned RealOffset = getMappedOffset(OrigOffset, AfterInserts: InsertAfter); |
| 91 | Buffer.insert(Offset: RealOffset, Start: Str.begin(), End: Str.end()); |
| 92 | |
| 93 | // Add a delta so that future changes are offset correctly. |
| 94 | AddInsertDelta(OrigOffset, Change: Str.size()); |
| 95 | } |
| 96 | |
| 97 | /// ReplaceText - This method replaces a range of characters in the input |
| 98 | /// buffer with a new string. This is effectively a combined "remove+insert" |
| 99 | /// operation. |
| 100 | void RewriteBuffer::ReplaceText(unsigned OrigOffset, unsigned OrigLength, |
| 101 | StringRef NewStr) { |
| 102 | unsigned RealOffset = getMappedOffset(OrigOffset, AfterInserts: true); |
| 103 | Buffer.erase(Offset: RealOffset, NumBytes: OrigLength); |
| 104 | Buffer.insert(Offset: RealOffset, Start: NewStr.begin(), End: NewStr.end()); |
| 105 | if (OrigLength != NewStr.size()) |
| 106 | AddReplaceDelta(OrigOffset, Change: NewStr.size() - OrigLength); |
| 107 | } |
| 108 | |