Skip to content

Commit 9fd3f69

Browse files
author
Nathan Hawes
committed
[syntax-coloring] Fix incorrectly reporting a no-op when a token is removed
1 parent 40c91b1 commit 9fd3f69

File tree

4 files changed

+100
-47
lines changed

4 files changed

+100
-47
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
let
2+
l

test/SourceKit/SyntaxMapData/syntaxmap-edit-multiline-string.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444

4545
// CHECK: {{^}}{
4646
// CHECK-NEXT: key.offset: 16,
47-
// CHECK-NEXT: key.length: 66,
47+
// CHECK-NEXT: key.length: 68,
4848
// CHECK-NEXT: key.diagnostic_stage: source.diagnostic.stage.swift.parse,
4949
// CHECK-NEXT: key.syntaxmap: [
5050
// CHECK-NEXT: {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// 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
2+
// RUN: %FileCheck -input-file=%t.response %s
3+
4+
// Initial state
5+
6+
// CHECK: {{^}}{
7+
// CHECK-NEXT: key.offset: 0,
8+
// CHECK-NEXT: key.length: 6,
9+
// CHECK-NEXT: key.diagnostic_stage: source.diagnostic.stage.swift.parse,
10+
// CHECK-NEXT: key.syntaxmap: [
11+
// CHECK-NEXT: {
12+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.keyword,
13+
// CHECK-NEXT: key.offset: 0,
14+
// CHECK-NEXT: key.length: 3
15+
// CHECK-NEXT: },
16+
// CHECK-NEXT: {
17+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.identifier,
18+
// CHECK-NEXT: key.offset: 4,
19+
// CHECK-NEXT: key.length: 1
20+
// CHECK-NEXT: }
21+
// CHECK-NEXT: ],
22+
23+
// After deleting the identifier 'l'
24+
25+
// CHECK: {{^}}{
26+
// CHECK-NEXT: key.offset: 4,
27+
// CHECK-NEXT: key.length: 1,
28+
// CHECK-NEXT: key.diagnostic_stage: source.diagnostic.stage.swift.parse,
29+
// CHECK-NEXT: key.syntaxmap: [
30+
// CHECK-NEXT: ],

tools/SourceKit/lib/SwiftLang/SwiftEditor.cpp

Lines changed: 67 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,8 @@ struct SwiftSyntaxMap {
286286
std::vector<SwiftSyntaxToken> Tokens;
287287

288288
SwiftSyntaxMap(unsigned Capacity = 0) {
289-
Tokens.reserve(Capacity);
289+
if (Capacity)
290+
Tokens.reserve(Capacity);
290291
}
291292

292293
void addToken(const SwiftSyntaxToken &Token) {
@@ -313,16 +314,32 @@ struct SwiftSyntaxMap {
313314
});
314315
}
315316

