Skip to content

Commit 2a9b497

Browse files
committed
Configuration, context
1 parent 6c8cb3f commit 2a9b497

File tree

4 files changed

+288
-0
lines changed

4 files changed

+288
-0
lines changed

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,25 @@ class FeatureConfiguration {
44
FeatureConfiguration({required this.enabled});
55
}
66

7+
enum MixinStyle { all, noBracket, bracket }
8+
9+
class CompletionConfiguration extends FeatureConfiguration {
10+
final bool completePropertyWithSemicolon;
11+
final bool css;
12+
final MixinStyle mixinStyle;
13+
final bool suggestFromUseOnly;
14+
final bool triggerPropertyValueCompletion;
15+
16+
CompletionConfiguration({
17+
required super.enabled,
18+
required this.completePropertyWithSemicolon,
19+
required this.css,
20+
required this.mixinStyle,
21+
required this.suggestFromUseOnly,
22+
required this.triggerPropertyValueCompletion,
23+
});
24+
}
25+
726
class HoverConfiguration extends FeatureConfiguration {
827
final bool documentation;
928
final bool references;
@@ -16,6 +35,7 @@ class HoverConfiguration extends FeatureConfiguration {
1635
}
1736

1837
class LanguageConfiguration {
38+
late final CompletionConfiguration completion;
1939
late final FeatureConfiguration definition;
2040
late final FeatureConfiguration documentSymbols;
2141
late final FeatureConfiguration documentLinks;
@@ -28,6 +48,20 @@ class LanguageConfiguration {
2848
late final FeatureConfiguration workspaceSymbols;
2949

3050
LanguageConfiguration.from(dynamic config) {
51+
completion = CompletionConfiguration(
52+
enabled: config?['completion']?['enabled'] as bool? ?? true,
53+
completePropertyWithSemicolon:
54+
config?['completion']?['completePropertyWithSemicolon'] as bool? ??
55+
true,
56+
css: config?['completion']?['css'] as bool? ?? true,
57+
mixinStyle: _toMixinStyle(config?['completion']?['mixinStyle']),
58+
suggestFromUseOnly:
59+
config?['completion']?['suggestFromUseOnly'] as bool? ?? true,
60+
triggerPropertyValueCompletion:
61+
config?['completion']?['triggerPropertyValueCompletion'] as bool? ??
62+
true,
63+
);
64+
3165
definition = FeatureConfiguration(
3266
enabled: config?['definition']?['enabled'] as bool? ?? true);
3367
documentSymbols = FeatureConfiguration(
@@ -54,4 +88,17 @@ class LanguageConfiguration {
5488
workspaceSymbols = FeatureConfiguration(
5589
enabled: config?['workspaceSymbols']?['enabled'] as bool? ?? true);
5690
}
91+
92+
MixinStyle _toMixinStyle(dynamic style) {
93+
var styleString = style as String? ?? 'all';
94+
switch (styleString) {
95+
case 'nobracket':
96+
return MixinStyle.noBracket;
97+
case 'bracket':
98+
return MixinStyle.bracket;
99+
case 'all':
100+
default:
101+
return MixinStyle.all;
102+
}
103+
}
57104
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import 'package:lsp_server/lsp_server.dart' as lsp;
2+
import 'package:sass_api/sass_api.dart' as sass;
3+
import 'package:sass_language_services/sass_language_services.dart';
4+
5+
import '../../configuration/language_configuration.dart';
6+
7+
class CompletionContext {
8+
final lsp.Position position;
9+
final String currentWord;
10+
final String lineBeforePosition;
11+
final int offset;
12+
final lsp.Range defaultReplaceRange;
13+
final TextDocument document;
14+
final sass.Stylesheet stylesheet;
15+
final CompletionConfiguration configuration;
16+
17+
CompletionContext({
18+
required this.offset,
19+
required this.position,
20+
required this.currentWord,
21+
required this.defaultReplaceRange,
22+
required this.document,
23+
required this.stylesheet,
24+
required this.configuration,
25+
required this.lineBeforePosition,
26+
});
27+
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import 'package:lsp_server/lsp_server.dart' as lsp;
2+
import 'package:sass_api/sass_api.dart' as sass;
3+
import 'package:sass_language_services/sass_language_services.dart';
4+
5+
import '../language_feature.dart';
6+
import '../node_at_offset_visitor.dart';
7+
import './completion_context.dart';
8+
import './completion_list.dart';
9+
10+
class CompletionFeature extends LanguageFeature {
11+
CompletionFeature({required super.ls});
12+
13+
Future<lsp.CompletionList> doComplete(
14+
TextDocument document, lsp.Position position) async {
15+
var configuration = getLanguageConfiguration(document).completion;
16+
17+
var offset = document.offsetAt(position);
18+
var (lineBeforePosition, currentWord) = _getLineContext(document, offset);
19+
20+
var defaultReplaceRange = lsp.Range(
21+
start: lsp.Position(
22+
line: position.line,
23+
character: position.character - currentWord.length,
24+
),
25+
end: position,
26+
);
27+
28+
var result = CompletionList(
29+
isIncomplete: false,
30+
items: [],
31+
itemDefaults: lsp.CompletionListItemDefaults(
32+
editRange: lsp.Either2.t2(defaultReplaceRange),
33+
),
34+
);
35+
36+
var stylesheet = ls.parseStylesheet(document);
37+
var path = getNodePathAtOffset(stylesheet, offset);
38+
39+
var context = CompletionContext(
40+
offset: offset,
41+
position: position,
42+
currentWord: currentWord,
43+
defaultReplaceRange: defaultReplaceRange,
44+
document: document,
45+
configuration: configuration,
46+
stylesheet: stylesheet,
47+
lineBeforePosition: lineBeforePosition,
48+
);
49+
50+
for (var i = path.length; i >= 0; i--) {
51+
var node = path[i];
52+
if (node is sass.Declaration) {
53+
_declarationCompletion(node, context, result);
54+
} else if (node is sass.SimpleSelector) {
55+
_selectorCompletion(node, context, result);
56+
} else if (node is sass.Interpolation) {
57+
var isExtendRule = false;
58+
for (var j = i; j >= 0; j--) {
59+
var parent = path[j];
60+
if (parent is sass.ExtendRule) {
61+
isExtendRule = true;
62+
break;
63+
}
64+
}
65+
if (isExtendRule) {
66+
_extendRuleCompletion(node, context, result);
67+
} else {
68+
_interpolationCompletion(node, context, result);
69+
}
70+
} else if (node is sass.ArgumentInvocation) {
71+
_argumentInvocationCompletion(node, context, result);
72+
} else if (node is sass.StyleRule) {
73+
_styleRuleCompletion(node, context, result);
74+
} else if (node is sass.PlaceholderSelector) {
75+
_placeholderSelectorCompletion(node, context, result);
76+
} else if (node is sass.VariableDeclaration) {
77+
_variableDeclarationCompletion(node, context, result);
78+
} else if (node is sass.FunctionRule) {
79+
_functionRuleCompletion(node, context, result);
80+
} else if (node is sass.MixinRule) {
81+
_mixinRuleCompletion(node, context, result);
82+
} else if (node is sass.SupportsRule) {
83+
_supportsRuleCompletion(node, context, result);
84+
} else if (node is sass.SupportsCondition) {
85+
_supportsConditionCompletion(node, context, result);
86+
} else if (node is sass.StringExpression) {
87+
var isSassDependency = false;
88+
var isImportRule = false;
89+
for (var j = i; j >= 0; j--) {
90+
var parent = path[j];
91+
if (parent is sass.SassDependency) {
92+
isSassDependency = true;
93+
break;
94+
} else if (parent is sass.ImportRule) {
95+
isImportRule = true;
96+
break;
97+
}
98+
}
99+
if (isSassDependency) {
100+
await _sassDependencyCompletion(node, context, result);
101+
} else if (isImportRule) {
102+
await _importRuleCompletion(node, context, result);
103+
}
104+
} else if (node is sass.SilentComment) {
105+
_commentCompletion(node, context, result);
106+
} else {
107+
continue;
108+
}
109+
110+
if (result.items.isNotEmpty || context.offset > node.span.start.offset) {
111+
return _send(result);
112+
}
113+
}
114+
115+
_stylesheetCompletion(context, result);
116+
return _send(result);
117+
}
118+
119+
lsp.CompletionList _send(CompletionList result) {
120+
return lsp.CompletionList(
121+
isIncomplete: result.isIncomplete,
122+
items: result.items,
123+
itemDefaults: result.itemDefaults,
124+
);
125+
}
126+
127+
/// Get the current word and the contents of the line before [offset].
128+
(String, String) _getLineContext(TextDocument document, int offset) {
129+
var text = document.getText();
130+
131+
// From offset, go back until hitting a newline
132+
var i = offset - 1;
133+
var codeUnit = text.codeUnitAt(i);
134+
const lineFeed = 10; // \n
135+
const carriageReturn = 13; // \r
136+
while (codeUnit != lineFeed && codeUnit != carriageReturn) {
137+
i--;
138+
codeUnit = text.codeUnitAt(i);
139+
}
140+
var lineBeforePosition = text.substring(i + 1, offset);
141+
142+
// From offset, go back until hitting a word delimiter
143+
i = offset - 1;
144+
var wordDelimiters = ' \t\n\r":[()]}/,\''.codeUnits;
145+
while (i >= 0 && !wordDelimiters.contains(text.codeUnitAt(i))) {
146+
i--;
147+
}
148+
var currentWord = text.substring(i + 1, offset);
149+
150+
return (lineBeforePosition, currentWord);
151+
}
152+
153+
void _declarationCompletion(sass.Declaration node, CompletionContext context,
154+
CompletionList result) {}
155+
156+
void _interpolationCompletion(sass.Interpolation node,
157+
CompletionContext context, CompletionList result) {}
158+
159+
void _extendRuleCompletion(sass.Interpolation node, CompletionContext context,
160+
CompletionList result) {}
161+
162+
void _selectorCompletion(sass.SimpleSelector node, CompletionContext context,
163+
CompletionList result) {}
164+
165+
void _argumentInvocationCompletion(sass.ArgumentInvocation node,
166+
CompletionContext context, CompletionList result) {}
167+
168+
void _styleRuleCompletion(
169+
sass.StyleRule node, CompletionContext context, CompletionList result) {}
170+
171+
void _variableDeclarationCompletion(sass.VariableDeclaration node,
172+
CompletionContext context, CompletionList result) {}
173+
174+
void _functionRuleCompletion(sass.FunctionRule node,
175+
CompletionContext context, CompletionList result) {}
176+
177+
void _mixinRuleCompletion(
178+
sass.MixinRule node, CompletionContext context, CompletionList result) {}
179+
180+
void _supportsRuleCompletion(sass.SupportsRule node,
181+
CompletionContext context, CompletionList result) {}
182+
183+
void _supportsConditionCompletion(sass.SupportsCondition node,
184+
CompletionContext context, CompletionList result) {}
185+
186+
Future<void> _sassDependencyCompletion(sass.StringExpression node,
187+
CompletionContext context, CompletionList result) async {}
188+
189+
Future<void> _importRuleCompletion(sass.StringExpression node,
190+
CompletionContext context, CompletionList result) async {}
191+
192+
void _stylesheetCompletion(
193+
CompletionContext context, CompletionList result) {}
194+
195+
void _commentCompletion(sass.SilentComment node, CompletionContext context,
196+
CompletionList result) {}
197+
198+
void _placeholderSelectorCompletion(sass.PlaceholderSelector node,
199+
CompletionContext context, CompletionList result) {}
200+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import 'package:lsp_server/lsp_server.dart' as lsp;
2+
3+
/// A mutable variant of [lsp.CompletionList].
4+
class CompletionList {
5+
bool isIncomplete;
6+
List<lsp.CompletionItem> items;
7+
lsp.CompletionListItemDefaults itemDefaults;
8+
9+
CompletionList({
10+
required this.isIncomplete,
11+
required this.itemDefaults,
12+
required this.items,
13+
});
14+
}

0 commit comments

Comments
 (0)