2017-12-19 20:23:48 +08:00
|
|
|
//===--- SourceCode.h - Manipulating source code as strings -----*- C++ -*-===//
|
|
|
|
//
|
|
|
|
// The LLVM Compiler Infrastructure
|
|
|
|
//
|
|
|
|
// This file is distributed under the University of Illinois Open Source
|
|
|
|
// License. See LICENSE.TXT for details.
|
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "SourceCode.h"
|
|
|
|
|
2018-07-06 03:35:01 +08:00
|
|
|
#include "Logger.h"
|
|
|
|
#include "clang/AST/ASTContext.h"
|
2018-02-21 10:39:08 +08:00
|
|
|
#include "clang/Basic/SourceManager.h"
|
2018-07-06 03:35:01 +08:00
|
|
|
#include "clang/Lex/Lexer.h"
|
Make positionToOffset return llvm::Expected<size_t>
Summary:
To implement incremental document syncing, we want to verify that the
ranges provided by the front-end are valid. Currently, positionToOffset
deals with invalid Positions by returning 0 or Code.size(), which are
two valid offsets. Instead, return an llvm:Expected<size_t> with an
error if the position is invalid.
According to the LSP, if the character value exceeds the number of
characters of the given line, it should default back to the end of the
line. It makes sense in some contexts to have this behavior, and does
not in other contexts. The AllowColumnsBeyondLineLength parameter
allows to decide what to do in that case, default back to the end of the
line, or return an error.
Reviewers: ilya-biryukov
Subscribers: klimek, ilya-biryukov, jkorous-apple, ioeric, cfe-commits
Differential Revision: https://reviews.llvm.org/D44673
llvm-svn: 328100
2018-03-21 22:36:46 +08:00
|
|
|
#include "llvm/Support/Errc.h"
|
|
|
|
#include "llvm/Support/Error.h"
|
2018-07-06 03:35:01 +08:00
|
|
|
#include "llvm/Support/Path.h"
|
2018-02-21 10:39:08 +08:00
|
|
|
|
2017-12-19 20:23:48 +08:00
|
|
|
namespace clang {
|
|
|
|
namespace clangd {
|
|
|
|
using namespace llvm;
|
|
|
|
|
[clangd] Fix unicode handling, using UTF-16 where LSP requires it.
Summary:
The Language Server Protocol unfortunately mandates that locations in files
be represented by line/column pairs, where the "column" is actually an index
into the UTF-16-encoded text of the line.
(This is because VSCode is written in JavaScript, which is UTF-16-native).
Internally clangd treats source files at UTF-8, the One True Encoding, and
generally deals with byte offsets (though there are exceptions).
Before this patch, conversions between offsets and LSP Position pretended
that Position.character was UTF-8 bytes, which is only true for ASCII lines.
Now we examine the text to convert correctly (but don't actually need to
transcode it, due to some nice details of the encodings).
The updated functions in SourceCode are the blessed way to interact with
the Position.character field, and anything else is likely to be wrong.
So I also updated the other accesses:
- CodeComplete needs a "clang-style" line/column, with column in utf-8 bytes.
This is now converted via Position -> offset -> clang line/column
(a new function is added to SourceCode.h for the second conversion).
- getBeginningOfIdentifier skipped backwards in UTF-16 space, which is will
behave badly when it splits a surrogate pair. Skipping backwards in UTF-8
coordinates gives the lexer a fighting chance of getting this right.
While here, I clarified(?) the logic comments, fixed a bug with identifiers
containing digits, simplified the signature slightly and added a test.
This seems likely to cause problems with editors that have the same bug, and
treat the protocol as if columns are UTF-8 bytes. But we can find and fix those.
Reviewers: hokein
Subscribers: klimek, ilya-biryukov, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D46035
llvm-svn: 331029
2018-04-27 19:59:28 +08:00
|
|
|
// Here be dragons. LSP positions use columns measured in *UTF-16 code units*!
|
|
|
|
// Clangd uses UTF-8 and byte-offsets internally, so conversion is nontrivial.
|
|
|
|
|
|
|
|
// Iterates over unicode codepoints in the (UTF-8) string. For each,
|
|
|
|
// invokes CB(UTF-8 length, UTF-16 length), and breaks if it returns true.
|
|
|
|
// Returns true if CB returned true, false if we hit the end of string.
|
|
|
|
template <typename Callback>
|
|
|
|
static bool iterateCodepoints(StringRef U8, const Callback &CB) {
|
|
|
|
for (size_t I = 0; I < U8.size();) {
|
|
|
|
unsigned char C = static_cast<unsigned char>(U8[I]);
|
|
|
|
if (LLVM_LIKELY(!(C & 0x80))) { // ASCII character.
|
|
|
|
if (CB(1, 1))
|
|
|
|
return true;
|
|
|
|
++I;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// This convenient property of UTF-8 holds for all non-ASCII characters.
|
|
|
|
size_t UTF8Length = countLeadingOnes(C);
|
|
|
|
// 0xxx is ASCII, handled above. 10xxx is a trailing byte, invalid here.
|
|
|
|
// 11111xxx is not valid UTF-8 at all. Assert because it's probably our bug.
|
|
|
|
assert((UTF8Length >= 2 && UTF8Length <= 4) &&
|
|
|
|
"Invalid UTF-8, or transcoding bug?");
|
|
|
|
I += UTF8Length; // Skip over all trailing bytes.
|
|
|
|
// A codepoint takes two UTF-16 code unit if it's astral (outside BMP).
|
|
|
|
// Astral codepoints are encoded as 4 bytes in UTF-8 (11110xxx ...)
|
|
|
|
if (CB(UTF8Length, UTF8Length == 4 ? 2 : 1))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns the offset into the string that matches \p Units UTF-16 code units.
|
|
|
|
// Conceptually, this converts to UTF-16, truncates to CodeUnits, converts back
|
|
|
|
// to UTF-8, and returns the length in bytes.
|
|
|
|
static size_t measureUTF16(StringRef U8, int U16Units, bool &Valid) {
|
|
|
|
size_t Result = 0;
|
|
|
|
Valid = U16Units == 0 || iterateCodepoints(U8, [&](int U8Len, int U16Len) {
|
|
|
|
Result += U8Len;
|
|
|
|
U16Units -= U16Len;
|
|
|
|
return U16Units <= 0;
|
|
|
|
});
|
|
|
|
if (U16Units < 0) // Offset was into the middle of a surrogate pair.
|
|
|
|
Valid = false;
|
|
|
|
// Don't return an out-of-range index if we overran.
|
|
|
|
return std::min(Result, U8.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Counts the number of UTF-16 code units needed to represent a string.
|
|
|
|
// Like most strings in clangd, the input is UTF-8 encoded.
|
|
|
|
static size_t utf16Len(StringRef U8) {
|
|
|
|
// A codepoint takes two UTF-16 code unit if it's astral (outside BMP).
|
|
|
|
// Astral codepoints are encoded as 4 bytes in UTF-8, starting with 11110xxx.
|
|
|
|
size_t Count = 0;
|
|
|
|
iterateCodepoints(U8, [&](int U8Len, int U16Len) {
|
|
|
|
Count += U16Len;
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
return Count;
|
|
|
|
}
|
|
|
|
|
Make positionToOffset return llvm::Expected<size_t>
Summary:
To implement incremental document syncing, we want to verify that the
ranges provided by the front-end are valid. Currently, positionToOffset
deals with invalid Positions by returning 0 or Code.size(), which are
two valid offsets. Instead, return an llvm:Expected<size_t> with an
error if the position is invalid.
According to the LSP, if the character value exceeds the number of
characters of the given line, it should default back to the end of the
line. It makes sense in some contexts to have this behavior, and does
not in other contexts. The AllowColumnsBeyondLineLength parameter
allows to decide what to do in that case, default back to the end of the
line, or return an error.
Reviewers: ilya-biryukov
Subscribers: klimek, ilya-biryukov, jkorous-apple, ioeric, cfe-commits
Differential Revision: https://reviews.llvm.org/D44673
llvm-svn: 328100
2018-03-21 22:36:46 +08:00
|
|
|
llvm::Expected<size_t> positionToOffset(StringRef Code, Position P,
|
|
|
|
bool AllowColumnsBeyondLineLength) {
|
2017-12-19 20:23:48 +08:00
|
|
|
if (P.line < 0)
|
Make positionToOffset return llvm::Expected<size_t>
Summary:
To implement incremental document syncing, we want to verify that the
ranges provided by the front-end are valid. Currently, positionToOffset
deals with invalid Positions by returning 0 or Code.size(), which are
two valid offsets. Instead, return an llvm:Expected<size_t> with an
error if the position is invalid.
According to the LSP, if the character value exceeds the number of
characters of the given line, it should default back to the end of the
line. It makes sense in some contexts to have this behavior, and does
not in other contexts. The AllowColumnsBeyondLineLength parameter
allows to decide what to do in that case, default back to the end of the
line, or return an error.
Reviewers: ilya-biryukov
Subscribers: klimek, ilya-biryukov, jkorous-apple, ioeric, cfe-commits
Differential Revision: https://reviews.llvm.org/D44673
llvm-svn: 328100
2018-03-21 22:36:46 +08:00
|
|
|
return llvm::make_error<llvm::StringError>(
|
|
|
|
llvm::formatv("Line value can't be negative ({0})", P.line),
|
|
|
|
llvm::errc::invalid_argument);
|
|
|
|
if (P.character < 0)
|
|
|
|
return llvm::make_error<llvm::StringError>(
|
|
|
|
llvm::formatv("Character value can't be negative ({0})", P.character),
|
|
|
|
llvm::errc::invalid_argument);
|
2017-12-19 20:23:48 +08:00
|
|
|
size_t StartOfLine = 0;
|
|
|
|
for (int I = 0; I != P.line; ++I) {
|
|
|
|
size_t NextNL = Code.find('\n', StartOfLine);
|
|
|
|
if (NextNL == StringRef::npos)
|
Make positionToOffset return llvm::Expected<size_t>
Summary:
To implement incremental document syncing, we want to verify that the
ranges provided by the front-end are valid. Currently, positionToOffset
deals with invalid Positions by returning 0 or Code.size(), which are
two valid offsets. Instead, return an llvm:Expected<size_t> with an
error if the position is invalid.
According to the LSP, if the character value exceeds the number of
characters of the given line, it should default back to the end of the
line. It makes sense in some contexts to have this behavior, and does
not in other contexts. The AllowColumnsBeyondLineLength parameter
allows to decide what to do in that case, default back to the end of the
line, or return an error.
Reviewers: ilya-biryukov
Subscribers: klimek, ilya-biryukov, jkorous-apple, ioeric, cfe-commits
Differential Revision: https://reviews.llvm.org/D44673
llvm-svn: 328100
2018-03-21 22:36:46 +08:00
|
|
|
return llvm::make_error<llvm::StringError>(
|
|
|
|
llvm::formatv("Line value is out of range ({0})", P.line),
|
|
|
|
llvm::errc::invalid_argument);
|
2017-12-19 20:23:48 +08:00
|
|
|
StartOfLine = NextNL + 1;
|
|
|
|
}
|
Make positionToOffset return llvm::Expected<size_t>
Summary:
To implement incremental document syncing, we want to verify that the
ranges provided by the front-end are valid. Currently, positionToOffset
deals with invalid Positions by returning 0 or Code.size(), which are
two valid offsets. Instead, return an llvm:Expected<size_t> with an
error if the position is invalid.
According to the LSP, if the character value exceeds the number of
characters of the given line, it should default back to the end of the
line. It makes sense in some contexts to have this behavior, and does
not in other contexts. The AllowColumnsBeyondLineLength parameter
allows to decide what to do in that case, default back to the end of the
line, or return an error.
Reviewers: ilya-biryukov
Subscribers: klimek, ilya-biryukov, jkorous-apple, ioeric, cfe-commits
Differential Revision: https://reviews.llvm.org/D44673
llvm-svn: 328100
2018-03-21 22:36:46 +08:00
|
|
|
|
|
|
|
size_t NextNL = Code.find('\n', StartOfLine);
|
|
|
|
if (NextNL == StringRef::npos)
|
|
|
|
NextNL = Code.size();
|
|
|
|
|
[clangd] Fix unicode handling, using UTF-16 where LSP requires it.
Summary:
The Language Server Protocol unfortunately mandates that locations in files
be represented by line/column pairs, where the "column" is actually an index
into the UTF-16-encoded text of the line.
(This is because VSCode is written in JavaScript, which is UTF-16-native).
Internally clangd treats source files at UTF-8, the One True Encoding, and
generally deals with byte offsets (though there are exceptions).
Before this patch, conversions between offsets and LSP Position pretended
that Position.character was UTF-8 bytes, which is only true for ASCII lines.
Now we examine the text to convert correctly (but don't actually need to
transcode it, due to some nice details of the encodings).
The updated functions in SourceCode are the blessed way to interact with
the Position.character field, and anything else is likely to be wrong.
So I also updated the other accesses:
- CodeComplete needs a "clang-style" line/column, with column in utf-8 bytes.
This is now converted via Position -> offset -> clang line/column
(a new function is added to SourceCode.h for the second conversion).
- getBeginningOfIdentifier skipped backwards in UTF-16 space, which is will
behave badly when it splits a surrogate pair. Skipping backwards in UTF-8
coordinates gives the lexer a fighting chance of getting this right.
While here, I clarified(?) the logic comments, fixed a bug with identifiers
containing digits, simplified the signature slightly and added a test.
This seems likely to cause problems with editors that have the same bug, and
treat the protocol as if columns are UTF-8 bytes. But we can find and fix those.
Reviewers: hokein
Subscribers: klimek, ilya-biryukov, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D46035
llvm-svn: 331029
2018-04-27 19:59:28 +08:00
|
|
|
bool Valid;
|
|
|
|
size_t ByteOffsetInLine = measureUTF16(
|
|
|
|
Code.substr(StartOfLine, NextNL - StartOfLine), P.character, Valid);
|
|
|
|
if (!Valid && !AllowColumnsBeyondLineLength)
|
Make positionToOffset return llvm::Expected<size_t>
Summary:
To implement incremental document syncing, we want to verify that the
ranges provided by the front-end are valid. Currently, positionToOffset
deals with invalid Positions by returning 0 or Code.size(), which are
two valid offsets. Instead, return an llvm:Expected<size_t> with an
error if the position is invalid.
According to the LSP, if the character value exceeds the number of
characters of the given line, it should default back to the end of the
line. It makes sense in some contexts to have this behavior, and does
not in other contexts. The AllowColumnsBeyondLineLength parameter
allows to decide what to do in that case, default back to the end of the
line, or return an error.
Reviewers: ilya-biryukov
Subscribers: klimek, ilya-biryukov, jkorous-apple, ioeric, cfe-commits
Differential Revision: https://reviews.llvm.org/D44673
llvm-svn: 328100
2018-03-21 22:36:46 +08:00
|
|
|
return llvm::make_error<llvm::StringError>(
|
[clangd] Fix unicode handling, using UTF-16 where LSP requires it.
Summary:
The Language Server Protocol unfortunately mandates that locations in files
be represented by line/column pairs, where the "column" is actually an index
into the UTF-16-encoded text of the line.
(This is because VSCode is written in JavaScript, which is UTF-16-native).
Internally clangd treats source files at UTF-8, the One True Encoding, and
generally deals with byte offsets (though there are exceptions).
Before this patch, conversions between offsets and LSP Position pretended
that Position.character was UTF-8 bytes, which is only true for ASCII lines.
Now we examine the text to convert correctly (but don't actually need to
transcode it, due to some nice details of the encodings).
The updated functions in SourceCode are the blessed way to interact with
the Position.character field, and anything else is likely to be wrong.
So I also updated the other accesses:
- CodeComplete needs a "clang-style" line/column, with column in utf-8 bytes.
This is now converted via Position -> offset -> clang line/column
(a new function is added to SourceCode.h for the second conversion).
- getBeginningOfIdentifier skipped backwards in UTF-16 space, which is will
behave badly when it splits a surrogate pair. Skipping backwards in UTF-8
coordinates gives the lexer a fighting chance of getting this right.
While here, I clarified(?) the logic comments, fixed a bug with identifiers
containing digits, simplified the signature slightly and added a test.
This seems likely to cause problems with editors that have the same bug, and
treat the protocol as if columns are UTF-8 bytes. But we can find and fix those.
Reviewers: hokein
Subscribers: klimek, ilya-biryukov, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D46035
llvm-svn: 331029
2018-04-27 19:59:28 +08:00
|
|
|
llvm::formatv("UTF-16 offset {0} is invalid for line {1}", P.character,
|
|
|
|
P.line),
|
Make positionToOffset return llvm::Expected<size_t>
Summary:
To implement incremental document syncing, we want to verify that the
ranges provided by the front-end are valid. Currently, positionToOffset
deals with invalid Positions by returning 0 or Code.size(), which are
two valid offsets. Instead, return an llvm:Expected<size_t> with an
error if the position is invalid.
According to the LSP, if the character value exceeds the number of
characters of the given line, it should default back to the end of the
line. It makes sense in some contexts to have this behavior, and does
not in other contexts. The AllowColumnsBeyondLineLength parameter
allows to decide what to do in that case, default back to the end of the
line, or return an error.
Reviewers: ilya-biryukov
Subscribers: klimek, ilya-biryukov, jkorous-apple, ioeric, cfe-commits
Differential Revision: https://reviews.llvm.org/D44673
llvm-svn: 328100
2018-03-21 22:36:46 +08:00
|
|
|
llvm::errc::invalid_argument);
|
[clangd] Fix unicode handling, using UTF-16 where LSP requires it.
Summary:
The Language Server Protocol unfortunately mandates that locations in files
be represented by line/column pairs, where the "column" is actually an index
into the UTF-16-encoded text of the line.
(This is because VSCode is written in JavaScript, which is UTF-16-native).
Internally clangd treats source files at UTF-8, the One True Encoding, and
generally deals with byte offsets (though there are exceptions).
Before this patch, conversions between offsets and LSP Position pretended
that Position.character was UTF-8 bytes, which is only true for ASCII lines.
Now we examine the text to convert correctly (but don't actually need to
transcode it, due to some nice details of the encodings).
The updated functions in SourceCode are the blessed way to interact with
the Position.character field, and anything else is likely to be wrong.
So I also updated the other accesses:
- CodeComplete needs a "clang-style" line/column, with column in utf-8 bytes.
This is now converted via Position -> offset -> clang line/column
(a new function is added to SourceCode.h for the second conversion).
- getBeginningOfIdentifier skipped backwards in UTF-16 space, which is will
behave badly when it splits a surrogate pair. Skipping backwards in UTF-8
coordinates gives the lexer a fighting chance of getting this right.
While here, I clarified(?) the logic comments, fixed a bug with identifiers
containing digits, simplified the signature slightly and added a test.
This seems likely to cause problems with editors that have the same bug, and
treat the protocol as if columns are UTF-8 bytes. But we can find and fix those.
Reviewers: hokein
Subscribers: klimek, ilya-biryukov, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D46035
llvm-svn: 331029
2018-04-27 19:59:28 +08:00
|
|
|
return StartOfLine + ByteOffsetInLine;
|
2017-12-19 20:23:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
Position offsetToPosition(StringRef Code, size_t Offset) {
|
|
|
|
Offset = std::min(Code.size(), Offset);
|
|
|
|
StringRef Before = Code.substr(0, Offset);
|
|
|
|
int Lines = Before.count('\n');
|
|
|
|
size_t PrevNL = Before.rfind('\n');
|
|
|
|
size_t StartOfLine = (PrevNL == StringRef::npos) ? 0 : (PrevNL + 1);
|
2018-02-14 18:52:04 +08:00
|
|
|
Position Pos;
|
|
|
|
Pos.line = Lines;
|
[clangd] Fix unicode handling, using UTF-16 where LSP requires it.
Summary:
The Language Server Protocol unfortunately mandates that locations in files
be represented by line/column pairs, where the "column" is actually an index
into the UTF-16-encoded text of the line.
(This is because VSCode is written in JavaScript, which is UTF-16-native).
Internally clangd treats source files at UTF-8, the One True Encoding, and
generally deals with byte offsets (though there are exceptions).
Before this patch, conversions between offsets and LSP Position pretended
that Position.character was UTF-8 bytes, which is only true for ASCII lines.
Now we examine the text to convert correctly (but don't actually need to
transcode it, due to some nice details of the encodings).
The updated functions in SourceCode are the blessed way to interact with
the Position.character field, and anything else is likely to be wrong.
So I also updated the other accesses:
- CodeComplete needs a "clang-style" line/column, with column in utf-8 bytes.
This is now converted via Position -> offset -> clang line/column
(a new function is added to SourceCode.h for the second conversion).
- getBeginningOfIdentifier skipped backwards in UTF-16 space, which is will
behave badly when it splits a surrogate pair. Skipping backwards in UTF-8
coordinates gives the lexer a fighting chance of getting this right.
While here, I clarified(?) the logic comments, fixed a bug with identifiers
containing digits, simplified the signature slightly and added a test.
This seems likely to cause problems with editors that have the same bug, and
treat the protocol as if columns are UTF-8 bytes. But we can find and fix those.
Reviewers: hokein
Subscribers: klimek, ilya-biryukov, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D46035
llvm-svn: 331029
2018-04-27 19:59:28 +08:00
|
|
|
Pos.character = utf16Len(Before.substr(StartOfLine));
|
2018-02-14 18:52:04 +08:00
|
|
|
return Pos;
|
2017-12-19 20:23:48 +08:00
|
|
|
}
|
|
|
|
|
2018-02-21 10:39:08 +08:00
|
|
|
Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc) {
|
[clangd] Fix unicode handling, using UTF-16 where LSP requires it.
Summary:
The Language Server Protocol unfortunately mandates that locations in files
be represented by line/column pairs, where the "column" is actually an index
into the UTF-16-encoded text of the line.
(This is because VSCode is written in JavaScript, which is UTF-16-native).
Internally clangd treats source files at UTF-8, the One True Encoding, and
generally deals with byte offsets (though there are exceptions).
Before this patch, conversions between offsets and LSP Position pretended
that Position.character was UTF-8 bytes, which is only true for ASCII lines.
Now we examine the text to convert correctly (but don't actually need to
transcode it, due to some nice details of the encodings).
The updated functions in SourceCode are the blessed way to interact with
the Position.character field, and anything else is likely to be wrong.
So I also updated the other accesses:
- CodeComplete needs a "clang-style" line/column, with column in utf-8 bytes.
This is now converted via Position -> offset -> clang line/column
(a new function is added to SourceCode.h for the second conversion).
- getBeginningOfIdentifier skipped backwards in UTF-16 space, which is will
behave badly when it splits a surrogate pair. Skipping backwards in UTF-8
coordinates gives the lexer a fighting chance of getting this right.
While here, I clarified(?) the logic comments, fixed a bug with identifiers
containing digits, simplified the signature slightly and added a test.
This seems likely to cause problems with editors that have the same bug, and
treat the protocol as if columns are UTF-8 bytes. But we can find and fix those.
Reviewers: hokein
Subscribers: klimek, ilya-biryukov, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D46035
llvm-svn: 331029
2018-04-27 19:59:28 +08:00
|
|
|
// We use the SourceManager's line tables, but its column number is in bytes.
|
|
|
|
FileID FID;
|
|
|
|
unsigned Offset;
|
|
|
|
std::tie(FID, Offset) = SM.getDecomposedSpellingLoc(Loc);
|
2018-02-21 10:39:08 +08:00
|
|
|
Position P;
|
[clangd] Fix unicode handling, using UTF-16 where LSP requires it.
Summary:
The Language Server Protocol unfortunately mandates that locations in files
be represented by line/column pairs, where the "column" is actually an index
into the UTF-16-encoded text of the line.
(This is because VSCode is written in JavaScript, which is UTF-16-native).
Internally clangd treats source files at UTF-8, the One True Encoding, and
generally deals with byte offsets (though there are exceptions).
Before this patch, conversions between offsets and LSP Position pretended
that Position.character was UTF-8 bytes, which is only true for ASCII lines.
Now we examine the text to convert correctly (but don't actually need to
transcode it, due to some nice details of the encodings).
The updated functions in SourceCode are the blessed way to interact with
the Position.character field, and anything else is likely to be wrong.
So I also updated the other accesses:
- CodeComplete needs a "clang-style" line/column, with column in utf-8 bytes.
This is now converted via Position -> offset -> clang line/column
(a new function is added to SourceCode.h for the second conversion).
- getBeginningOfIdentifier skipped backwards in UTF-16 space, which is will
behave badly when it splits a surrogate pair. Skipping backwards in UTF-8
coordinates gives the lexer a fighting chance of getting this right.
While here, I clarified(?) the logic comments, fixed a bug with identifiers
containing digits, simplified the signature slightly and added a test.
This seems likely to cause problems with editors that have the same bug, and
treat the protocol as if columns are UTF-8 bytes. But we can find and fix those.
Reviewers: hokein
Subscribers: klimek, ilya-biryukov, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D46035
llvm-svn: 331029
2018-04-27 19:59:28 +08:00
|
|
|
P.line = static_cast<int>(SM.getLineNumber(FID, Offset)) - 1;
|
|
|
|
bool Invalid = false;
|
|
|
|
StringRef Code = SM.getBufferData(FID, &Invalid);
|
|
|
|
if (!Invalid) {
|
|
|
|
auto ColumnInBytes = SM.getColumnNumber(FID, Offset) - 1;
|
|
|
|
auto LineSoFar = Code.substr(Offset - ColumnInBytes, ColumnInBytes);
|
|
|
|
P.character = utf16Len(LineSoFar);
|
|
|
|
}
|
2018-02-21 10:39:08 +08:00
|
|
|
return P;
|
|
|
|
}
|
|
|
|
|
2018-03-12 23:28:22 +08:00
|
|
|
Range halfOpenToRange(const SourceManager &SM, CharSourceRange R) {
|
|
|
|
// Clang is 1-based, LSP uses 0-based indexes.
|
|
|
|
Position Begin = sourceLocToPosition(SM, R.getBegin());
|
|
|
|
Position End = sourceLocToPosition(SM, R.getEnd());
|
|
|
|
|
|
|
|
return {Begin, End};
|
|
|
|
}
|
|
|
|
|
[clangd] Fix unicode handling, using UTF-16 where LSP requires it.
Summary:
The Language Server Protocol unfortunately mandates that locations in files
be represented by line/column pairs, where the "column" is actually an index
into the UTF-16-encoded text of the line.
(This is because VSCode is written in JavaScript, which is UTF-16-native).
Internally clangd treats source files at UTF-8, the One True Encoding, and
generally deals with byte offsets (though there are exceptions).
Before this patch, conversions between offsets and LSP Position pretended
that Position.character was UTF-8 bytes, which is only true for ASCII lines.
Now we examine the text to convert correctly (but don't actually need to
transcode it, due to some nice details of the encodings).
The updated functions in SourceCode are the blessed way to interact with
the Position.character field, and anything else is likely to be wrong.
So I also updated the other accesses:
- CodeComplete needs a "clang-style" line/column, with column in utf-8 bytes.
This is now converted via Position -> offset -> clang line/column
(a new function is added to SourceCode.h for the second conversion).
- getBeginningOfIdentifier skipped backwards in UTF-16 space, which is will
behave badly when it splits a surrogate pair. Skipping backwards in UTF-8
coordinates gives the lexer a fighting chance of getting this right.
While here, I clarified(?) the logic comments, fixed a bug with identifiers
containing digits, simplified the signature slightly and added a test.
This seems likely to cause problems with editors that have the same bug, and
treat the protocol as if columns are UTF-8 bytes. But we can find and fix those.
Reviewers: hokein
Subscribers: klimek, ilya-biryukov, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D46035
llvm-svn: 331029
2018-04-27 19:59:28 +08:00
|
|
|
std::pair<size_t, size_t> offsetToClangLineColumn(StringRef Code,
|
|
|
|
size_t Offset) {
|
|
|
|
Offset = std::min(Code.size(), Offset);
|
|
|
|
StringRef Before = Code.substr(0, Offset);
|
|
|
|
int Lines = Before.count('\n');
|
|
|
|
size_t PrevNL = Before.rfind('\n');
|
|
|
|
size_t StartOfLine = (PrevNL == StringRef::npos) ? 0 : (PrevNL + 1);
|
|
|
|
return {Lines + 1, Offset - StartOfLine + 1};
|
|
|
|
}
|
|
|
|
|
[clangd] Implementation of workspace/symbol request
Summary:
This is a basic implementation of the "workspace/symbol" request which is
used to find symbols by a string query. Since this is similar to code completion
in terms of result, this implementation reuses the "fuzzyFind" in order to get
matches. For now, the scoring algorithm is the same as code completion and
improvements could be done in the future.
The index model doesn't contain quite enough symbols for this to cover
common symbols like methods, enum class enumerators, functions in unamed
namespaces, etc. The index model will be augmented separately to achieve this.
Reviewers: sammccall, ilya-biryukov
Reviewed By: sammccall
Subscribers: jkorous, hokein, simark, sammccall, klimek, mgorny, ilya-biryukov, mgrang, jkorous-apple, ioeric, MaskRay, cfe-commits
Differential Revision: https://reviews.llvm.org/D44882
llvm-svn: 330637
2018-04-24 04:00:52 +08:00
|
|
|
std::pair<llvm::StringRef, llvm::StringRef>
|
|
|
|
splitQualifiedName(llvm::StringRef QName) {
|
|
|
|
size_t Pos = QName.rfind("::");
|
|
|
|
if (Pos == llvm::StringRef::npos)
|
|
|
|
return {StringRef(), QName};
|
|
|
|
return {QName.substr(0, Pos + 2), QName.substr(Pos + 2)};
|
|
|
|
}
|
|
|
|
|
2018-05-11 20:12:08 +08:00
|
|
|
TextEdit replacementToEdit(StringRef Code, const tooling::Replacement &R) {
|
|
|
|
Range ReplacementRange = {
|
|
|
|
offsetToPosition(Code, R.getOffset()),
|
|
|
|
offsetToPosition(Code, R.getOffset() + R.getLength())};
|
|
|
|
return {ReplacementRange, R.getReplacementText()};
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<TextEdit> replacementsToEdits(StringRef Code,
|
|
|
|
const tooling::Replacements &Repls) {
|
|
|
|
std::vector<TextEdit> Edits;
|
|
|
|
for (const auto &R : Repls)
|
|
|
|
Edits.push_back(replacementToEdit(Code, R));
|
|
|
|
return Edits;
|
|
|
|
}
|
|
|
|
|
[clangd] Avoid duplicates in findDefinitions response
Summary:
When compile_commands.json contains some source files expressed as
relative paths, we can get duplicate responses to findDefinitions. The
responses only differ by the URI, which are different versions of the
same file:
"result": [
{
...
"uri": "file:///home/emaisin/src/ls-interact/cpp-test/build/../src/first.h"
},
{
...
"uri": "file:///home/emaisin/src/ls-interact/cpp-test/src/first.h"
}
]
In getAbsoluteFilePath, we try to obtain the realpath of the FileEntry
by calling tryGetRealPathName. However, this can fail and return an
empty string. It may be bug a bug in clang, but in any case we should
fall back to computing it ourselves if it happens.
I changed getAbsoluteFilePath so that if tryGetRealPathName succeeds, we
return right away (a real path is always absolute). Otherwise, we try
to build an absolute path, as we did before, but we also call
VFS->getRealPath to make sure to get the canonical path (e.g. without
any ".." in it).
Reviewers: malaperle
Subscribers: hokein, ilya-biryukov, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D48687
llvm-svn: 339483
2018-08-11 06:27:53 +08:00
|
|
|
llvm::Optional<std::string> getRealPath(const FileEntry *F,
|
|
|
|
const SourceManager &SourceMgr) {
|
|
|
|
// Ideally, we get the real path from the FileEntry object.
|
|
|
|
SmallString<128> FilePath = F->tryGetRealPathName();
|
|
|
|
if (!FilePath.empty()) {
|
|
|
|
return FilePath.str().str();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, we try to compute ourselves.
|
|
|
|
vlog("FileEntry for {0} did not contain the real path.", F->getName());
|
|
|
|
|
|
|
|
llvm::SmallString<128> Path = F->getName();
|
|
|
|
|
|
|
|
if (!llvm::sys::path::is_absolute(Path)) {
|
|
|
|
if (!SourceMgr.getFileManager().makeAbsolutePath(Path)) {
|
|
|
|
log("Could not turn relative path to absolute: {0}", Path);
|
2018-07-06 03:35:01 +08:00
|
|
|
return llvm::None;
|
|
|
|
}
|
|
|
|
}
|
[clangd] Avoid duplicates in findDefinitions response
Summary:
When compile_commands.json contains some source files expressed as
relative paths, we can get duplicate responses to findDefinitions. The
responses only differ by the URI, which are different versions of the
same file:
"result": [
{
...
"uri": "file:///home/emaisin/src/ls-interact/cpp-test/build/../src/first.h"
},
{
...
"uri": "file:///home/emaisin/src/ls-interact/cpp-test/src/first.h"
}
]
In getAbsoluteFilePath, we try to obtain the realpath of the FileEntry
by calling tryGetRealPathName. However, this can fail and return an
empty string. It may be bug a bug in clang, but in any case we should
fall back to computing it ourselves if it happens.
I changed getAbsoluteFilePath so that if tryGetRealPathName succeeds, we
return right away (a real path is always absolute). Otherwise, we try
to build an absolute path, as we did before, but we also call
VFS->getRealPath to make sure to get the canonical path (e.g. without
any ".." in it).
Reviewers: malaperle
Subscribers: hokein, ilya-biryukov, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D48687
llvm-svn: 339483
2018-08-11 06:27:53 +08:00
|
|
|
|
|
|
|
llvm::SmallString<128> RealPath;
|
|
|
|
if (SourceMgr.getFileManager().getVirtualFileSystem()->getRealPath(
|
|
|
|
Path, RealPath)) {
|
|
|
|
log("Could not compute real path: {0}", Path);
|
|
|
|
return Path.str().str();
|
|
|
|
}
|
|
|
|
|
|
|
|
return RealPath.str().str();
|
2018-07-06 03:35:01 +08:00
|
|
|
}
|
|
|
|
|
2018-08-08 16:59:29 +08:00
|
|
|
TextEdit toTextEdit(const FixItHint &FixIt, const SourceManager &M,
|
|
|
|
const LangOptions &L) {
|
|
|
|
TextEdit Result;
|
|
|
|
Result.range =
|
|
|
|
halfOpenToRange(M, Lexer::makeFileCharRange(FixIt.RemoveRange, M, L));
|
|
|
|
Result.newText = FixIt.CodeToInsert;
|
|
|
|
return Result;
|
|
|
|
}
|
|
|
|
|
2018-08-13 16:23:01 +08:00
|
|
|
bool IsRangeConsecutive(const Range &Left, const Range &Right) {
|
|
|
|
return Left.end.line == Right.start.line &&
|
|
|
|
Left.end.character == Right.start.character;
|
|
|
|
}
|
|
|
|
|
2017-12-19 20:23:48 +08:00
|
|
|
} // namespace clangd
|
|
|
|
} // namespace clang
|