volar-service-css 0.0.30 → 0.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.d.ts +16 -5
  2. package/index.js +206 -105
  3. package/package.json +5 -5
package/index.d.ts CHANGED
@@ -1,9 +1,20 @@
1
- import type { ServicePlugin } from '@volar/language-service';
1
+ import type { Disposable, DocumentSelector, FormattingOptions, Result, ServiceContext, ServicePlugin } from '@volar/language-service';
2
2
  import * as css from 'vscode-css-languageservice';
3
- import type { TextDocument } from 'vscode-languageserver-textdocument';
3
+ import { TextDocument } from 'vscode-languageserver-textdocument';
4
4
  export interface Provide {
5
- 'css/stylesheet': (document: TextDocument) => css.Stylesheet | undefined;
6
- 'css/languageService': (languageId: string) => css.LanguageService | undefined;
5
+ 'css/stylesheet': (document: TextDocument, ls: css.LanguageService) => css.Stylesheet;
6
+ 'css/languageService': (document: TextDocument) => css.LanguageService | undefined;
7
7
  }
8
- export declare function create(): ServicePlugin;
8
+ export declare function create({ cssDocumentSelector, scssDocumentSelector, lessDocumentSelector, useDefaultDataProvider, getDocumentContext, isFormattingEnabled, getFormattingOptions, getLanguageSettings, getCustomData, onDidChangeCustomData, }?: {
9
+ cssDocumentSelector?: DocumentSelector;
10
+ scssDocumentSelector?: DocumentSelector;
11
+ lessDocumentSelector?: DocumentSelector;
12
+ useDefaultDataProvider?: boolean;
13
+ getDocumentContext?(context: ServiceContext): css.DocumentContext;
14
+ isFormattingEnabled?(document: TextDocument, context: ServiceContext): Result<boolean>;
15
+ getFormattingOptions?(document: TextDocument, options: FormattingOptions, context: ServiceContext): Result<css.CSSFormatConfiguration>;
16
+ getLanguageSettings?(document: TextDocument, context: ServiceContext): Result<css.LanguageSettings | undefined>;
17
+ getCustomData?(context: ServiceContext): Result<css.ICSSDataProvider[]>;
18
+ onDidChangeCustomData?(listener: () => void, context: ServiceContext): Disposable;
19
+ }): ServicePlugin;
9
20
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -2,70 +2,91 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.create = void 0;
4
4
  const css = require("vscode-css-languageservice");
5
+ const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
5
6
  const vscode_uri_1 = require("vscode-uri");
