Skip to content

Commit ae6b6b5

Browse files
authored
Folding ranges (#32)
1 parent dda764e commit ae6b6b5

File tree

7 files changed

+240
-3
lines changed

7 files changed

+240
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
const assert = require('node:assert');
2+
const path = require('node:path');
3+
const vscode = require('vscode');
4+
const { showFile, sleepCI } = require('../util');
5+
6+
const stylesUri = vscode.Uri.file(
7+
path.resolve(__dirname, 'fixtures', 'styles.scss')
8+
);
9+
10+
before(async () => {
11+
await showFile(stylesUri);
12+
await sleepCI();
13+
});
14+
15+
after(async () => {
16+
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
17+
});
18+
19+
/**
20+
* @param {import('vscode').Uri} documentUri
21+
* @param {Array<import('vscode').Position>} positions
22+
* @returns {Promise<Array<import('vscode').FoldingRange>>}
23+
*/
24+
async function getFoldingRanges(documentUri, positions) {
25+
const result = await vscode.commands.executeCommand(
26+
'vscode.executeFoldingRangeProvider',
27+
documentUri,
28+
positions
29+
);
30+
return result;
31+
}
32+
33+
test('gets document folding ranges', async () => {
34+
const [result] = await getFoldingRanges(stylesUri, [
35+
new vscode.Position(7, 5),
36+
]);
37+
38+
assert.ok(result, 'Should have gotten folding ranges');
39+
});

pkgs/sass_language_server/lib/src/language_server.dart

+21
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ class LanguageServer {
176176
documentHighlightProvider: Either2.t1(true),
177177
documentLinkProvider: DocumentLinkOptions(resolveProvider: false),
178178
documentSymbolProvider: Either2.t1(true),
179+
foldingRangeProvider: Either3.t1(true),
179180
referencesProvider: Either2.t1(true),
180181
renameProvider: Either2.t2(RenameOptions(prepareProvider: true)),
181182
selectionRangeProvider: Either3.t1(true),
@@ -433,6 +434,26 @@ class LanguageServer {
433434
}
434435
});
435436

