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