Skip to content

Commit 68ed8ed

Browse files
committed
Find references
1 parent 2a3d6ee commit 68ed8ed

File tree

13 files changed

+598
-37
lines changed

13 files changed

+598
-37
lines changed

pkgs/sass_language_server/lib/src/language_server.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ class LanguageServer {
174174
definitionProvider: Either2.t1(true),
175175
documentLinkProvider: DocumentLinkOptions(resolveProvider: false),
176176
documentSymbolProvider: Either2.t1(true),
177+
referencesProvider: Either2.t1(true),
177178
textDocumentSync: Either2.t1(TextDocumentSyncKind.Incremental),
178179
workspaceSymbolProvider: Either2.t1(true),
179180
);
@@ -313,6 +314,32 @@ class LanguageServer {
313314
_connection.peer
314315
.registerMethod('textDocument/documentSymbol', onDocumentSymbol);
315316

317+
_connection.onReferences((params) async {
318+
try {
319+
var document = _documents.get(params.textDocument.uri);
320+
if (document == null) return [];
321+
322+
var configuration = _getLanguageConfiguration(document);
323+
if (configuration.references.enabled) {
324+
if (initialScan != null) {
325+
await initialScan;
326+
}
327+
328+
var result = await _ls.findReferences(
329+
document,
330+
params.position,
331+
params.context,
332+
);
333+
return result;
334+
} else {
335+
return [];
336+
}
337+
} on Exception catch (e) {
338+
_log.debug(e.toString());
339+
return [];
340+
}
341+
});
342+
316343
// TODO: add this handler upstream
317344
Future<List<WorkspaceSymbol>> onWorkspaceSymbol(dynamic params) async {
318345
try {

pkgs/sass_language_services/lib/src/configuration/language_configuration.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ class DocumentLinksConfiguration extends FeatureConfiguration {
1616
DocumentLinksConfiguration({required super.enabled});
1717
}
1818

19+
class ReferencesConfiguration extends FeatureConfiguration {
20+
ReferencesConfiguration({required super.enabled});
21+
}
22+
1923
class WorkspaceSymbolsConfiguration extends FeatureConfiguration {
2024
WorkspaceSymbolsConfiguration({required super.enabled});
2125
}
@@ -24,6 +28,7 @@ class LanguageConfiguration {
2428
late final DefinitionConfiguration definition;
2529
late final DocumentSymbolsConfiguration documentSymbols;
2630
late final DocumentLinksConfiguration documentLinks;
31+
late final ReferencesConfiguration references;
2732
late final WorkspaceSymbolsConfiguration workspaceSymbols;
2833

2934
LanguageConfiguration.from(dynamic config) {
@@ -33,6 +38,8 @@ class LanguageConfiguration {
3338
enabled: config?['documentSymbols']?['enabled'] as bool? ?? true);
3439
documentLinks = DocumentLinksConfiguration(
3540
enabled: config?['documentLinks']?['enabled'] as bool? ?? true);
41+
references = ReferencesConfiguration(
42+
enabled: config?['references']?['enabled'] as bool? ?? true);
3643
workspaceSymbols = WorkspaceSymbolsConfiguration(
3744
enabled: config?['workspaceSymbols']?['enabled'] as bool? ?? true);
3845
}

pkgs/sass_language_services/lib/src/features/document_symbols/document_symbols_visitor.dart

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -231,17 +231,7 @@ class DocumentSymbolsVisitor with sass.RecursiveStatementVisitor {
231231
}
232232

233233
if (nameRange == null) {
234-
// The selector span seems to be relative to node, not to the file.
235-
nameRange = lsp.Range(
236-
start: lsp.Position(
237-
line: node.span.start.line + selector.span.start.line,
238-
character: node.span.start.column + selector.span.start.column,
239-
),
240-
end: lsp.Position(
241-
line: node.span.start.line + selector.span.end.line,
242-
character: node.span.start.column + selector.span.end.column,
243-
),
244-
);
234+
nameRange = selectorNameRange(node, selector);
245235

246236
// symbolRange: start position of selector's nameRange, end of stylerule (node.span.end).
247237
symbolRange = lsp.Range(
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import 'package:lsp_server/lsp_server.dart' as lsp;
2+
import 'package:sass_language_services/sass_language_services.dart';
3+
import 'package:sass_language_services/src/features/find_references/find_references_visitor.dart';
4+
import 'package:sass_language_services/src/features/go_to_definition/go_to_definition_feature.dart';
5+
6+
import '../../sass/sass_data.dart';
7+
import '../go_to_definition/definition.dart';
8+
import 'reference.dart';
9+
10+
class FindReferencesFeature extends GoToDefinitionFeature {
11+
FindReferencesFeature({required super.ls});
12+
13+
Future<List<lsp.Location>> findReferences(TextDocument document,
14+
lsp.Position position, lsp.ReferenceContext context) async {
15+
var references = await internalFindReferences(document, position, context);
16+
return references.references.map((r) => r.location).toList();
17+
}
18+
19+
Future<({Definition? definition, List<Reference> references})>
20+
internalFindReferences(TextDocument document, lsp.Position position,
21+
lsp.ReferenceContext context) async {
22+
var references = <Reference>[];
23+
var definition = await internalGoToDefinition(document, position);
24+
if (definition == null) {
25+
return (definition: definition, references: references);
26+
}
27+
28+
String? builtin;
29+
if (definition.location == null) {
30+
// If we don't have a location we might be dealing with a built-in.
31+
var sassData = SassData();
32+
for (var module in sassData.modules) {
33+
for (var function in module.functions) {
34+
if (function.name == definition.name) {
35+
builtin = function.name;
36+
break;
37+
}
38+
}
39+
for (var variable in module.variables) {
40+
if (variable.name == definition.name) {
41+
builtin = variable.name;
42+
break;
43+
}
44+
}
45+
if (builtin != null) {
46+
break;
47+
}
48+
}
49+
}
50+
51+
if (definition.location == null && builtin == null) {
52+
return (definition: definition, references: references);
53+
}
54+
55+
var name = builtin ?? definition.name;
56+
57+
var candidates = <Reference>[];
58+
// Go through all documents with a visitor.
59+
// For each document, collect candidates that match the definition name.
60+
for (var document in ls.cache.getDocuments()) {
61+
var stylesheet = ls.parseStylesheet(document);
62+
var visitor = FindReferencesVisitor(
63+
document,
64+
name,
65+
includeDeclaration: context.includeDeclaration,
66+
);
67+
stylesheet.accept(visitor);
68+
candidates.addAll(visitor.candidates);
69+
70+
// Go through all candidates and add matches to references.
71+
// A match is a candidate with the same name, referenceKind,
72+
// and whose definition is the same as the definition of the
73+
// symbol at [position].
74+
for (var candidate in candidates) {
75+
if (builtin case var name?) {
76+
if (name.contains(candidate.name)) {
77+
references.add(
78+
Reference(
79+
name: candidate.name,
80+
kind: candidate.kind,
81+
location: candidate.location,
82+
defaultBehavior: true,
83+
),
84+
);
85+
}
86+
} else {
87+
if (candidate.kind != definition.kind) {
88+
continue;
89+
}
90+
91+
var candidateIsDefinition = _isSameLocation(
92+
candidate.location,
93+
definition.location!,
94+
);
95+
96+
if (!context.includeDeclaration && candidateIsDefinition) {
97+
continue;
98+
} else if (candidateIsDefinition) {
99+
references.add(candidate);
100+
continue;
101+
}
102+
103+
// Find the definition of the candidate and compare it
104+
// to the definition of the symbol at [position]. If
105+
// the two definitions are the same, we have a reference.
106+
var candidateDefinition = await internalGoToDefinition(
107+
document,
108+
position,
109+
);
110+
111+
if (candidateDefinition != null &&
112+
candidateDefinition.location != null) {
113+
if (_isSameLocation(
114+
candidateDefinition.location!,
115+
definition.location!,
116+
)) {
117+
references.add(candidate);
118+
continue;
119+
}
120+
}
121+
}
122+
}
123+
}
124+
125+
return (definition: definition, references: references);
126+
}
127+
128+
bool _isSameLocation(lsp.Location a, lsp.Location b) {
129+
return a.uri.toString() == b.uri.toString() &&
130+
a.range.start.line == b.range.start.line &&
131+
a.range.start.character == b.range.start.character &&
132+
a.range.end.line == b.range.end.line &&
133+
a.range.end.character == b.range.end.character;
134+
}
135+
}

0 commit comments

Comments
 (0)