437+
_connection.onFoldingRanges((params) async {
438+
try {
439+
var document = _documents.get(params.textDocument.uri);
440+
if (document == null) {
441+
return [];
442+
}
443+
444+
var configuration = _getLanguageConfiguration(document);
445+
if (configuration.foldingRanges.enabled) {
446+
var result = _ls.getFoldingRanges(document);
447+
return result;
448+
} else {
449+
return [];
450+
}
451+
} on Exception catch (e) {
452+
_log.debug(e.toString());
453+
return [];
454+
}
455+
});
456+
436457
_connection.onSelectionRanges((params) async {
437458
try {
438459
var document = _documents.get(params.textDocument.uri);

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ class FeatureConfiguration {
66

77
class LanguageConfiguration {
88
late final FeatureConfiguration definition;
9-
late final FeatureConfiguration highlights;
109
late final FeatureConfiguration documentSymbols;
1110
late final FeatureConfiguration documentLinks;
11+
late final FeatureConfiguration foldingRanges;
12+
late final FeatureConfiguration highlights;
1213
late final FeatureConfiguration references;
1314
late final FeatureConfiguration rename;
1415
late final FeatureConfiguration selectionRanges;
@@ -21,6 +22,8 @@ class LanguageConfiguration {
2122
enabled: config?['documentSymbols']?['enabled'] as bool? ?? true);
2223
documentLinks = FeatureConfiguration(
2324
enabled: config?['documentLinks']?['enabled'] as bool? ?? true);
25+
foldingRanges = FeatureConfiguration(
26+
enabled: config?['foldingRanges']?['enabled'] as bool? ?? true);
2427
highlights = FeatureConfiguration(
2528
enabled: config?['highlights']?['enabled'] as bool? ?? true);
2629
references = FeatureConfiguration(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import 'package:lsp_server/lsp_server.dart' as lsp;
2+
import 'package:sass_language_services/sass_language_services.dart';
3+
4+
import '../go_to_definition/scope.dart';
5+
import '../go_to_definition/scope_visitor.dart';
6+
import '../go_to_definition/scoped_symbols.dart';
7+
import '../language_feature.dart';
8+
9+
class FoldingRangesFeature extends LanguageFeature {
10+
FoldingRangesFeature({required super.ls});
11+
12+
List<lsp.FoldingRange> getFoldingRanges(TextDocument document) {
13+
var stylesheet = ls.parseStylesheet(document);
14+
15+
var symbols = ls.cache.getDocumentSymbols(document) ??
16+
ScopedSymbols(
17+
stylesheet,
18+
document.languageId == 'sass' ? Dialect.indented : Dialect.scss,
19+
);
20+
ls.cache.setDocumentSymbols(document, symbols);
21+
22+
var result = <lsp.FoldingRange>[];
23+
// Omit the global scope.
24+
for (var childScope in symbols.globalScope.children) {
25+
result.addAll(_toFoldingRanges(document, childScope));
26+
}
27+
return result;
28+
}
29+
30+
List<lsp.FoldingRange> _toFoldingRanges(TextDocument document, Scope scope) {
31+
var result = <lsp.FoldingRange>[];
32+
result.add(_toFoldingRange(document, scope));
33+
if (scope.children.isEmpty) {
34+
return result;
35+
}
36+
for (var childScope in scope.children) {
37+
result.addAll(_toFoldingRanges(document, childScope));
38+
}
39+
return result;
40+
}
41+
42+
lsp.FoldingRange _toFoldingRange(TextDocument document, Scope scope) {
43+
var startLine = document.positionAt(scope.offset).line;
44+
var endLine = document.positionAt(scope.offset + scope.length).line;
45+
return lsp.FoldingRange(startLine: startLine, endLine: endLine);
46+
}
47+
}

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

+37
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,28 @@ class ScopeVisitor with sass.RecursiveStatementVisitor {
6060
return null;
6161
}
6262

63+
@override
64+
void visitAtRule(sass.AtRule node) {
65+
if (node.children != null) {
66+
var span = node.span;
67+
_addScope(
68+
offset: span.start.offset,
69+
length: span.length,
70+
);
71+
}
72+
super.visitAtRule(node);
73+
}
74+
75+
@override
76+
void visitAtRootRule(sass.AtRootRule node) {
77+
var span = node.span;
78+
_addScope(
79+
offset: span.start.offset,
80+
length: span.length,
81+
);
82+
super.visitAtRootRule(node);
83+
}
84+
6385
@override
6486
void visitDeclaration(node) {
6587
var isCustomProperty =
@@ -241,6 +263,21 @@ class ScopeVisitor with sass.RecursiveStatementVisitor {
241263
super.visitIfRule(node);
242264
}
243265

266+
@override
267+
void visitIncludeRule(sass.IncludeRule node) {
268+
var span = node.span;
269+
270+
var argsEndIndex = node.arguments.span.end.offset - span.start.offset;
271+
var scopeIndex = span.text.indexOf(openBracketOrNewline, argsEndIndex);
272+
273+
_addScope(
274+
offset: span.start.offset + scopeIndex,
275+
length: span.length - scopeIndex,
276+
);
277+
278+
super.visitIncludeRule(node);
279+
}
280+
244281
@override
245282
void visitMixinRule(node) {
246283
var span = node.span;

pkgs/sass_language_services/lib/src/language_services.dart

+9-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:sass_api/sass_api.dart' as sass;
33
import 'package:sass_language_services/sass_language_services.dart';
44
import 'package:sass_language_services/src/features/document_highlights/document_highlights_feature.dart';
55
import 'package:sass_language_services/src/features/find_references/find_references_feature.dart';
6+
import 'package:sass_language_services/src/features/folding_ranges/folding_ranges_feature.dart';
67
import 'package:sass_language_services/src/features/go_to_definition/go_to_definition_feature.dart';
78
import 'package:sass_language_services/src/features/rename/rename_feature.dart';
89
import 'package:sass_language_services/src/features/selection_ranges/selection_ranges_feature.dart';
@@ -23,8 +24,9 @@ class LanguageServices {
2324
late final DocumentHighlightsFeature _documentHighlights;
2425
late final DocumentLinksFeature _documentLinks;
2526
late final DocumentSymbolsFeature _documentSymbols;
26-
late final GoToDefinitionFeature _goToDefinition;
27+
late final FoldingRangesFeature _foldingRanges;
2728
late final FindReferencesFeature _findReferences;
29+
late final GoToDefinitionFeature _goToDefinition;
2830
late final RenameFeature _rename;
2931
late final SelectionRangesFeature _selectionRanges;
3032
late final WorkspaceSymbolsFeature _workspaceSymbols;
@@ -36,8 +38,9 @@ class LanguageServices {
3638
_documentHighlights = DocumentHighlightsFeature(ls: this);
3739
_documentLinks = DocumentLinksFeature(ls: this);
3840
_documentSymbols = DocumentSymbolsFeature(ls: this);
39-
_goToDefinition = GoToDefinitionFeature(ls: this);
4041
_findReferences = FindReferencesFeature(ls: this);
42+
_foldingRanges = FoldingRangesFeature(ls: this);
43+
_goToDefinition = GoToDefinitionFeature(ls: this);
4144
_rename = RenameFeature(ls: this);
4245
_selectionRanges = SelectionRangesFeature(ls: this);
4346
_workspaceSymbols = WorkspaceSymbolsFeature(ls: this);
@@ -70,6 +73,10 @@ class LanguageServices {
7073
return _workspaceSymbols.findWorkspaceSymbols(query);
7174
}
7275

76+
List<lsp.FoldingRange> getFoldingRanges(TextDocument document) {
77+
return _foldingRanges.getFoldingRanges(document);
78+
}
79+
7380
List<lsp.SelectionRange> getSelectionRanges(
7481
TextDocument document, List<lsp.Position> positions) {
7582
return _selectionRanges.getSelectionRanges(document, positions);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import 'package:lsp_server/lsp_server.dart' as lsp;
2+
import 'package:sass_language_services/sass_language_services.dart';
3+
import 'package:test/test.dart';
4+
5+
import '../../memory_file_system.dart';
6+
import '../../test_client_capabilities.dart';
7+
8+
final fs = MemoryFileSystem();
9+
final ls = LanguageServices(fs: fs, clientCapabilities: getCapabilities());
10+
11+
lsp.FoldingRange fr(int startLine, int endLine) {
12+
return lsp.FoldingRange(
13+
startLine: startLine,
14+
endLine: endLine,
15+
);
16+
}
17+
18+
void main() {
19+
group('folding ranges', () {
20+
setUp(() {
21+
ls.cache.clear();
22+
});
23+
24+
test('style rules', () {
25+
var document = fs.createDocument('''
26+
.foo {
27+
color: red;
28+
29+
.bar {
30+
color: blue;
31+
}
32+
}
33+
''');
34+
35+
var result = ls.getFoldingRanges(document);
36+
expect(result, hasLength(2));
37+
expect(
38+
result,
39+
equals([
40+
fr(0, 6),
41+
fr(3, 5),
42+
]),
43+
);
44+
});
45+
46+
test('mixin rules', () {
47+
var document = fs.createDocument('''@mixin foo {
48+
color: red;
49+
50+
.bar {
51+
color: blue;
52+
}
53+
}
54+
''');
55+
56+
var result = ls.getFoldingRanges(document);
57+
expect(result, hasLength(2));
58+
expect(
59+
result,
60+
equals([
61+
fr(0, 6),
62+
fr(3, 5),
63+
]),
64+
);
65+
});
66+
67+
test('include rules', () {
68+
var document = fs.createDocument('''@include foo {
69+
--color-foo: red;
70+
}
71+
''');
72+
73+
var result = ls.getFoldingRanges(document);
74+
expect(result, hasLength(1));
75+
expect(
76+
result,
77+
equals([
78+
fr(0, 2),
79+
]),
80+
);
81+
});
82+
});
83+
}

0 commit comments

Comments
 (0)