6
- function create() {
7
+ function create({ cssDocumentSelector = ['css'], scssDocumentSelector = ['scss'], lessDocumentSelector = ['less'], useDefaultDataProvider = true, getDocumentContext = context => {
8
+ return {
9
+ resolveReference(ref, base) {
10
+ if (ref.match(/^\w[\w\d+.-]*:/)) {
11
+ // starts with a schema
12
+ return ref;
13
+ }
14
+ if (ref[0] === '/') { // resolve absolute path against the current workspace folder
15
+ let folderUri = context.env.workspaceFolder;
16
+ if (!folderUri.endsWith('/')) {
17
+ folderUri += '/';
18
+ }
19
+ return folderUri + ref.substring(1);
20
+ }
21
+ const baseUri = vscode_uri_1.URI.parse(base);
22
+ const baseUriDir = baseUri.path.endsWith('/') ? baseUri : vscode_uri_1.Utils.dirname(baseUri);
23
+ return vscode_uri_1.Utils.resolvePath(baseUriDir, ref).toString(true);
24
+ },
25
+ };
26
+ }, isFormattingEnabled = async (document, context) => {
27
+ return await context.env.getConfiguration?.(document.languageId + '.format.enable') ?? true;
28
+ }, getFormattingOptions = async (document, options, context) => {
29
+ return {
30
+ ...options,
31
+ ...await context.env.getConfiguration?.(document.languageId + '.format'),
32
+ };
33
+ }, getLanguageSettings = async (document, context) => {
34
+ return await context.env.getConfiguration?.(document.languageId);
35
+ }, getCustomData = async (context) => {
36
+ const customData = await context.env.getConfiguration?.('css.customData') ?? [];
37
+ const newData = [];
38
+ for (const customDataPath of customData) {
39
+ const uri = vscode_uri_1.Utils.resolvePath(vscode_uri_1.URI.parse(context.env.workspaceFolder), customDataPath);
40
+ const json = await context.env.fs?.readFile?.(uri.toString());
41
+ if (json) {
42
+ try {
43
+ const data = JSON.parse(json);
44
+ newData.push(css.newCSSDataProvider(data));
45
+ }
46
+ catch (error) {
47
+ console.error(error);
48
+ }
49
+ }
50
+ }
51
+ return newData;
52
+ }, onDidChangeCustomData = (listener, context) => {
53
+ const disposable = context.env.onDidChangeConfiguration?.(listener);
54
+ return {
55
+ dispose() {
56
+ disposable?.dispose();
57
+ },
58
+ };
59
+ }, } = {}) {
7
60
  return {
8
61
  name: 'css',
9
62
  // https://github.com/microsoft/vscode/blob/09850876e652688fb142e2e19fd00fd38c0bc4ba/extensions/css-language-features/server/src/cssServer.ts#L97
10
63
  triggerCharacters: ['/', '-', ':'],
11
64
  create(context) {
12
- let inited = false;
13
65
  const stylesheets = new WeakMap();
14
66
  const fileSystemProvider = {
15
- stat: async (uri) => await context.env.fs?.stat(uri) ?? {
16
- type: css.FileType.Unknown,
17
- ctime: 0,
18
- mtime: 0,
19
- size: 0,
20
- },
67
+ stat: async (uri) => await context.env.fs?.stat(uri)
68
+ ?? { type: css.FileType.Unknown, ctime: 0, mtime: 0, size: 0 },
21
69
  readDirectory: async (uri) => await context.env.fs?.readDirectory(uri) ?? [],
22
70
  };
23
- const documentContext = {
24
- resolveReference(ref, base) {
25
- if (ref.match(/^\w[\w\d+.-]*:/)) {
26
- // starts with a schema
27
- return ref;
28
- }
29
- if (ref[0] === '/') { // resolve absolute path against the current workspace folder
30
- return base + ref;
31
- }
32
- const baseUri = vscode_uri_1.URI.parse(base);
33
- const baseUriDir = baseUri.path.endsWith('/') ? baseUri : vscode_uri_1.Utils.dirname(baseUri);
34
- return vscode_uri_1.Utils.resolvePath(baseUriDir, ref).toString(true);
35
- },
36
- };
37
- const cssLs = css.getCSSLanguageService({
38
- fileSystemProvider,
39
- clientCapabilities: context.env.clientCapabilities,
40
- });
41
- const scssLs = css.getSCSSLanguageService({
42
- fileSystemProvider,
43
- clientCapabilities: context.env.clientCapabilities,
44
- });
45
- const lessLs = css.getLESSLanguageService({
46
- fileSystemProvider,
47
- clientCapabilities: context.env.clientCapabilities,
48
- });
49
- const postcssLs = {
50
- ...scssLs,
51
- doValidation: (document, stylesheet, documentSettings) => {
52
- let errors = scssLs.doValidation(document, stylesheet, documentSettings);
53
- errors = errors.filter(error => error.code !== 'css-semicolonexpected');
54
- errors = errors.filter(error => error.code !== 'css-ruleorselectorexpected');
55
- errors = errors.filter(error => error.code !== 'unknownAtRules');
56
- return errors;
57
- },
58
- };
71
+ const documentContext = getDocumentContext(context);
72
+ const disposable = onDidChangeCustomData(() => initializing = undefined, context);
73
+ let cssLs;
74
+ let scssLs;
75
+ let lessLs;
76
+ let customData = [];
77
+ let initializing;
59
78
  return {
79
+ dispose() {
80
+ disposable.dispose();
81
+ },
60
82
  provide: {
61
83
  'css/stylesheet': getStylesheet,
62
84
  'css/languageService': getCssLs,
63
85
  },
64
86
  async provideCompletionItems(document, position) {
65
87
  return worker(document, async (stylesheet, cssLs) => {
66
- const settings = await context.env.getConfiguration?.(document.languageId);
67
- const cssResult = await cssLs.doComplete2(document, position, stylesheet, documentContext, settings?.completion);
68
- return cssResult;
88
+ const settings = await getLanguageSettings(document, context);
89
+ return await cssLs.doComplete2(document, position, stylesheet, documentContext, settings?.completion);
69
90
  });
70
91
  },
71
92
  provideRenameRange(document, position) {
@@ -97,13 +118,13 @@ function create() {
97
118
  },
98
119
  async provideDiagnostics(document) {
99
120
  return worker(document, async (stylesheet, cssLs) => {
100
- const settings = await context.env.getConfiguration?.(document.languageId);
121
+ const settings = await getLanguageSettings(document, context);
101
122
  return cssLs.doValidation(document, stylesheet, settings);
102
123
  });
103
124
  },
104
125
  async provideHover(document, position) {
105
126
  return worker(document, async (stylesheet, cssLs) => {
106
- const settings = await context.env.getConfiguration?.(document.languageId);
127
+ const settings = await getLanguageSettings(document, context);
107
128
  return cssLs.doHover(document, position, stylesheet, settings?.hover);
108
129
  });
109
130
  },
@@ -138,8 +159,8 @@ function create() {
138
159
  });
139
160
  },
140
161
  provideFoldingRanges(document) {
141
- return worker(document, (stylesheet, cssLs) => {
142
- return cssLs.getFoldingRanges(document, stylesheet);
162
+ return worker(document, (_stylesheet, cssLs) => {
163
+ return cssLs.getFoldingRanges(document, context.env.clientCapabilities?.textDocument?.foldingRange);
143
164
  });
144
165
  },
145
166
  provideSelectionRanges(document, positions) {
@@ -147,59 +168,138 @@ function create() {
147
168
  return cssLs.getSelectionRanges(document, positions, stylesheet);
148
169
  });
149
170
  },
150
- async provideDocumentFormattingEdits(document, formatRange, options) {
171
+ async provideDocumentFormattingEdits(document, formatRange, options, codeOptions) {
151
172
  return worker(document, async (_stylesheet, cssLs) => {
152
- const options_2 = await context.env.getConfiguration?.(document.languageId + '.format');
153
- if (options_2?.enable === false) {
173
+ if (!await isFormattingEnabled(document, context)) {
154
174
  return;
155
175
  }
156
- return cssLs.format(document, formatRange, {
157
- ...options_2,
158
- ...options,
159
- });
176
+ const formatOptions = await getFormattingOptions(document, options, context);
177
+ let formatDocument = document;
178
+ let prefixes = [];
179
+ let suffixes = [];
180
+ if (codeOptions?.initialIndentLevel) {
181
+ for (let i = 0; i < codeOptions.initialIndentLevel; i++) {
182
+ if (i === codeOptions.initialIndentLevel - 1) {
183
+ prefixes.push('_', '{');
184
+ suffixes.unshift('}');
185
+ }
186
+ else {
187
+ prefixes.push('_', '{\n');
188
+ suffixes.unshift('\n}');
189
+ }
190
+ }
191
+ formatDocument = vscode_languageserver_textdocument_1.TextDocument.create(document.uri, document.languageId, document.version, prefixes.join('') + document.getText() + suffixes.join(''));
192
+ formatRange = {
193
+ start: formatDocument.positionAt(0),
194
+ end: formatDocument.positionAt(formatDocument.getText().length),
195
+ };
196
+ }
197
+ let edits = cssLs.format(formatDocument, formatRange, formatOptions);
198
+ if (codeOptions) {
199
+ let newText = vscode_languageserver_textdocument_1.TextDocument.applyEdits(formatDocument, edits);
200
+ for (const prefix of prefixes) {
201
+ newText = newText.trimStart().slice(prefix.trim().length);
202
+ }
203
+ for (const suffix of suffixes.reverse()) {
204
+ newText = newText.trimEnd().slice(0, -suffix.trim().length);
205
+ }
206
+ if (!codeOptions.initialIndentLevel && codeOptions.level > 0) {
207
+ newText = ensureNewLines(newText);
208
+ }
209
+ edits = [{
210
+ range: {
211
+ start: document.positionAt(0),
212
+ end: document.positionAt(document.getText().length),
213
+ },
214
+ newText,
215
+ }];
216
+ }
217
+ return edits;
218
+ function ensureNewLines(newText) {
219
+ const verifyDocument = vscode_languageserver_textdocument_1.TextDocument.create(document.uri, document.languageId, document.version, '_ {' + newText + '}');
220
+ const verifyEdits = cssLs.format(verifyDocument, undefined, formatOptions);
221
+ let verifyText = vscode_languageserver_textdocument_1.TextDocument.applyEdits(verifyDocument, verifyEdits);
222
+ verifyText = verifyText.trimStart().slice('_'.length);
223
+ verifyText = verifyText.trim().slice('{'.length, -'}'.length);
224
+ if (startWithNewLine(verifyText) !== startWithNewLine(newText)) {
225
+ if (startWithNewLine(verifyText)) {
226
+ newText = '\n' + newText;
227
+ }
228
+ else if (newText.startsWith('\n')) {
229
+ newText = newText.slice(1);
230
+ }
231
+ else if (newText.startsWith('\r\n')) {
232
+ newText = newText.slice(2);
233
+ }
234
+ }
235
+ if (endWithNewLine(verifyText) !== endWithNewLine(newText)) {
236
+ if (endWithNewLine(verifyText)) {
237
+ newText = newText + '\n';
238
+ }
239
+ else if (newText.endsWith('\n')) {
240
+ newText = newText.slice(0, -1);
241
+ }
242
+ else if (newText.endsWith('\r\n')) {
243
+ newText = newText.slice(0, -2);
244
+ }
245
+ }
246
+ return newText;
247
+ }
248
+ function startWithNewLine(text) {
249
+ return text.startsWith('\n') || text.startsWith('\r\n');
250
+ }
251
+ function endWithNewLine(text) {
252
+ return text.endsWith('\n') || text.endsWith('\r\n');
253
+ }
160
254
  });
161
255
  },
162
256
  };
163
- async function initCustomData() {
164
- if (!inited) {
165
- context.env.onDidChangeConfiguration?.(async () => {
166
- const customData = await getCustomData();
167
- cssLs.setDataProviders(true, customData);
168
- scssLs.setDataProviders(true, customData);
169
- lessLs.setDataProviders(true, customData);
170
- });
171
- const customData = await getCustomData();
172
- cssLs.setDataProviders(true, customData);
173
- scssLs.setDataProviders(true, customData);
174
- lessLs.setDataProviders(true, customData);
175
- inited = true;
257
+ function getCssLs(document) {
258
+ if (matchDocument(cssDocumentSelector, document)) {
259
+ if (!cssLs) {
260
+ cssLs = css.getCSSLanguageService({
261
+ fileSystemProvider,
262
+ clientCapabilities: context.env.clientCapabilities,
263
+ useDefaultDataProvider,
264
+ customDataProviders: customData,
265
+ });
266
+ cssLs.setDataProviders(useDefaultDataProvider, customData);
267
+ }
268
+ return cssLs;
176
269
  }
177
- }
178
- async function getCustomData() {
179
- const customData = await context.env.getConfiguration?.('css.customData') ?? [];
180
- const newData = [];
181
- for (const customDataPath of customData) {
182
- try {
183
- const pathModuleName = 'path'; // avoid bundle
184
- const { posix: path } = require(pathModuleName);
185
- const jsonPath = path.resolve(customDataPath);
186
- newData.push(css.newCSSDataProvider(require(jsonPath)));
270
+ else if (matchDocument(scssDocumentSelector, document)) {
271
+ if (!scssLs) {
272
+ scssLs = css.getSCSSLanguageService({
273
+ fileSystemProvider,
274
+ clientCapabilities: context.env.clientCapabilities,
275
+ useDefaultDataProvider,
276
+ customDataProviders: customData,
277
+ });
278
+ scssLs.setDataProviders(useDefaultDataProvider, customData);
187
279
  }
188
- catch (error) {
189
- console.error(error);
280
+ return scssLs;
281
+ }
282
+ else if (matchDocument(lessDocumentSelector, document)) {
283
+ if (!lessLs) {
284
+ lessLs = css.getLESSLanguageService({
285
+ fileSystemProvider,
286
+ clientCapabilities: context.env.clientCapabilities,
287
+ useDefaultDataProvider,
288
+ customDataProviders: customData,
289
+ });
290
+ lessLs.setDataProviders(useDefaultDataProvider, customData);
190
291
  }
292
+ return lessLs;
191
293
  }
192
- return newData;
193
294
  }
194
- function getCssLs(lang) {
195
- switch (lang) {
196
- case 'css': return cssLs;
197
- case 'scss': return scssLs;
198
- case 'less': return lessLs;
199
- case 'postcss': return postcssLs;
200
- }
295
+ async function worker(document, callback) {
296
+ const cssLs = getCssLs(document);
297
+ if (!cssLs)
298
+ return;
299
+ await (initializing ??= initialize());
300
+ return callback(getStylesheet(document, cssLs), cssLs);
201
301
  }
202
- function getStylesheet(document) {
302
+ function getStylesheet(document, ls) {
203
303
  const cache = stylesheets.get(document);
204
304
  if (cache) {
205
305
  const [cacheVersion, cacheStylesheet] = cache;
@@ -207,25 +307,26 @@ function create() {
207
307
  return cacheStylesheet;
208
308
  }
209
309
  }
210
- const cssLs = getCssLs(document.languageId);
211
- if (!cssLs)
212
- return;
213
- const stylesheet = cssLs.parseStylesheet(document);
310
+ const stylesheet = ls.parseStylesheet(document);
214
311
  stylesheets.set(document, [document.version, stylesheet]);
215
312
  return stylesheet;
216
313
  }
217
- async function worker(document, callback) {
218
- const stylesheet = getStylesheet(document);
219
- if (!stylesheet)
220
- return;
221
- const cssLs = getCssLs(document.languageId);
222
- if (!cssLs)
223
- return;
224
- await initCustomData();
225
- return callback(stylesheet, cssLs);
314
+ async function initialize() {
315
+ customData = await getCustomData(context);
316
+ cssLs?.setDataProviders(useDefaultDataProvider, customData);
317
+ scssLs?.setDataProviders(useDefaultDataProvider, customData);
318
+ lessLs?.setDataProviders(useDefaultDataProvider, customData);
226
319
  }
227
320
  },
228
321
  };
229
322
  }
230
323
  exports.create = create;
324
+ function matchDocument(selector, document) {
325
+ for (const sel of selector) {
326
+ if (sel === document.languageId || (typeof sel === 'object' && sel.language === document.languageId)) {
327
+ return true;
328
+ }
329
+ }
330
+ return false;
331
+ }
231
332
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "volar-service-css",
3
- "version": "0.0.30",
3
+ "version": "0.0.32",
4
4
  "description": "Integrate vscode-css-languageservice into Volar",
5
5
  "homepage": "https://github.com/volarjs/services/tree/master/packages/css",
6
6
  "bugs": "https://github.com/volarjs/services/issues",
@@ -25,19 +25,19 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "vscode-css-languageservice": "^6.2.10",
28
+ "vscode-languageserver-textdocument": "^1.0.11",
28
29
  "vscode-uri": "^3.0.8"
29
30
  },
30
31
  "devDependencies": {
31
- "@types/node": "latest",
32
- "vscode-languageserver-textdocument": "^1.0.11"
32
+ "@types/node": "latest"
33
33
  },
34
34
  "peerDependencies": {
35
- "@volar/language-service": "~2.0.1"
35
+ "@volar/language-service": "~2.1.0"
36
36
  },
37
37
  "peerDependenciesMeta": {
38
38
  "@volar/language-service": {
39
39
  "optional": true
40
40
  }
41
41
  },
42
- "gitHead": "30c3cc3c76e90f75f14fe0c2fa4fd33b7ff06507"
42
+ "gitHead": "717049e7dcd5c30f451f6db8eb71eaba43f74c83"
43
43
  }