3#include <llvm/ADT/StringExtras.h>
4#include <llvm/Support/Errc.h>
11template <
typename Callback>
12static bool iterateCodepoints(llvm::StringRef U8,
const Callback &CB) {
13 bool LoggedInvalid =
false;
16 for (
size_t I = 0; I < U8.size();) {
17 unsigned char C =
static_cast<unsigned char>(U8[I]);
18 if (LLVM_LIKELY(!(C & 0x80))) {
25 size_t UTF8Length = llvm::countl_one(C);
28 if (LLVM_UNLIKELY(UTF8Length < 2 || UTF8Length > 4)) {
30 elog(
"File has invalid UTF-8 near offset {0}: {1}", I, llvm::toHex(U8));
44 if (CB(UTF8Length, UTF8Length == 4 ? 2 : 1))
53 switch (lspEncoding()) {
58 iterateCodepoints(Code, [&](
int U8Len,
int U16Len) {
64 iterateCodepoints(Code, [&](
int U8Len,
int U16Len) {
70 llvm_unreachable(
"unsupported encoding");
79static size_t measureUnits(llvm::StringRef U8,
int Units,
OffsetEncoding Enc,
90 Valid = iterateCodepoints(U8, [&](
int U8Len,
int U16Len) {
99 Valid = iterateCodepoints(U8, [&](
int U8Len,
int U16Len) {
106 llvm_unreachable(
"unsupported encoding");
109 if (Result > U8.size()) {
117 bool AllowColumnsBeyondLineLength) {
119 return error(llvm::errc::invalid_argument,
120 "Line value can't be negative ({0})", P.
line);
122 return error(llvm::errc::invalid_argument,
123 "Character value can't be negative ({0})", P.
character);
124 size_t StartOfLine = 0;
125 for (
int I = 0; I != P.
line; ++I) {
126 size_t NextNL = Code.find(
'\n', StartOfLine);
127 if (NextNL == llvm::StringRef::npos)
128 return error(llvm::errc::invalid_argument,
129 "Line value is out of range ({0})", P.
line);
130 StartOfLine = NextNL + 1;
132 llvm::StringRef Line =
133 Code.substr(StartOfLine).take_until([](
char C) {
return C ==
'\n'; });
137 size_t ByteInLine = measureUnits(Line, P.
character, lspEncoding(), Valid);
138 if (!Valid && !AllowColumnsBeyondLineLength)
139 return error(llvm::errc::invalid_argument,
140 "{0} offset {1} is invalid for line {2}", lspEncoding(),
142 return StartOfLine + ByteInLine;
146 Offset = std::min(Code.size(), Offset);
147 llvm::StringRef Before = Code.substr(0, Offset);
148 int Lines = Before.count(
'\n');
149 size_t PrevNL = Before.rfind(
'\n');
150 size_t StartOfLine = (PrevNL == llvm::StringRef::npos) ? 0 : (PrevNL + 1);
175static void inferFinalNewline(llvm::Expected<size_t> &Err,
176 std::string &Contents,
const Position &Pos) {
179 if (!Contents.empty() && Contents.back() ==
'\n')
181 if (Pos.character != 0)
183 if (Pos.line != llvm::count(Contents,
'\n') + 1)
185 log(
"Editor sent invalid change coordinates, inferring newline at EOF");
186 Contents.push_back(
'\n');
187 consumeError(Err.takeError());
188 Err = Contents.size();
194 Contents = Change.
text;
195 return llvm::Error::success();
199 llvm::Expected<size_t> StartIndex =
positionToOffset(Contents, Start,
false);
200 inferFinalNewline(StartIndex, Contents, Start);
202 return StartIndex.takeError();
206 inferFinalNewline(EndIndex, Contents, End);
208 return EndIndex.takeError();
210 if (*EndIndex < *StartIndex)
211 return error(llvm::errc::invalid_argument,
212 "Range's end position ({0}) is before start position ({1})",
222 ssize_t ComputedRangeLength =
223 lspLength(Contents.substr(*StartIndex, *EndIndex - *StartIndex));
226 return error(llvm::errc::invalid_argument,
227 "Change's rangeLength ({0}) doesn't match the "
228 "computed range length ({1}).",
231 Contents.replace(*StartIndex, *EndIndex - *StartIndex, Change.
text);
233 return llvm::Error::success();
Whether current platform treats paths case insensitively.
llvm::unique_function< void(llvm::Expected< T >)> Callback
llvm::Error error(std::error_code EC, const char *Fmt, Ts &&...Vals)
llvm::Error applyChange(std::string &Contents, const TextDocumentContentChangeEvent &Change)
Apply an incremental update to a text document.
size_t lspLength(llvm::StringRef Code)
void elog(const char *Fmt, Ts &&...Vals)
Position offsetToPosition(llvm::StringRef Code, size_t Offset)
llvm::Expected< size_t > positionToOffset(llvm::StringRef Code, Position P, bool AllowColumnsBeyondLineLength=true)
void log(const char *Fmt, Ts &&...Vals)
int64_t line
Line position in a document (zero-based).
std::string text
The new text of the range/document.
std::optional< Range > range
The range of the document that changed.
std::optional< int > rangeLength
The length of the range that got replaced.