Skip to content

Commit 9d28005

Browse files
committed
Completions
1 parent 7cd086a commit 9d28005

File tree

8 files changed

+333
-94
lines changed

8 files changed

+333
-94
lines changed

pkgs/sass_language_server/lib/src/language_server.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ class LanguageServer {
376376

377377
var configuration = _getLanguageConfiguration(document);
378378
if (configuration.hover.enabled) {
379-
var result = await _ls.doHover(document, params.position);
379+
var result = await _ls.hover(document, params.position);
380380
return result ?? Hover(contents: Either2.t2(""));
381381
} else {
382382
return Hover(contents: Either2.t2(""));
Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
1+
import 'package:lsp_server/lsp_server.dart' as lsp;
2+
3+
import '../utils/sass_lsp_utils.dart';
14
import 'css_value.dart';
25
import 'entry_status.dart';
36
import 'reference.dart';
47

8+
final re = RegExp(r'([A-Z]+)(\d+)?');
9+
const browserNames = {
10+
"E": "Edge",
11+
"FF": "Firefox",
12+
"S": "Safari",
13+
"C": "Chrome",
14+
"IE": "IE",
15+
"O": "Opera",
16+
};
17+
518
class CssProperty {
619
final String name;
720
final String? description;
@@ -14,14 +27,65 @@ class CssProperty {
1427
final int? relevance;
1528
final String? atRule;
1629

17-
CssProperty(this.name,
18-
{this.description,
19-
this.browsers,
20-
this.restrictions,
21-
this.status,
22-
this.syntax,
23-
this.values,
24-
this.references,
25-
this.relevance,
26-
this.atRule});
30+
CssProperty(
31+
this.name, {
32+
this.description,
33+
this.browsers,
34+
this.restrictions,
35+
this.status,
36+
this.syntax,
37+
this.values,
38+
this.references,
39+
this.relevance,
40+
this.atRule,
41+
});
42+
43+
lsp.Either2<lsp.MarkupContent, String> getPlaintextDescription() {
44+
var browsersString = browsers?.map<String>((b) {
45+
var matches = re.firstMatch(b);
46+
if (matches != null) {
47+
var browser = matches.group(1);
48+
var version = matches.group(2);
49+
return "${browserNames[browser]} $version";
50+
}
51+
return b;
52+
}).join(', ');
53+
54+
var contents = asPlaintext('''
55+
$description
56+
57+
Syntax: $syntax
58+
59+
$browsersString
60+
''');
61+
return contents;
62+
}
63+
64+
lsp.Either2<lsp.MarkupContent, String> getMarkdownDescription() {
65+
var browsersString = browsers?.map<String>((b) {
66+
var matches = re.firstMatch(b);
67+
if (matches != null) {
68+
var browser = matches.group(1);
69+
var version = matches.group(2);
70+
return "${browserNames[browser]} $version";
71+
}
72+
return b;
73+
}).join(', ');
74+
75+
var referencesString = references
76+
?.map<String>((r) => '[${r.name}](${r.uri.toString()})')
77+
.join('\n');
78+
79+
var contents = asMarkdown('''
80+
$description
81+
82+
Syntax: $syntax
83+
84+
$referencesString
85+
86+
$browsersString
87+
'''
88+
.trim());
89+
return contents;
90+
}
2791
}

pkgs/sass_language_services/lib/src/features/completion/completion_feature.dart

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,28 @@
1+
import 'dart:math';
2+
13
import 'package:lsp_server/lsp_server.dart' as lsp;
24
import 'package:sass_api/sass_api.dart' as sass;
35
import 'package:sass_language_services/sass_language_services.dart';
6+
import 'package:sass_language_services/src/css/entry_status.dart';
47

8+
import '../../utils/sass_lsp_utils.dart';
59
import '../language_feature.dart';
610
import '../node_at_offset_visitor.dart';
711
import './completion_context.dart';
812
import './completion_list.dart';
913

14+
final triggerSuggestCommand = lsp.Command(
15+
title: 'Suggest',
16+
command: 'editor.action.triggerSuggest',
17+
);
18+
19+
// Sort string prefixes
20+
const enums = ' ';
21+
const normal = 'e';
22+
const vendorPrefix = 'o';
23+
const term = 'p';
24+
const variable = 'q';
25+
1026
class CompletionFeature extends LanguageFeature {
1127
CompletionFeature({required super.ls});
1228

@@ -47,7 +63,7 @@ class CompletionFeature extends LanguageFeature {
4763
lineBeforePosition: lineBeforePosition,
4864
);
4965

50-
for (var i = path.length; i >= 0; i--) {
66+
for (var i = path.length - 1; i >= 0; i--) {
5167
var node = path[i];
5268
if (node is sass.Declaration) {
5369
_declarationCompletion(node, context, result);
@@ -130,12 +146,9 @@ class CompletionFeature extends LanguageFeature {
130146

131147
// From offset, go back until hitting a newline
132148
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) {
149+
var linebreaks = '\n\r'.codeUnits;
150+
while (i >= 0 && !linebreaks.contains(text.codeUnitAt(i))) {
137151
i--;
138-
codeUnit = text.codeUnitAt(i);
139152
}
140153
var lineBeforePosition = text.substring(i + 1, offset);
141154

@@ -150,8 +163,63 @@ class CompletionFeature extends LanguageFeature {
150163
return (lineBeforePosition, currentWord);
151164
}
152165

153-
void _declarationCompletion(sass.Declaration node, CompletionContext context,
154-
CompletionList result) {}
166+
void _declarationCompletion(
167+
sass.AstNode node, CompletionContext context, CompletionList result) {
168+
for (var property in cssData.properties) {
169+
var range = context.defaultReplaceRange;
170+
var insertText = property.name;
171+
var triggerSuggest = false;
172+
173+
if (node is sass.Declaration) {
174+
range = toRange(node.name.span);
175+
if (!node.span.text.contains(':')) {
176+
insertText += ': ';
177+
triggerSuggest = true;
178+
}
179+
} else {
180+
insertText += ': ';
181+
triggerSuggest = true;
182+
}
183+
184+
var isDeprecated = property.status == EntryStatus.nonstandard ||
185+
property.status == EntryStatus.obsolete;
186+
187+
if (property.restrictions == null) {
188+
triggerSuggest = false;
189+
}
190+
191+
lsp.Command? command;
192+
if (context.configuration.triggerPropertyValueCompletion &&
193+
triggerSuggest) {
194+
command = triggerSuggestCommand;
195+
}
196+
197+
var relevance = 50;
198+
if (property.relevance case var rel?) {
199+
relevance = min(max(rel, 0), 99);
200+
}
201+
202+
var suffix = (255 - relevance).toRadixString(16);
203+
var prefix = insertText.startsWith('-') ? vendorPrefix : normal;
204+
var sortText = '${prefix}_$suffix';
205+
206+
var item = lsp.CompletionItem(
207+
label: property.name,
208+
documentation: supportsMarkdown()
209+
? property.getMarkdownDescription()
210+
: property.getPlaintextDescription(),
211+
tags: isDeprecated ? [lsp.CompletionItemTag.Deprecated] : [],
212+
textEdit: lsp.Either2.t2(
213+
lsp.TextEdit(range: range, newText: insertText),
214+
),
215+
insertTextFormat: lsp.InsertTextFormat.Snippet,
216+
sortText: sortText,
217+
kind: lsp.CompletionItemKind.Property,
218+
command: command,
219+
);
220+
result.items.add(item);
221+
}
222+
}
155223

156224
void _interpolationCompletion(sass.Interpolation node,
157225
CompletionContext context, CompletionList result) {}
@@ -166,7 +234,9 @@ class CompletionFeature extends LanguageFeature {
166234
CompletionContext context, CompletionList result) {}
167235

168236
void _styleRuleCompletion(
169-
sass.StyleRule node, CompletionContext context, CompletionList result) {}
237+
sass.StyleRule node, CompletionContext context, CompletionList result) {
238+
_declarationCompletion(node, context, result);
239+
}
170240

171241
void _variableDeclarationCompletion(sass.VariableDeclaration node,
172242
CompletionContext context, CompletionList result) {}

0 commit comments

Comments
 (0)