316-
void adjustForReplacement(unsigned Offset, unsigned Len, unsigned NewLen) {
317-
unsigned EndOffset = Offset + Len;
317+
SwiftEditorCharRange
318+
adjustForReplacement(unsigned Offset, unsigned Len, unsigned NewLen) {
319+
unsigned AffectedStart = Offset;
320+
unsigned AffectedEnd = Offset + Len;
321+
318322
for (auto &Token: Tokens) {
319-
if (Token.Offset > EndOffset) {
323+
if (Token.endOffset() <= AffectedStart)
324+
continue; // Completely before
325+
326+
if (Token.Offset >= AffectedEnd) {
320327
Token.Offset += NewLen - Len;
321-
} else if (Token.endOffset() > Offset) {
322-
Token.Offset = Offset;
323-
Token.Length = 0;
328+
continue; // Completely after
324329
}
330+
331+
if (Token.Offset < AffectedStart)
332+
AffectedStart = Token.Offset;
333+
if (Token.endOffset() > AffectedEnd)
334+
AffectedEnd = Token.endOffset();
335+
336+
Token.Length = 0; // Forces a mismatch - no new token has length 0
325337
}
338+
339+
// Adjust for the change in length
340+
AffectedEnd += NewLen - Len;
341+
342+
return {AffectedStart, AffectedEnd - AffectedStart};
326343
}
327344

328345
void forEach(EditorConsumer &Consumer) {
@@ -332,23 +349,24 @@ struct SwiftSyntaxMap {
332349
}
333350
}
334351

335-
SwiftEditorCharRange forEachChanged(SwiftSyntaxMap &Prev,
336-
StringRef BufferText,
337-
EditorConsumer &Consumer) const {
338-
unsigned StartOffset = BufferText.size(), EndOffset = 0;
352+
/// Returns true if there were changes
353+
bool forEachChanged(SwiftSyntaxMap &Prev,
354+
SwiftEditorCharRange &Affected,
355+
StringRef BufferText,
356+
EditorConsumer &Consumer) const {
357+
unsigned StartOffset = Affected.Offset, EndOffset = Affected.endOffset();
339358

340359
// Find the first tokens that don't match
341360
auto Start = std::make_pair(Tokens.begin(), Prev.Tokens.begin());
342361
while (Start.first != Tokens.end() && Start.second != Prev.Tokens.end() &&
343362
*Start.first == *Start.second)
344363
++Start.first, ++Start.second;
345364

346-
if (Start.first != Tokens.end())
365+
if (Start.first != Tokens.end()) {
347366
StartOffset = std::min(Start.first->Offset, StartOffset);
348-
if (Start.second != Prev.Tokens.end())
349-
StartOffset = std::min(Start.second->Offset, StartOffset);
350-
if (StartOffset == BufferText.size()) // No differences
351-
return {0, 0};
367+
} else if (Start.second == Prev.Tokens.end()) {
368+
return false; // Both sets are identical
369+
}
352370

353371
// Find the last tokens that don't match
354372
auto End = std::make_pair(Tokens.rbegin(), Prev.Tokens.rbegin());
@@ -358,16 +376,20 @@ struct SwiftSyntaxMap {
358376

359377
if (End.first != Tokens.rend())
360378
EndOffset = std::max(End.first->endOffset(), EndOffset);
361-
if (End.second != Prev.Tokens.rend())
362-
EndOffset = std::max(End.second->endOffset(), EndOffset);
379+
363380
assert(EndOffset >= StartOffset);
364381

365382
// Adjust offsets to line boundaries
366-
StartOffset = getLineBoundary(BufferText, StartOffset, /*Start=*/true);
367-
EndOffset = getLineBoundary(BufferText, EndOffset, /*Start=*/false);
368-
369-
if (Start.first == Tokens.end()) // No tokens to return
370-
return {StartOffset, EndOffset - StartOffset};
383+
StartOffset = getPrevLineBoundary(BufferText, StartOffset);
384+
EndOffset = getNextLineBoundary(BufferText, EndOffset,
385+
/*hasLength=*/StartOffset < EndOffset);
386+
387+
if (Start.first == Tokens.end()) {
388+
// No tokens to return
389+
Affected.Offset = StartOffset;
390+
Affected.Length = EndOffset - StartOffset;
391+
return true;
392+
}
371393

372394
auto From = Start.first;
373395
auto To = End.first;
@@ -380,7 +402,7 @@ struct SwiftSyntaxMap {
380402
if (Prev->endOffset() <= StartOffset)
381403
break;
382404
// Multi-line token – move to previous line
383-
StartOffset = getLineBoundary(BufferText, Prev->Offset, /*Start=*/true);
405+
StartOffset = getPrevLineBoundary(BufferText, Prev->Offset);
384406
From = Prev;
385407
};
386408

@@ -391,7 +413,7 @@ struct SwiftSyntaxMap {
391413
if (Prev->Offset >= EndOffset)
392414
break;
393415
// Multi-line token – move to next line
394-
EndOffset = getLineBoundary(BufferText, Prev->endOffset(), /*Start=*/false);
416+
EndOffset = getNextLineBoundary(BufferText, Prev->endOffset(), true);
395417
To = Prev;
396418
}
397419

@@ -401,14 +423,23 @@ struct SwiftSyntaxMap {
401423
Consumer.handleSyntaxMap(From->Offset, From->Length, Kind);
402424
}
403425

404-
return {StartOffset, EndOffset - StartOffset};
426+
Affected.Offset = StartOffset;
427+
Affected.Length = EndOffset - StartOffset;
428+
return true;
405429
}
406430

407431
private:
408-
static size_t getLineBoundary(StringRef Text, size_t Offset, bool Start) {
409-
auto Bound = Start? Text.rfind('\n', Offset) : Text.find('\n', Offset - 1);
432+
static size_t getPrevLineBoundary(StringRef Text, size_t Offset) {
433+
auto Bound = Text.rfind('\n', Offset);
434+
if (Bound == StringRef::npos)
435+
return 0;
436+
return Bound + 1;
437+
}
438+
439+
static size_t getNextLineBoundary(StringRef Text, size_t Offset, bool hasLength) {
440+
auto Bound = Text.find('\n', hasLength? Offset - 1 : Offset);
410441
if (Bound == StringRef::npos)
411-
return Start? 0 : Text.size();
442+
return Text.size();
412443
return Bound + 1;
413444
}
414445
};
@@ -1593,7 +1624,7 @@ ImmutableTextSnapshotRef SwiftEditorDocument::initializeText(
15931624
new EditableTextBuffer(Impl.FilePath, Buf->getBuffer());
15941625
Impl.SyntaxMap.Tokens.clear();
15951626
Impl.Edited = false;
1596-
Impl.AffectedRange = {0, (unsigned)Buf->getBufferSize()};
1627+
Impl.AffectedRange = {0, Buf->getBufferSize()};
15971628
Impl.SemanticInfo =
15981629
new SwiftDocumentSemanticInfo(Impl.FilePath, Impl.LangSupport);
15991630
Impl.SemanticInfo->setCompilerArgs(Args);
@@ -1633,19 +1664,8 @@ ImmutableTextSnapshotRef SwiftEditorDocument::replaceText(
16331664
}
16341665

16351666
// update the old syntax data offsets to account for the replaced range
1636-
Impl.SyntaxMap.adjustForReplacement(Offset, Length, Str.size());
1667+
Impl.AffectedRange = Impl.SyntaxMap.adjustForReplacement(Offset, Length, Str.size());
16371668
ImmutableTextBufferRef ImmBuf = Snapshot->getBuffer();
1638-
1639-
// The affected range starts from the previous newline.
1640-
if (Offset > 0) {
1641-
auto AffectedRangeOffset = ImmBuf->getText().rfind('\n', Offset);
1642-
Impl.AffectedRange.Offset =
1643-
AffectedRangeOffset != StringRef::npos ? AffectedRangeOffset + 1 : 0;
1644-
}
1645-
else
1646-
Impl.AffectedRange.Offset = 0;
1647-
1648-
Impl.AffectedRange.Length = ImmBuf->getText().size() - Impl.AffectedRange.Offset;
16491669
Impl.Edited = true;
16501670

16511671
return Snapshot;
@@ -1707,19 +1727,20 @@ void SwiftEditorDocument::readSyntaxInfo(EditorConsumer &Consumer) {
17071727
Impl.SyntaxInfo->getBufferID());
17081728
ModelContext.walk(SyntaxWalker);
17091729

1730+
bool SawChanges = true;
17101731
if (Impl.Edited) {
17111732
auto Text = Impl.EditableBuffer->getBuffer()->getText();
1712-
auto AffectedRange = NewMap.forEachChanged(Impl.SyntaxMap, Text, Consumer);
1713-
Impl.AffectedRange = AffectedRange;
1733+
SawChanges = NewMap.forEachChanged(Impl.SyntaxMap, Impl.AffectedRange, Text,
1734+
Consumer);
17141735
} else {
17151736
NewMap.forEach(Consumer);
17161737
}
1717-
Impl.SyntaxMap = NewMap;
1738+
Impl.SyntaxMap = std::move(NewMap);
17181739

17191740
// Recording an affected length of 0 still results in the client updating its
17201741
// copy of the syntax map (by clearning all tokens on the line of the affected
17211742
// offset). We need to not record it at all to signal a no-op.
1722-
if (!Impl.AffectedRange.isEmpty())
1743+
if (SawChanges)
17231744
Consumer.recordAffectedRange(Impl.AffectedRange.Offset,
17241745
Impl.AffectedRange.Length);
17251746
}

0 commit comments

Comments
 (0)