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