volar-service-html 0.0.29 → 0.0.31

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 +13 -6
  2. package/index.js +192 -137
  3. package/package.json +5 -5
package/index.d.ts CHANGED
@@ -1,16 +1,23 @@
1
- import type { ServicePlugin } from '@volar/language-service';
1
+ import type { Disposable, DocumentSelector, Result, ServiceContext, ServicePlugin } from '@volar/language-service';
2
2
  import * as html from 'vscode-html-languageservice';
3
- import type { TextDocument } from 'vscode-languageserver-textdocument';
3
+ import { TextDocument } from 'vscode-languageserver-textdocument';
4
4
  export interface Provide {
5
5
  'html/htmlDocument': (document: TextDocument) => html.HTMLDocument | undefined;
6
6
  'html/languageService': () => html.LanguageService;
7
7
  'html/documentContext': () => html.DocumentContext;
8
- 'html/updateCustomData': (extraData: html.IHTMLDataProvider[]) => void;
9
8
  }
10
- export declare function getHtmlDocument(document: TextDocument): html.HTMLDocument;
11
- export declare function create({ languageId, useDefaultDataProvider, useCustomDataProviders, }?: {
12
- languageId?: string;
9
+ export declare function create({ documentSelector, useDefaultDataProvider, getDocumentContext, isFormattingEnabled, isAutoCreateQuotesEnabled, isAutoClosingTagsEnabled, getFormattingOptions, getCompletionConfiguration, getHoverSettings, getCustomData, onDidChangeCustomData, }?: {
10
+ documentSelector?: DocumentSelector;
13
11
  useDefaultDataProvider?: boolean;
14
12
  useCustomDataProviders?: boolean;
13
+ isFormattingEnabled?(document: TextDocument, context: ServiceContext): Result<boolean>;
14
+ isAutoCreateQuotesEnabled?(document: TextDocument, context: ServiceContext): Result<boolean>;
15
+ isAutoClosingTagsEnabled?(document: TextDocument, context: ServiceContext): Result<boolean>;
16
+ getDocumentContext?(context: ServiceContext): html.DocumentContext;
17
+ getFormattingOptions?(document: TextDocument, context: ServiceContext): Result<html.HTMLFormatConfiguration | undefined>;
18
+ getCompletionConfiguration?(document: TextDocument, context: ServiceContext): Result<html.CompletionConfiguration | undefined>;
19
+ getHoverSettings?(document: TextDocument, context: ServiceContext): Result<html.HoverSettings | undefined>;
20
+ getCustomData?(context: ServiceContext): Result<html.IHTMLDataProvider[]>;
21
+ onDidChangeCustomData?(listener: () => void, context: ServiceContext): Disposable;
15
22
  }): ServicePlugin;
16
23
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -1,76 +1,100 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.create = exports.getHtmlDocument = void 0;
3
+ exports.create = void 0;
4
4
  const html = require("vscode-html-languageservice");
5
+ const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
5
6
  const vscode_uri_1 = require("vscode-uri");
6
- const parserLs = html.getLanguageService();
7
- const htmlDocuments = new WeakMap();
8
- function getHtmlDocument(document) {
9
- const cache = htmlDocuments.get(document);
10
- if (cache) {
11
- const [cacheVersion, cacheDoc] = cache;
12
- if (cacheVersion === document.version) {
13
- return cacheDoc;
7
+ function create({ documentSelector = ['html'], 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?.('html.format.enable') ?? true;
28
+ }, isAutoCreateQuotesEnabled = async (_document, context) => {
29
+ return await context.env.getConfiguration?.('html.autoCreateQuotes') ?? true;
30
+ }, isAutoClosingTagsEnabled = async (_document, context) => {
31
+ return await context.env.getConfiguration?.('html.autoClosingTags') ?? true;
32
+ }, getFormattingOptions = async (_document, context) => {
33
+ return await context.env.getConfiguration?.('html.format');
34
+ }, getCompletionConfiguration = async (_document, context) => {
35
+ return await context.env.getConfiguration?.('html.completion');
36
+ }, getHoverSettings = async (_document, context) => {
37
+ return await context.env.getConfiguration?.('html.hover');
38
+ }, getCustomData = async (context) => {
39
+ const customData = await context.env.getConfiguration?.('html.customData') ?? [];
40
+ const newData = [];
41
+ for (const customDataPath of customData) {
42
+ const uri = vscode_uri_1.Utils.resolvePath(vscode_uri_1.URI.parse(context.env.workspaceFolder), customDataPath);
43
+ const json = await context.env.fs?.readFile?.(uri.toString());
44
+ if (json) {
45
+ try {
46
+ const data = JSON.parse(json);
47
+ newData.push(html.newHTMLDataProvider(customDataPath, data));
48
+ }
49
+ catch (error) {
50
+ console.error(error);
51
+ }
14
52
  }
15
53
  }
16
- const doc = parserLs.parseHTMLDocument(document);
17
- htmlDocuments.set(document, [document.version, doc]);
18
- return doc;
19
- }
20
- exports.getHtmlDocument = getHtmlDocument;
21
- function create({ languageId = 'html', useDefaultDataProvider = true, useCustomDataProviders = true, } = {}) {
54
+ return newData;
55
+ }, onDidChangeCustomData = (listener, context) => {
56
+ const disposable = context.env.onDidChangeConfiguration?.(listener);
57
+ return {
58
+ dispose() {
59
+ disposable?.dispose();
60
+ },
61
+ };
62
+ }, } = {}) {
22
63
  return {
23
64
  name: 'html',
24
65
  // https://github.com/microsoft/vscode/blob/09850876e652688fb142e2e19fd00fd38c0bc4ba/extensions/html-language-features/server/src/htmlServer.ts#L183
25
66
  triggerCharacters: ['.', ':', '<', '"', '=', '/'],
26
67
  create(context) {
27
- let shouldUpdateCustomData = true;
28
- let customData = [];
29
- let extraData = [];
68
+ const htmlDocuments = new WeakMap();
30
69
  const fileSystemProvider = {
31
- stat: async (uri) => await context.env.fs?.stat(uri) ?? {
32
- type: html.FileType.Unknown,
33
- ctime: 0,
34
- mtime: 0,
35
- size: 0,
36
- },
70
+ stat: async (uri) => await context.env.fs?.stat(uri)
71
+ ?? { type: html.FileType.Unknown, ctime: 0, mtime: 0, size: 0 },
37
72
  readDirectory: async (uri) => context.env.fs?.readDirectory(uri) ?? [],
38
73
  };
39
- const documentContext = {
40
- resolveReference(ref, base) {
41
- if (ref.match(/^\w[\w\d+.-]*:/)) {
42
- // starts with a schema
43
- return ref;
44
- }
45
- if (ref[0] === '/') { // resolve absolute path against the current workspace folder
46
- return base + ref;
47
- }
48
- const baseUri = vscode_uri_1.URI.parse(base);
49
- const baseUriDir = baseUri.path.endsWith('/') ? baseUri : vscode_uri_1.Utils.dirname(baseUri);
50
- return vscode_uri_1.Utils.resolvePath(baseUriDir, ref).toString(true);
51
- },
52
- };
74
+ const documentContext = getDocumentContext(context);
53
75
  const htmlLs = html.getLanguageService({
54
76
  fileSystemProvider,
55
77
  clientCapabilities: context.env.clientCapabilities,
78
+ useDefaultDataProvider,
56
79
  });
57
- context.env.onDidChangeConfiguration?.(() => {
58
- shouldUpdateCustomData = true;
59
- });
80
+ const disposable = onDidChangeCustomData(() => initializing = undefined, context);
81
+ let initializing;
60
82
  return {
83
+ dispose() {
84
+ disposable.dispose();
85
+ },
61
86
  provide: {
62
87
  'html/htmlDocument': (document) => {
63
- if (document.languageId === languageId) {
88
+ if (matchDocument(documentSelector, document)) {
64
89
  return getHtmlDocument(document);
65
90
  }
66
91
  },
67
92
  'html/languageService': () => htmlLs,
68
93
  'html/documentContext': () => documentContext,
69
- 'html/updateCustomData': updateExtraCustomData,
70
94
  },
71
95
  async provideCompletionItems(document, position) {
72
96
  return worker(document, async (htmlDocument) => {
73
- const configs = await context.env.getConfiguration?.('html.completion');
97
+ const configs = await getCompletionConfiguration(document, context);
74
98
  return htmlLs.doComplete2(document, position, htmlDocument, documentContext, configs);
75
99
  });
76
100
  },
@@ -90,7 +114,7 @@ function create({ languageId = 'html', useDefaultDataProvider = true, useCustomD
90
114
  },
91
115
  async provideHover(document, position) {
92
116
  return worker(document, async (htmlDocument) => {
93
- const hoverSettings = await context.env.getConfiguration?.('html.hover');
117
+ const hoverSettings = await getHoverSettings(document, context);
94
118
  return htmlLs.doHover(document, position, htmlDocument, hoverSettings);
95
119
  });
96
120
  },
@@ -111,7 +135,7 @@ function create({ languageId = 'html', useDefaultDataProvider = true, useCustomD
111
135
  },
112
136
  provideFoldingRanges(document) {
113
137
  return worker(document, () => {
114
- return htmlLs.getFoldingRanges(document);
138
+ return htmlLs.getFoldingRanges(document, context.env.clientCapabilities?.textDocument?.foldingRange);
115
139
  });
116
140
  },
117
141
  provideSelectionRanges(document, positions) {
@@ -119,81 +143,114 @@ function create({ languageId = 'html', useDefaultDataProvider = true, useCustomD
119
143
  return htmlLs.getSelectionRanges(document, positions);
120
144
  });
121
145
  },
122
- async provideDocumentFormattingEdits(document, formatRange, options) {
146
+ async provideDocumentFormattingEdits(document, formatRange, options, codeOptions) {
123
147
  return worker(document, async () => {
124
- const options_2 = await context.env.getConfiguration?.('html.format');
125
- if (options_2?.enable === false) {
148
+ if (!await isFormattingEnabled(document, context)) {
126
149
  return;
127
150
  }
128
- { // https://github.com/microsoft/vscode/blob/dce493cb6e36346ef2714e82c42ce14fc461b15c/extensions/html-language-features/server/src/modes/formatting.ts#L13-L23
129
- const endPos = formatRange.end;
130
- let endOffset = document.offsetAt(endPos);
131
- const content = document.getText();
132
- if (endPos.character === 0 && endPos.line > 0 && endOffset !== content.length) {
133
- // if selection ends after a new line, exclude that new line
134
- const prevLineStart = document.offsetAt({ line: endPos.line - 1, character: 0 });
135
- while (isEOL(content, endOffset - 1) && endOffset > prevLineStart) {
136
- endOffset--;
137
- }
138
- formatRange = {
139
- start: formatRange.start,
140
- end: document.positionAt(endOffset),
141
- };
151
+ // https://github.com/microsoft/vscode/blob/dce493cb6e36346ef2714e82c42ce14fc461b15c/extensions/html-language-features/server/src/modes/formatting.ts#L13-L23
152
+ const endPos = formatRange.end;
153
+ let endOffset = document.offsetAt(endPos);
154
+ const content = document.getText();
155
+ if (endPos.character === 0 && endPos.line > 0 && endOffset !== content.length) {
156
+ // if selection ends after a new line, exclude that new line
157
+ const prevLineStart = document.offsetAt({ line: endPos.line - 1, character: 0 });
158
+ while (isEOL(content, endOffset - 1) && endOffset > prevLineStart) {
159
+ endOffset--;
142
160
  }
161
+ formatRange = {
162
+ start: formatRange.start,
163
+ end: document.positionAt(endOffset),
164
+ };
143
165
  }
144
- return htmlLs.format(document, formatRange, {
145
- ...options_2,
166
+ const formatSettings = {
146
167
  ...options,
147
- });
148
- });
149
- },
150
- provideFormattingIndentSensitiveLines(document) {
151
- return worker(document, (htmlDocument) => {
152
- const lines = [];
153
- /**
154
- * comments
155
- */
156
- const scanner = htmlLs.createScanner(document.getText());
157
- let token = scanner.scan();
158
- let startCommentTagLine;
159
- while (token !== html.TokenType.EOS) {
160
- if (token === html.TokenType.StartCommentTag) {
161
- startCommentTagLine = document.positionAt(scanner.getTokenOffset()).line;
162
- }
163
- else if (token === html.TokenType.EndCommentTag) {
164
- const line = document.positionAt(scanner.getTokenOffset()).line;
165
- for (let i = startCommentTagLine + 1; i <= line; i++) {
166
- lines.push(i);
168
+ endWithNewline: options.insertFinalNewline ? true : options.trimFinalNewlines ? false : undefined,
169
+ ...await getFormattingOptions(document, context),
170
+ };
171
+ // https://github.com/microsoft/vscode/blob/a8f73340be02966c3816a2f23cb7e446a3a7cb9b/extensions/html-language-features/server/src/modes/htmlMode.ts#L47-L51
172
+ if (formatSettings.contentUnformatted) {
173
+ formatSettings.contentUnformatted = formatSettings.contentUnformatted + ',script';
174
+ }
175
+ else {
176
+ formatSettings.contentUnformatted = 'script';
177
+ }
178
+ let formatDocument = document;
179
+ let prefixes = [];
180
+ let suffixes = [];
181
+ if (codeOptions?.initialIndentLevel) {
182
+ for (let i = 0; i < codeOptions.initialIndentLevel; i++) {
183
+ if (i === codeOptions.initialIndentLevel - 1) {
184
+ prefixes.push('<template>');
185
+ suffixes.unshift('</template>');
167
186
  }
168
- startCommentTagLine = undefined;
169
- }
170
- else if (token === html.TokenType.AttributeValue) {
171
- const startLine = document.positionAt(scanner.getTokenOffset()).line;
172
- for (let i = 1; i < scanner.getTokenText().split('\n').length; i++) {
173
- lines.push(startLine + i);
187
+ else {
188
+ prefixes.push('<template>\n');
189
+ suffixes.unshift('\n</template>');
174
190
  }
175
191
  }
176
- token = scanner.scan();
192
+ formatDocument = vscode_languageserver_textdocument_1.TextDocument.create(document.uri, document.languageId, document.version, prefixes.join('') + document.getText() + suffixes.join(''));
193
+ formatRange = {
194
+ start: formatDocument.positionAt(0),
195
+ end: formatDocument.positionAt(formatDocument.getText().length),
196
+ };
197
+ }
198
+ let edits = htmlLs.format(formatDocument, formatRange, formatSettings);
199
+ if (codeOptions) {
200
+ let newText = vscode_languageserver_textdocument_1.TextDocument.applyEdits(formatDocument, edits);
201
+ for (const prefix of prefixes) {
202
+ newText = newText.trimStart().slice(prefix.trim().length);
203
+ }
204
+ for (const suffix of suffixes.reverse()) {
205
+ newText = newText.trimEnd().slice(0, -suffix.trim().length);
206
+ }
207
+ if (!codeOptions.initialIndentLevel && codeOptions.level > 0) {
208
+ newText = ensureNewLines(newText);
209
+ }
210
+ edits = [{
211
+ range: {
212
+ start: document.positionAt(0),
213
+ end: document.positionAt(document.getText().length),
214
+ },
215
+ newText,
216
+ }];
177
217
  }
178
- /**
179
- * tags
180
- */
181
- // https://github.com/beautify-web/js-beautify/blob/686f8c1b265990908ece86ce39291733c75c997c/js/src/html/options.js#L81
182
- const indentSensitiveTags = new Set(['pre', 'textarea']);
183
- htmlDocument.roots.forEach(function visit(node) {
184
- if (node.tag !== undefined
185
- && node.startTagEnd !== undefined
186
- && node.endTagStart !== undefined
187
- && indentSensitiveTags.has(node.tag)) {
188
- for (let i = document.positionAt(node.startTagEnd).line + 1; i <= document.positionAt(node.endTagStart).line; i++) {
189
- lines.push(i);
218
+ return edits;
219
+ function ensureNewLines(newText) {
220
+ const verifyDocument = vscode_languageserver_textdocument_1.TextDocument.create(document.uri, document.languageId, document.version, '<template>' + newText + '</template>');
221
+ const verifyEdits = htmlLs.format(verifyDocument, undefined, formatSettings);
222
+ let verifyText = vscode_languageserver_textdocument_1.TextDocument.applyEdits(verifyDocument, verifyEdits);
223
+ verifyText = verifyText.trim().slice('<template>'.length, -'</template>'.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);
190
233
  }
191
234
  }
192
- else {
193
- node.children.forEach(visit);
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
+ }
194
245
  }
195
- });
196
- return lines;
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
+ }
197
254
  });
198
255
  },
199
256
  provideLinkedEditingRanges(document, position) {
@@ -210,16 +267,17 @@ function create({ languageId = 'html', useDefaultDataProvider = true, useCustomD
210
267
  const rangeLengthIsZero = lastChange.range.start.line === lastChange.range.end.line
211
268
  && lastChange.range.start.character === lastChange.range.end.character;
212
269
  if (rangeLengthIsZero && lastCharacter === '=') {
213
- const enabled = (await context.env.getConfiguration?.('html.autoCreateQuotes')) ?? true;
270
+ const enabled = await isAutoCreateQuotesEnabled(document, context);
214
271
  if (enabled) {
215
- const text = htmlLs.doQuoteComplete(document, position, htmlDocument, await context.env.getConfiguration?.('html.completion'));
272
+ const completionConfiguration = await getCompletionConfiguration(document, context);
273
+ const text = htmlLs.doQuoteComplete(document, position, htmlDocument, completionConfiguration);
216
274
  if (text) {
217
275
  return text;
218
276
  }
219
277
  }
220
278
  }
221
279
  if (rangeLengthIsZero && (lastCharacter === '>' || lastCharacter === '/')) {
222
- const enabled = (await context.env.getConfiguration?.('html.autoClosingTags')) ?? true;
280
+ const enabled = await isAutoClosingTagsEnabled(document, context);
223
281
  if (enabled) {
224
282
  const text = htmlLs.doTagComplete(document, position, htmlDocument);
225
283
  if (text) {
@@ -230,42 +288,31 @@ function create({ languageId = 'html', useDefaultDataProvider = true, useCustomD
230
288
  });
231
289
  },
232
290
  };
233
- async function initCustomData() {
234
- if (shouldUpdateCustomData && useCustomDataProviders) {
235
- shouldUpdateCustomData = false;
236
- customData = await getCustomData();
237
- htmlLs.setDataProviders(useDefaultDataProvider, [...customData, ...extraData]);
238
- }
239
- }
240
- function updateExtraCustomData(data) {
241
- extraData = data;
242
- htmlLs.setDataProviders(useDefaultDataProvider, [...customData, ...extraData]);
243
- }
244
- async function getCustomData() {
245
- const customData = await context.env.getConfiguration?.('html.customData') ?? [];
246
- const newData = [];
247
- for (const customDataPath of customData) {
248
- try {
249
- const pathModuleName = 'path'; // avoid bundle
250
- const { posix: path } = require(pathModuleName);
251
- const jsonPath = path.resolve(customDataPath);
252
- newData.push(html.newHTMLDataProvider(customDataPath, require(jsonPath)));
253
- }
254
- catch (error) {
255
- console.error(error);
291
+ function getHtmlDocument(document) {
292
+ const cache = htmlDocuments.get(document);
293
+ if (cache) {
294
+ const [cacheVersion, cacheDoc] = cache;
295
+ if (cacheVersion === document.version) {
296
+ return cacheDoc;
256
297
  }
257
298
  }
258
- return newData;
299
+ const doc = htmlLs.parseHTMLDocument(document);
300
+ htmlDocuments.set(document, [document.version, doc]);
301
+ return doc;
259
302
  }
260
303
  async function worker(document, callback) {
261
- if (document.languageId !== languageId)
304
+ if (!matchDocument(documentSelector, document))
262
305
  return;
263
306
  const htmlDocument = getHtmlDocument(document);
264
307
  if (!htmlDocument)
265
308
  return;
266
- await initCustomData();
309
+ await (initializing ??= initialize());
267
310
  return callback(htmlDocument);
268
311
  }
312
+ async function initialize() {
313
+ const customData = await getCustomData(context);
314
+ htmlLs.setDataProviders(useDefaultDataProvider, customData);
315
+ }
269
316
  },
270
317
  };
271
318
  }
@@ -278,4 +325,12 @@ const NL = '\n'.charCodeAt(0);
278
325
  function isNewlineCharacter(charCode) {
279
326
  return charCode === CR || charCode === NL;
280
327
  }
328
+ function matchDocument(selector, document) {
329
+ for (const sel of selector) {
330
+ if (sel === document.languageId || (typeof sel === 'object' && sel.language === document.languageId)) {
331
+ return true;
332
+ }
333
+ }
334
+ return false;
335
+ }
281
336
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "volar-service-html",
3
- "version": "0.0.29",
3
+ "version": "0.0.31",
4
4
  "description": "Integrate vscode-languageservice-html into Volar",
5
5
  "homepage": "https://github.com/volarjs/services/tree/master/packages/html",
6
6
  "bugs": "https://github.com/volarjs/services/issues",
@@ -25,19 +25,19 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "vscode-html-languageservice": "^5.1.0",
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": "6927645293abcc249e7a39c98e52b9f5a2c08469"
42
+ "gitHead": "f7005aef724767786ee9fe943fa976231cc79bf1"
43
43
  }