Skip to content

Commit ee62776

Browse files
committed
Configuration, context
1 parent 68be7f6 commit ee62776

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
@@ -7,6 +7,25 @@ class FeatureConfiguration {
77
FeatureConfiguration({required this.enabled});
88
}
99

10+
enum MixinStyle { all, noBracket, bracket }
11+
12+
class CompletionConfiguration extends FeatureConfiguration {
13+
final bool completePropertyWithSemicolon;
14+
final bool css;
15+
final MixinStyle mixinStyle;
16+
final bool suggestFromUseOnly;
17+
final bool triggerPropertyValueCompletion;
18+
19+
CompletionConfiguration({
20+
required super.enabled,
21+
required this.completePropertyWithSemicolon,
22+
required this.css,
23+
required this.mixinStyle,
24+
required this.suggestFromUseOnly,
25+
required this.triggerPropertyValueCompletion,
26+
});
27+
}
28+
1029
class HoverConfiguration extends FeatureConfiguration {
1130
final bool documentation;
1231
final bool references;
@@ -25,6 +44,7 @@ class HoverConfiguration extends FeatureConfiguration {
2544
/// options to turn off features that cause duplicates or other
2645
/// interoperability errors.
2746
class LanguageConfiguration {
47+
late final CompletionConfiguration completion;
2848
late final FeatureConfiguration definition;
2949
late final FeatureConfiguration documentSymbols;
3050
late final FeatureConfiguration documentLinks;
@@ -37,6 +57,20 @@ class LanguageConfiguration {
3757
late final FeatureConfiguration workspaceSymbols;
3858

3959
LanguageConfiguration.from(dynamic config) {
60+
completion = CompletionConfiguration(
61+
enabled: config?['completion']?['enabled'] as bool? ?? true,
62+
completePropertyWithSemicolon:
63+
config?['completion']?['completePropertyWithSemicolon'] as bool? ??
64+
true,
65+
css: config?['completion']?['css'] as bool? ?? true,
66+
mixinStyle: _toMixinStyle(config?['completion']?['mixinStyle']),
67+
suggestFromUseOnly:
68+
config?['completion']?['suggestFromUseOnly'] as bool? ?? true,
69+
triggerPropertyValueCompletion:
70+
config?['completion']?['triggerPropertyValueCompletion'] as bool? ??
71+
true,
72+
);
73+
4074
definition = FeatureConfiguration(
4175
enabled: config?['definition']?['enabled'] as bool? ?? true);
4276
documentSymbols = FeatureConfiguration(
@@ -63,4 +97,17 @@ class LanguageConfiguration {
6397
workspaceSymbols = FeatureConfiguration(
6498
enabled: config?['workspaceSymbols']?['enabled'] as bool? ?? true);
6599
}
100+
101+
MixinStyle _toMixinStyle(dynamic style) {
102+
var styleString = style as String? ?? 'all';
103+
switch (styleString) {
104+
case 'nobracket':
105+
return MixinStyle.noBracket;
106+
case 'bracket':
107+
return MixinStyle.bracket;
108+
case 'all':
109+
default:
110+
return MixinStyle.all;
111+
}
112+
}
66113
}
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)