mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
[syntax-coloring] Fix incorrectly reporting a no-op when a token is removed
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
let
|
||||||
|
l
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
// CHECK: {{^}}{
|
// CHECK: {{^}}{
|
||||||
// CHECK-NEXT: key.offset: 16,
|
// CHECK-NEXT: key.offset: 16,
|
||||||
// CHECK-NEXT: key.length: 66,
|
// CHECK-NEXT: key.length: 68,
|
||||||
// CHECK-NEXT: key.diagnostic_stage: source.diagnostic.stage.swift.parse,
|
// CHECK-NEXT: key.diagnostic_stage: source.diagnostic.stage.swift.parse,
|
||||||
// CHECK-NEXT: key.syntaxmap: [
|
// CHECK-NEXT: key.syntaxmap: [
|
||||||
// CHECK-NEXT: {
|
// CHECK-NEXT: {
|
||||||
|
|||||||
30
test/SourceKit/SyntaxMapData/syntaxmap-edit-remove.swift
Normal file
30
test/SourceKit/SyntaxMapData/syntaxmap-edit-remove.swift
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// RUN: %sourcekitd-test -req=open -print-raw-response %S/Inputs/syntaxmap-edit-remove.swift == -req=edit -print-raw-response %S/Inputs/syntaxmap-edit-remove.swift -pos=2:1 -replace='' -length=1 | %sed_clean > %t.response
|
||||||
|
// RUN: %FileCheck -input-file=%t.response %s
|
||||||
|
|
||||||
|
// Initial state
|
||||||
|
|
||||||
|
// CHECK: {{^}}{
|
||||||
|
// CHECK-NEXT: key.offset: 0,
|
||||||
|
// CHECK-NEXT: key.length: 6,
|
||||||
|
// CHECK-NEXT: key.diagnostic_stage: source.diagnostic.stage.swift.parse,
|
||||||
|
// CHECK-NEXT: key.syntaxmap: [
|
||||||
|
// CHECK-NEXT: {
|
||||||
|
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.keyword,
|
||||||
|
// CHECK-NEXT: key.offset: 0,
|
||||||
|
// CHECK-NEXT: key.length: 3
|
||||||
|
// CHECK-NEXT: },
|
||||||
|
// CHECK-NEXT: {
|
||||||
|
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.identifier,
|
||||||
|
// CHECK-NEXT: key.offset: 4,
|
||||||
|
// CHECK-NEXT: key.length: 1
|
||||||
|
// CHECK-NEXT: }
|
||||||
|
// CHECK-NEXT: ],
|
||||||
|
|
||||||
|
// After deleting the identifier 'l'
|
||||||
|
|
||||||
|
// CHECK: {{^}}{
|
||||||
|
// CHECK-NEXT: key.offset: 4,
|
||||||
|
// CHECK-NEXT: key.length: 1,
|
||||||
|
// CHECK-NEXT: key.diagnostic_stage: source.diagnostic.stage.swift.parse,
|
||||||
|
// CHECK-NEXT: key.syntaxmap: [
|
||||||
|
// CHECK-NEXT: ],
|
||||||
@@ -287,7 +287,8 @@ struct SwiftSyntaxMap {
|
|||||||
std::vector<SwiftSyntaxToken> Tokens;
|
std::vector<SwiftSyntaxToken> Tokens;
|
||||||
|
|
||||||
SwiftSyntaxMap(unsigned Capacity = 0) {
|
SwiftSyntaxMap(unsigned Capacity = 0) {
|
||||||
Tokens.reserve(Capacity);
|
if (Capacity)
|
||||||
|
Tokens.reserve(Capacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addToken(const SwiftSyntaxToken &Token) {
|
void addToken(const SwiftSyntaxToken &Token) {
|
||||||
@@ -314,16 +315,32 @@ struct SwiftSyntaxMap {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void adjustForReplacement(unsigned Offset, unsigned Len, unsigned NewLen) {
|
SwiftEditorCharRange
|
||||||
unsigned EndOffset = Offset + Len;
|
adjustForReplacement(unsigned Offset, unsigned Len, unsigned NewLen) {
|
||||||
|
unsigned AffectedStart = Offset;
|
||||||
|
unsigned AffectedEnd = Offset + Len;
|
||||||
|
|
||||||
for (auto &Token: Tokens) {
|
for (auto &Token: Tokens) {
|
||||||
if (Token.Offset > EndOffset) {
|
if (Token.endOffset() <= AffectedStart)
|
||||||
|
continue; // Completely before
|
||||||
|
|
||||||
|
if (Token.Offset >= AffectedEnd) {
|
||||||
Token.Offset += NewLen - Len;
|
Token.Offset += NewLen - Len;
|
||||||
} else if (Token.endOffset() > Offset) {
|
continue; // Completely after
|
||||||
Token.Offset = Offset;
|
|
||||||
Token.Length = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Token.Offset < AffectedStart)
|
||||||
|
AffectedStart = Token.Offset;
|
||||||
|
if (Token.endOffset() > AffectedEnd)
|
||||||
|
AffectedEnd = Token.endOffset();
|
||||||
|
|
||||||
|
Token.Length = 0; // Forces a mismatch - no new token has length 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adjust for the change in length
|
||||||
|
AffectedEnd += NewLen - Len;
|
||||||
|
|
||||||
|
return {AffectedStart, AffectedEnd - AffectedStart};
|
||||||
}
|
}
|
||||||
|
|
||||||
void forEach(EditorConsumer &Consumer) {
|
void forEach(EditorConsumer &Consumer) {
|
||||||
@@ -333,10 +350,12 @@ struct SwiftSyntaxMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SwiftEditorCharRange forEachChanged(SwiftSyntaxMap &Prev,
|
/// Returns true if there were changes
|
||||||
StringRef BufferText,
|
bool forEachChanged(SwiftSyntaxMap &Prev,
|
||||||
EditorConsumer &Consumer) const {
|
SwiftEditorCharRange &Affected,
|
||||||
unsigned StartOffset = BufferText.size(), EndOffset = 0;
|
StringRef BufferText,
|
||||||
|
EditorConsumer &Consumer) const {
|
||||||
|
unsigned StartOffset = Affected.Offset, EndOffset = Affected.endOffset();
|
||||||
|
|
||||||
// Find the first tokens that don't match
|
// Find the first tokens that don't match
|
||||||
auto Start = std::make_pair(Tokens.begin(), Prev.Tokens.begin());
|
auto Start = std::make_pair(Tokens.begin(), Prev.Tokens.begin());
|
||||||
@@ -344,12 +363,11 @@ struct SwiftSyntaxMap {
|
|||||||
*Start.first == *Start.second)
|
*Start.first == *Start.second)
|
||||||
++Start.first, ++Start.second;
|
++Start.first, ++Start.second;
|
||||||
|
|
||||||
if (Start.first != Tokens.end())
|
if (Start.first != Tokens.end()) {
|
||||||
StartOffset = std::min(Start.first->Offset, StartOffset);
|
StartOffset = std::min(Start.first->Offset, StartOffset);
|
||||||
if (Start.second != Prev.Tokens.end())
|
} else if (Start.second == Prev.Tokens.end()) {
|
||||||
StartOffset = std::min(Start.second->Offset, StartOffset);
|
return false; // Both sets are identical
|
||||||
if (StartOffset == BufferText.size()) // No differences
|
}
|
||||||
return {0, 0};
|
|
||||||
|
|
||||||
// Find the last tokens that don't match
|
// Find the last tokens that don't match
|
||||||
auto End = std::make_pair(Tokens.rbegin(), Prev.Tokens.rbegin());
|
auto End = std::make_pair(Tokens.rbegin(), Prev.Tokens.rbegin());
|
||||||
@@ -359,16 +377,20 @@ struct SwiftSyntaxMap {
|
|||||||
|
|
||||||
if (End.first != Tokens.rend())
|
if (End.first != Tokens.rend())
|
||||||
EndOffset = std::max(End.first->endOffset(), EndOffset);
|
EndOffset = std::max(End.first->endOffset(), EndOffset);
|
||||||
if (End.second != Prev.Tokens.rend())
|
|
||||||
EndOffset = std::max(End.second->endOffset(), EndOffset);
|
|
||||||
assert(EndOffset >= StartOffset);
|
assert(EndOffset >= StartOffset);
|
||||||
|
|
||||||
// Adjust offsets to line boundaries
|
// Adjust offsets to line boundaries
|
||||||
StartOffset = getLineBoundary(BufferText, StartOffset, /*Start=*/true);
|
StartOffset = getPrevLineBoundary(BufferText, StartOffset);
|
||||||
EndOffset = getLineBoundary(BufferText, EndOffset, /*Start=*/false);
|
EndOffset = getNextLineBoundary(BufferText, EndOffset,
|
||||||
|
/*hasLength=*/StartOffset < EndOffset);
|
||||||
|
|
||||||
if (Start.first == Tokens.end()) // No tokens to return
|
if (Start.first == Tokens.end()) {
|
||||||
return {StartOffset, EndOffset - StartOffset};
|
// No tokens to return
|
||||||
|
Affected.Offset = StartOffset;
|
||||||
|
Affected.Length = EndOffset - StartOffset;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
auto From = Start.first;
|
auto From = Start.first;
|
||||||
auto To = End.first;
|
auto To = End.first;
|
||||||
@@ -381,7 +403,7 @@ struct SwiftSyntaxMap {
|
|||||||
if (Prev->endOffset() <= StartOffset)
|
if (Prev->endOffset() <= StartOffset)
|
||||||
break;
|
break;
|
||||||
// Multi-line token – move to previous line
|
// Multi-line token – move to previous line
|
||||||
StartOffset = getLineBoundary(BufferText, Prev->Offset, /*Start=*/true);
|
StartOffset = getPrevLineBoundary(BufferText, Prev->Offset);
|
||||||
From = Prev;
|
From = Prev;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -392,7 +414,7 @@ struct SwiftSyntaxMap {
|
|||||||
if (Prev->Offset >= EndOffset)
|
if (Prev->Offset >= EndOffset)
|
||||||
break;
|
break;
|
||||||
// Multi-line token – move to next line
|
// Multi-line token – move to next line
|
||||||
EndOffset = getLineBoundary(BufferText, Prev->endOffset(), /*Start=*/false);
|
EndOffset = getNextLineBoundary(BufferText, Prev->endOffset(), true);
|
||||||
To = Prev;
|
To = Prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,14 +424,23 @@ struct SwiftSyntaxMap {
|
|||||||
Consumer.handleSyntaxMap(From->Offset, From->Length, Kind);
|
Consumer.handleSyntaxMap(From->Offset, From->Length, Kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {StartOffset, EndOffset - StartOffset};
|
Affected.Offset = StartOffset;
|
||||||
|
Affected.Length = EndOffset - StartOffset;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static size_t getLineBoundary(StringRef Text, size_t Offset, bool Start) {
|
static size_t getPrevLineBoundary(StringRef Text, size_t Offset) {
|
||||||
auto Bound = Start? Text.rfind('\n', Offset) : Text.find('\n', Offset - 1);
|
auto Bound = Text.rfind('\n', Offset);
|
||||||
if (Bound == StringRef::npos)
|
if (Bound == StringRef::npos)
|
||||||
return Start? 0 : Text.size();
|
return 0;
|
||||||
|
return Bound + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t getNextLineBoundary(StringRef Text, size_t Offset, bool hasLength) {
|
||||||
|
auto Bound = Text.find('\n', hasLength? Offset - 1 : Offset);
|
||||||
|
if (Bound == StringRef::npos)
|
||||||
|
return Text.size();
|
||||||
return Bound + 1;
|
return Bound + 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1545,7 +1576,7 @@ ImmutableTextSnapshotRef SwiftEditorDocument::initializeText(
|
|||||||
new EditableTextBuffer(Impl.FilePath, Buf->getBuffer());
|
new EditableTextBuffer(Impl.FilePath, Buf->getBuffer());
|
||||||
Impl.SyntaxMap.Tokens.clear();
|
Impl.SyntaxMap.Tokens.clear();
|
||||||
Impl.Edited = false;
|
Impl.Edited = false;
|
||||||
Impl.AffectedRange = {0, (unsigned)Buf->getBufferSize()};
|
Impl.AffectedRange = {0, Buf->getBufferSize()};
|
||||||
Impl.SemanticInfo =
|
Impl.SemanticInfo =
|
||||||
new SwiftDocumentSemanticInfo(Impl.FilePath, Impl.LangSupport);
|
new SwiftDocumentSemanticInfo(Impl.FilePath, Impl.LangSupport);
|
||||||
Impl.SemanticInfo->setCompilerArgs(Args);
|
Impl.SemanticInfo->setCompilerArgs(Args);
|
||||||
@@ -1585,19 +1616,8 @@ ImmutableTextSnapshotRef SwiftEditorDocument::replaceText(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// update the old syntax data offsets to account for the replaced range
|
// update the old syntax data offsets to account for the replaced range
|
||||||
Impl.SyntaxMap.adjustForReplacement(Offset, Length, Str.size());
|
Impl.AffectedRange = Impl.SyntaxMap.adjustForReplacement(Offset, Length, Str.size());
|
||||||
ImmutableTextBufferRef ImmBuf = Snapshot->getBuffer();
|
ImmutableTextBufferRef ImmBuf = Snapshot->getBuffer();
|
||||||
|
|
||||||
// The affected range starts from the previous newline.
|
|
||||||
if (Offset > 0) {
|
|
||||||
auto AffectedRangeOffset = ImmBuf->getText().rfind('\n', Offset);
|
|
||||||
Impl.AffectedRange.Offset =
|
|
||||||
AffectedRangeOffset != StringRef::npos ? AffectedRangeOffset + 1 : 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Impl.AffectedRange.Offset = 0;
|
|
||||||
|
|
||||||
Impl.AffectedRange.Length = ImmBuf->getText().size() - Impl.AffectedRange.Offset;
|
|
||||||
Impl.Edited = true;
|
Impl.Edited = true;
|
||||||
|
|
||||||
return Snapshot;
|
return Snapshot;
|
||||||
@@ -1659,19 +1679,20 @@ void SwiftEditorDocument::readSyntaxInfo(EditorConsumer &Consumer) {
|
|||||||
Impl.SyntaxInfo->getBufferID());
|
Impl.SyntaxInfo->getBufferID());
|
||||||
ModelContext.walk(SyntaxWalker);
|
ModelContext.walk(SyntaxWalker);
|
||||||
|
|
||||||
|
bool SawChanges = true;
|
||||||
if (Impl.Edited) {
|
if (Impl.Edited) {
|
||||||
auto Text = Impl.EditableBuffer->getBuffer()->getText();
|
auto Text = Impl.EditableBuffer->getBuffer()->getText();
|
||||||
auto AffectedRange = NewMap.forEachChanged(Impl.SyntaxMap, Text, Consumer);
|
SawChanges = NewMap.forEachChanged(Impl.SyntaxMap, Impl.AffectedRange, Text,
|
||||||
Impl.AffectedRange = AffectedRange;
|
Consumer);
|
||||||
} else {
|
} else {
|
||||||
NewMap.forEach(Consumer);
|
NewMap.forEach(Consumer);
|
||||||
}
|
}
|
||||||
Impl.SyntaxMap = NewMap;
|
Impl.SyntaxMap = std::move(NewMap);
|
||||||
|
|
||||||
// Recording an affected length of 0 still results in the client updating its
|
// Recording an affected length of 0 still results in the client updating its
|
||||||
// copy of the syntax map (by clearning all tokens on the line of the affected
|
// copy of the syntax map (by clearning all tokens on the line of the affected
|
||||||
// offset). We need to not record it at all to signal a no-op.
|
// offset). We need to not record it at all to signal a no-op.
|
||||||
if (!Impl.AffectedRange.isEmpty())
|
if (SawChanges)
|
||||||
Consumer.recordAffectedRange(Impl.AffectedRange.Offset,
|
Consumer.recordAffectedRange(Impl.AffectedRange.Offset,
|
||||||
Impl.AffectedRange.Length);
|
Impl.AffectedRange.Length);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user