Skip to content

Commit b9a6fbc

Browse files
committed
Rewrite scope lookup for performance
Linear search didn't scale well, turns out.
1 parent d62831b commit b9a6fbc

File tree

2 files changed

+44
-11
lines changed

2 files changed

+44
-11
lines changed

pkgs/sass_language_services/lib/src/features/go_to_definition/scope.dart

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,59 @@ class Scope {
2121
}
2222

2323
Scope? findScope({required int offset, int length = 0}) {
24-
if ((this.offset <= offset &&
24+
var scopeContainsOffset = (this.offset <= offset &&
2525
this.offset + this.length > offset + length) ||
26-
(this.offset == offset && this.length == length)) {
26+
(this.offset == offset && this.length == length);
27+
28+
if (scopeContainsOffset) {
2729
return findInScope(offset: offset, length: length);
2830
}
31+
2932
return null;
3033
}
3134

35+
/// Assumes a sorted [list], where [matcher] would return false
36+
/// for all elements before it returns true. This lets us do a bisect
37+
/// to quickly find the first element, as opposed to a linear firstWhere.
38+
int _first<T>(List<T> list, bool Function(T item) matcher) {
39+
if (list.isEmpty) {
40+
return 0;
41+
}
42+
43+
var low = 0;
44+
var high = list.length;
45+
46+
while (low < high) {
47+
var half = ((low + high) / 2).floor();
48+
if (matcher(list[half])) {
49+
high = half;
50+
} else {
51+
low = half + 1;
52+
}
53+
}
54+
55+
return low;
56+
}
57+
3258
Scope findInScope({required int offset, int length = 0}) {
33-
var scopeAtOffset = children.firstWhere(
34-
(scope) =>
35-
scope.offset <= offset &&
36-
scope.offset + scope.length >= offset + length,
37-
orElse: () => this);
59+
var end = offset + length;
60+
var scopeIndex = _first(
61+
children,
62+
(scope) => scope.offset > end,
63+
);
3864

39-
if (scopeAtOffset == this) {
65+
if (scopeIndex == 0) {
4066
return this;
4167
}
4268

43-
return scopeAtOffset.findInScope(offset: offset, length: length);
69+
var candidate = children.elementAt(scopeIndex - 1);
70+
var containsOffset = candidate.offset <= offset &&
71+
candidate.offset + candidate.length >= offset + length;
72+
if (containsOffset) {
73+
return candidate.findInScope(offset: offset, length: length);
74+
}
75+
76+
return this;
4477
}
4578

4679
void addSymbol(StylesheetDocumentSymbol symbol) {

pkgs/sass_language_services/test/features/go_to_definition/scope_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ void main() {
4242
expect(global.findScope(offset: 21), equals(global));
4343

4444
expect(global.findScope(offset: 10), equals(first));
45-
expect(global.findScope(offset: 15), equals(first));
46-
expect(global.findScope(offset: 16), equals(second));
45+
expect(global.findScope(offset: 14), equals(first));
46+
expect(global.findScope(offset: 15), equals(second));
4747
expect(global.findScope(offset: 20), equals(second));
4848
});
4949
});

0 commit comments

Comments
 (0)