@staticbolt/lsp 1.0.0-beta.30 → 1.0.0-beta.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.
@@ -1,83 +0,0 @@
1
- /* eslint-disable @typescript-eslint/require-await */
2
- import path from "node:path";
3
- import { Resolver } from "@staticbolt/core";
4
- import * as vscodeUri from "vscode-uri";
5
-
6
- import { createMergedHtmlDataProvider } from "../helpers/merge-html-data.ts";
7
- import { getLanguageModelCache } from "../language-model-cache.ts";
8
- import { FILE_PROTOCOL } from "./language-modes.ts";
9
-
10
- import type { LanguageMode } from "./language-modes.ts";
11
- import type { HTMLDataV1, HTMLDocument, IHTMLDataProvider, LanguageService, TextDocument } from "vscode-html-languageservice";
12
-
13
- export function getHTMLMode(htmlLanguageService: LanguageService): LanguageMode {
14
- const htmlDocuments = getLanguageModelCache<HTMLDocument>(10, 60, document => htmlLanguageService.parseHTMLDocument(document));
15
-
16
- // the merged provider is rebuilt only when the collected html data actually changes
17
- let lastHtmlData: HTMLDataV1[] | undefined;
18
- let lastProvider: IHTMLDataProvider | undefined;
19
-
20
- function setHtmlDataProviders(htmlData: HTMLDataV1[]) {
21
- if (!lastProvider || lastHtmlData !== htmlData) {
22
- lastHtmlData = htmlData;
23
- lastProvider = createMergedHtmlDataProvider(FILE_PROTOCOL, htmlData);
24
- }
25
-
26
- htmlLanguageService.setDataProviders(false, [lastProvider]);
27
- }
28
-
29
- return {
30
- getId() {
31
- return "html";
32
- },
33
-
34
- async doComplete(document, position, documentContext, htmlData) {
35
- setHtmlDataProviders(htmlData);
36
-
37
- const htmlDocument = htmlDocuments.get(document);
38
- const completionList = await htmlLanguageService.doComplete2(document, position, htmlDocument, documentContext);
39
-
40
- for (const item of completionList.items) {
41
- item.sortText = "0_" + item.label;
42
- }
43
-
44
- return completionList;
45
- },
46
-
47
- async doHover(document, position, htmlData) {
48
- setHtmlDataProviders(htmlData);
49
-
50
- return htmlLanguageService.doHover(document, position, htmlDocuments.get(document));
51
- },
52
-
53
- async onDocumentRemoved(document: TextDocument) {
54
- htmlDocuments.onDocumentRemoved(document);
55
- },
56
-
57
- async findDocumentLinks(document, documentContext, projectRoot) {
58
- const resolver = new Resolver(projectRoot);
59
- const documentFs = vscodeUri.URI.parse(document.uri).fsPath;
60
- const projectRootRelative = path.relative(projectRoot, documentFs);
61
-
62
- const links = htmlLanguageService.findDocumentLinks(document, documentContext);
63
-
64
- for (const link of links) {
65
- if (!link.target) continue;
66
-
67
- const linkFs = vscodeUri.URI.parse(link.target ?? "").fsPath;
68
- const source = path.relative(path.dirname(documentFs), linkFs);
69
-
70
- const resolved = resolver.resolve(source, projectRootRelative);
71
- if (resolved) {
72
- link.target = vscodeUri.URI.file(resolved.path).toString();
73
- }
74
- }
75
-
76
- return links;
77
- },
78
-
79
- dispose() {
80
- htmlDocuments.dispose();
81
- },
82
- };
83
- }
@@ -1,156 +0,0 @@
1
- import vscodeHtml from "vscode-html-languageservice";
2
-
3
- import { getLanguageModelCache } from "../language-model-cache.ts";
4
- import { getDocumentRegions } from "./embedded-support.ts";
5
- import { getHTMLMode } from "./html-mode.ts";
6
-
7
- import type { LanguageModelCache } from "../language-model-cache.ts";
8
- import type { FileSystemProvider } from "../requests.ts";
9
- import type { HTMLDocumentRegions } from "./embedded-support.ts";
10
- import type { ClientCapabilities, DocumentContext, HTMLDataV1, IHTMLDataProvider } from "vscode-html-languageservice";
11
- import type { CompletionItem, CompletionList, DocumentLink, Hover, Position } from "vscode-languageserver";
12
- import type { TextDocument } from "vscode-languageserver-textdocument";
13
-
14
- export type CompletionItemData = {
15
- languageId: string;
16
- uri: string;
17
- offset: number;
18
- };
19
-
20
- export function isCompletionItemData(value: Record<string, unknown>): value is CompletionItemData {
21
- return value && typeof value.languageId === "string" && typeof value.uri === "string" && typeof value.offset === "number";
22
- }
23
-
24
- export interface LanguageMode {
25
- getId(): string;
26
- doComplete?: (
27
- document: TextDocument,
28
- position: Position,
29
- documentContext: DocumentContext,
30
- htmlData: HTMLDataV1[]
31
- ) => Promise<CompletionList>;
32
- doResolve?: (document: TextDocument, item: CompletionItem) => Promise<CompletionItem>;
33
- doHover?: (document: TextDocument, position: Position, htmlData: HTMLDataV1[]) => Promise<Hover | null>;
34
- findDocumentLinks?: (document: TextDocument, documentContext: DocumentContext, projectRoot: string) => Promise<DocumentLink[]>;
35
- onDocumentRemoved(document: TextDocument): void;
36
- dispose(): void;
37
- }
38
-
39
- export interface LanguageModes {
40
- updateDataProviders(dataProviders: IHTMLDataProvider[]): void;
41
- /** The document to hand to the HTML mode: itself for an HTML file, a copy holding only the raw HTML for a markdown one. */
42
- getHtmlDocument(document: TextDocument): TextDocument;
43
- getModeAtPosition(document: TextDocument, position: Position): LanguageMode | undefined;
44
- getAllModesInDocument(document: TextDocument): LanguageMode[];
45
- getMode(languageId: string): LanguageMode | undefined;
46
- onDocumentRemoved(document: TextDocument): void;
47
- dispose(): void;
48
- }
49
-
50
- export const FILE_PROTOCOL = "staticbolt-server";
51
-
52
- export function getLanguageModes(clientCapabilities: ClientCapabilities, requestService: FileSystemProvider): LanguageModes {
53
- const htmlLanguageService = vscodeHtml.getLanguageService({
54
- clientCapabilities,
55
- fileSystemProvider: requestService,
56
- customDataProviders: [
57
- {
58
- getId() {
59
- return FILE_PROTOCOL;
60
- },
61
- isApplicable(languageId) {
62
- return languageId === "html";
63
- },
64
- provideValues(tag, attribute) {
65
- return [{ name: `tag:${tag} attribute:${attribute}` }];
66
- },
67
- provideTags() {
68
- return [{ name: "staticbolt", description: "# staticbolt-description", attributes: [] }];
69
- },
70
-
71
- provideAttributes(tag: string) {
72
- if (tag === "staticbolt") {
73
- return [
74
- {
75
- name: "staticbolt-attribute",
76
- description: "# staticbolt-attribute-description",
77
- values: [{ name: "staticbolt-attribute-value" }],
78
- },
79
- ];
80
- }
81
-
82
- return [{ name: "global-attribute" }];
83
- },
84
- },
85
- ],
86
- useDefaultDataProvider: false,
87
- });
88
-
89
- const documentRegions = getLanguageModelCache<HTMLDocumentRegions>(10, 60, document =>
90
- getDocumentRegions(htmlLanguageService, document)
91
- );
92
-
93
- let modelCaches: LanguageModelCache<unknown>[] = [documentRegions];
94
-
95
- let modes = Object.create(null) as { [languageId: string]: LanguageMode };
96
- modes["html"] = getHTMLMode(htmlLanguageService);
97
-
98
- return {
99
- // eslint-disable-next-line @typescript-eslint/require-await
100
- async updateDataProviders(dataProviders: IHTMLDataProvider[]): Promise<void> {
101
- htmlLanguageService.setDataProviders(true, dataProviders);
102
- },
103
-
104
- getHtmlDocument(document: TextDocument): TextDocument {
105
- return documentRegions.get(document).getHtmlDocument();
106
- },
107
-
108
- getModeAtPosition(document: TextDocument, position: Position): LanguageMode | undefined {
109
- const languageId = documentRegions.get(document).getLanguageAtPosition(position);
110
- if (languageId) {
111
- return modes[languageId];
112
- }
113
-
114
- return undefined;
115
- },
116
-
117
- getAllModesInDocument(document: TextDocument): LanguageMode[] {
118
- const result: LanguageMode[] = [];
119
- for (const languageId of documentRegions.get(document).getLanguagesInDocument()) {
120
- const mode = modes[languageId];
121
- if (mode) {
122
- result.push(mode);
123
- }
124
- }
125
-
126
- return result;
127
- },
128
-
129
- getMode(languageId: string): LanguageMode {
130
- return modes[languageId];
131
- },
132
-
133
- onDocumentRemoved(document: TextDocument) {
134
- for (const mc of modelCaches) {
135
- mc.onDocumentRemoved(document);
136
- }
137
-
138
- for (const mode in modes) {
139
- modes[mode].onDocumentRemoved(document);
140
- }
141
- },
142
-
143
- dispose(): void {
144
- for (const mc of modelCaches) {
145
- mc.dispose();
146
- }
147
-
148
- modelCaches = [];
149
- for (const mode in modes) {
150
- modes[mode].dispose();
151
- }
152
-
153
- modes = {};
154
- },
155
- };
156
- }
package/src/requests.ts DELETED
@@ -1,72 +0,0 @@
1
- import { RequestType } from "vscode-languageserver";
2
-
3
- import type { RuntimeEnvironment } from "./html-server.ts";
4
- import type { Connection } from "vscode-languageserver";
5
-
6
- // Interface
7
- export const FsStatRequest = { type: new RequestType<string, FileStat, unknown>("fs/stat") } as const;
8
-
9
- // Interface
10
- export const FsReadDirectoryRequest = { type: new RequestType<string, [string, FileType][], unknown>("fs/readDir") } as const;
11
-
12
- export const FileType = Object.freeze({
13
- /** The file type is unknown. */
14
- Unknown: 0,
15
-
16
- /** A regular file. */
17
- File: 1,
18
-
19
- /** A directory. */
20
- Directory: 2,
21
-
22
- /** A symbolic link to a file. */
23
- SymbolicLink: 64,
24
- });
25
-
26
- export type FileType = (typeof FileType)[keyof typeof FileType];
27
-
28
- export interface FileStat {
29
- /** The type of the file, e.g. is a regular file, a directory, or symbolic link to a file. */
30
- type: FileType;
31
-
32
- /** The creation timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC. */
33
- ctime: number;
34
-
35
- /** The modification timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC. */
36
- mtime: number;
37
-
38
- /** The size in bytes. */
39
- size: number;
40
- }
41
-
42
- export interface FileSystemProvider {
43
- stat(uri: string): Promise<FileStat>;
44
- readDirectory(uri: string): Promise<[string, FileType][]>;
45
- }
46
-
47
- export function getFileSystemProvider(
48
- handledSchemas: string[],
49
- connection: Connection,
50
- runtime: RuntimeEnvironment
51
- ): FileSystemProvider {
52
- const fileFs = runtime.fileFs && handledSchemas.includes("file") ? runtime.fileFs : undefined;
53
-
54
- return {
55
- async stat(uri: string): Promise<FileStat> {
56
- if (fileFs && uri.startsWith("file:")) {
57
- return fileFs.stat(uri);
58
- }
59
-
60
- const result = await connection.sendRequest(FsStatRequest.type, uri);
61
- return result;
62
- },
63
-
64
- readDirectory(uri: string): Promise<[string, FileType][]> {
65
- if (fileFs && uri.startsWith("file:")) {
66
- return fileFs.readDirectory(uri);
67
- }
68
-
69
- return connection.sendRequest(FsReadDirectoryRequest.type, uri);
70
- },
71
- };
72
- }
@@ -1,73 +0,0 @@
1
- export function pushAll<T>(to: T[], from: T[]) {
2
- if (from) {
3
- for (const item of from) {
4
- to.push(item);
5
- }
6
- }
7
- }
8
-
9
- // eslint-disable-next-line unicorn/consistent-boolean-name
10
- export function contains<T>(array: T[], value: T) {
11
- return array.includes(value);
12
- }
13
-
14
- /**
15
- * Like `Array#sort` but always stable. Usually runs a little slower `than Array#sort` so only use this when actually needing
16
- * stable sort.
17
- */
18
- export function mergeSort<T>(data: T[], compare: (a: T, b: T) => number): T[] {
19
- _divideAndMerge(data, compare);
20
- return data;
21
- }
22
-
23
- function _divideAndMerge<T>(data: T[], compare: (a: T, b: T) => number): void {
24
- if (data.length <= 1) {
25
- // sorted
26
- return;
27
- }
28
- const p = Math.trunc(data.length / 2);
29
- const left = data.slice(0, p);
30
- const right = data.slice(p);
31
-
32
- _divideAndMerge(left, compare);
33
- _divideAndMerge(right, compare);
34
-
35
- let leftIndex = 0;
36
- let rightIndex = 0;
37
- let index = 0;
38
- while (leftIndex < left.length && rightIndex < right.length) {
39
- const returnValue = compare(left[leftIndex], right[rightIndex]);
40
- if (returnValue <= 0) {
41
- // smaller_equal -> take left to preserve order
42
- data[index++] = left[leftIndex++];
43
- } else {
44
- // greater -> take right
45
- data[index++] = right[rightIndex++];
46
- }
47
- }
48
- while (leftIndex < left.length) {
49
- data[index++] = left[leftIndex++];
50
- }
51
- while (rightIndex < right.length) {
52
- data[index++] = right[rightIndex++];
53
- }
54
- }
55
-
56
- export function binarySearch<T>(array: T[], key: T, comparator: (op1: T, op2: T) => number): number {
57
- let low = 0,
58
- high = array.length - 1;
59
-
60
- while (low <= high) {
61
- const mid = Math.trunc((low + high) / 2);
62
- const comp = comparator(array[mid], key);
63
- if (comp < 0) {
64
- low = mid + 1;
65
- } else if (comp > 0) {
66
- high = mid - 1;
67
- } else {
68
- return mid;
69
- }
70
- }
71
-
72
- return -(low + 1);
73
- }
@@ -1,44 +0,0 @@
1
- import * as vscodeUri from "vscode-uri";
2
-
3
- import { isEndingWith, isStartingWith } from "../utils/strings.ts";
4
-
5
- import type { DocumentContext } from "vscode-css-languageservice";
6
- import type { WorkspaceFolder } from "vscode-languageserver";
7
-
8
- export function getDocumentContext(documentUri: string, workspaceFolders: WorkspaceFolder[]): DocumentContext {
9
- function getRootFolder(): string | undefined {
10
- for (const folder of workspaceFolders) {
11
- let folderURI = folder.uri;
12
- if (!isEndingWith(folderURI, "/")) {
13
- folderURI += "/";
14
- }
15
-
16
- if (isStartingWith(documentUri, folderURI)) {
17
- return folderURI;
18
- }
19
- }
20
-
21
- return undefined;
22
- }
23
-
24
- return {
25
- resolveReference: (reference: string, base = documentUri) => {
26
- if (/^\w[\w\d+.-]*:/.test(reference)) {
27
- // starts with a schema
28
- return reference;
29
- }
30
-
31
- if (reference[0] === "/") {
32
- // resolve absolute path against the current workspace folder
33
- const folderUri = getRootFolder();
34
- if (folderUri) {
35
- return folderUri + reference.slice(1);
36
- }
37
- }
38
-
39
- const baseUri = vscodeUri.URI.parse(base);
40
- const baseUriDirectory = baseUri.path.endsWith("/") ? baseUri : vscodeUri.Utils.dirname(baseUri);
41
- return vscodeUri.Utils.resolvePath(baseUriDirectory, reference).toString(true);
42
- },
43
- };
44
- }
@@ -1,24 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import * as vscodeUri from "vscode-uri";
4
-
5
- export function findProjectRoot(fileUri: string): string | null {
6
- let directory = path.dirname(vscodeUri.URI.parse(fileUri).fsPath);
7
-
8
- while (true) {
9
- for (const config of [".staticbolt.ts", ".staticbolt.js"]) {
10
- if (fs.existsSync(path.join(directory, config))) {
11
- return directory;
12
- }
13
- }
14
-
15
- const parent = path.dirname(directory);
16
-
17
- // reached fs root, no config found
18
- if (parent === directory) {
19
- return null;
20
- }
21
-
22
- directory = parent;
23
- }
24
- }
@@ -1,77 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as vscodeUri from "vscode-uri";
3
-
4
- import type { FileSystemProvider } from "../requests.ts";
5
-
6
- const FileType = Object.freeze({
7
- /** The file type is unknown. */
8
- Unknown: 0,
9
- /** A regular file. */
10
- File: 1,
11
- /** A directory. */
12
- Directory: 2,
13
- /** A symbolic link to a file. */
14
- SymbolicLink: 64,
15
- });
16
-
17
- type FileType = (typeof FileType)[keyof typeof FileType];
18
-
19
- export function getNodeFileFS(): FileSystemProvider {
20
- function ensureFileUri(location: string) {
21
- if (!location.startsWith("file:")) {
22
- throw new Error("fileSystemProvider can only handle file URLs");
23
- }
24
- }
25
- return {
26
- stat(location: string) {
27
- ensureFileUri(location);
28
- return new Promise((c, error_) => {
29
- const uri = vscodeUri.URI.parse(location);
30
- fs.stat(uri.fsPath, (error, stats) => {
31
- if (error) {
32
- return error.code === "ENOENT" ? c({ type: FileType.Unknown, ctime: -1, mtime: -1, size: -1 }) : error_(error);
33
- }
34
-
35
- let type: FileType = FileType.Unknown;
36
- if (stats.isFile()) {
37
- type = FileType.File;
38
- } else if (stats.isDirectory()) {
39
- type = FileType.Directory;
40
- } else if (stats.isSymbolicLink()) {
41
- type = FileType.SymbolicLink;
42
- }
43
-
44
- c({
45
- type,
46
- ctime: stats.ctime.getTime(),
47
- mtime: stats.mtime.getTime(),
48
- size: stats.size,
49
- });
50
- });
51
- });
52
- },
53
- readDirectory(location: string) {
54
- ensureFileUri(location);
55
- return new Promise((c, error_) => {
56
- const path = vscodeUri.URI.parse(location).fsPath;
57
-
58
- fs.readdir(path, { withFileTypes: true }, (error, children) => {
59
- if (error) {
60
- return error_(error);
61
- }
62
- c(
63
- children.map(stat => {
64
- if (stat.isSymbolicLink()) {
65
- return [stat.name, FileType.SymbolicLink];
66
- }
67
- if (stat.isDirectory()) {
68
- return [stat.name, FileType.Directory];
69
- }
70
- return stat.isFile() ? [stat.name, FileType.File] : [stat.name, FileType.Unknown];
71
- })
72
- );
73
- });
74
- });
75
- },
76
- };
77
- }
@@ -1,56 +0,0 @@
1
- import vscode from "vscode-languageserver";
2
-
3
- import type { RuntimeEnvironment } from "../html-server.ts";
4
- import type { CancellationToken, ResponseError } from "vscode-languageserver";
5
-
6
- export function formatError(message: string, error: unknown): string {
7
- if (error instanceof Error) {
8
- return `${message}: ${error.message}\n${error.stack}`;
9
- }
10
-
11
- if (typeof error === "string") {
12
- return `${message}: ${error}`;
13
- }
14
-
15
- if (error) {
16
- return `${message}: ${error as string}`;
17
- }
18
-
19
- return message;
20
- }
21
-
22
- export function runSafe<T>(
23
- runtime: RuntimeEnvironment,
24
- function_: () => Thenable<T>,
25
- errorValue: T,
26
- errorMessage: string,
27
- token: CancellationToken
28
- ): Thenable<T | ResponseError<never>> {
29
- return new Promise<T | ResponseError<never>>(resolve => {
30
- runtime.timer.setImmediate(() => {
31
- if (token.isCancellationRequested) {
32
- resolve(cancelValue());
33
- return;
34
- }
35
- return function_().then(
36
- result => {
37
- if (token.isCancellationRequested) {
38
- resolve(cancelValue());
39
- return;
40
- }
41
-
42
- resolve(result);
43
- },
44
-
45
- error => {
46
- console.error(formatError(errorMessage, error));
47
- resolve(errorValue);
48
- }
49
- );
50
- });
51
- });
52
- }
53
-
54
- function cancelValue<E>() {
55
- return new vscode.ResponseError<E>(vscode.LSPErrorCodes.RequestCancelled, "Request cancelled");
56
- }
@@ -1,76 +0,0 @@
1
- export function getWordAtText(text: string, offset: number, wordDefinition: RegExp): { start: number; length: number } {
2
- let lineStart = offset;
3
- while (lineStart > 0 && !isNewlineCharacter(text.codePointAt(lineStart - 1)!)) {
4
- lineStart--;
5
- }
6
- const offsetInLine = offset - lineStart;
7
- const lineText = text.slice(lineStart);
8
-
9
- // make a copy of the regex as to not keep the state
10
- const flags = wordDefinition.ignoreCase ? "gi" : "g";
11
- wordDefinition = new RegExp(wordDefinition.source, flags);
12
-
13
- let match = wordDefinition.exec(lineText);
14
- while (match && match.index + match[0].length < offsetInLine) {
15
- match = wordDefinition.exec(lineText);
16
- }
17
- if (match && match.index <= offsetInLine) {
18
- return { start: match.index + lineStart, length: match[0].length };
19
- }
20
-
21
- return { start: offset, length: 0 };
22
- }
23
-
24
- export function isStartingWith(haystack: string, needle: string): boolean {
25
- if (haystack.length < needle.length) {
26
- return false;
27
- }
28
-
29
- // eslint-disable-next-line unicorn/no-for-loop
30
- for (let index = 0; index < needle.length; index++) {
31
- if (haystack[index] !== needle[index]) {
32
- return false;
33
- }
34
- }
35
-
36
- return true;
37
- }
38
-
39
- export function isEndingWith(haystack: string, needle: string): boolean {
40
- const diff = haystack.length - needle.length;
41
- if (diff > 0) {
42
- return haystack.indexOf(needle, diff) === diff;
43
- }
44
-
45
- if (diff === 0) {
46
- return haystack === needle;
47
- }
48
-
49
- return false;
50
- }
51
-
52
- export function repeat(value: string, count: number) {
53
- let s = "";
54
- while (count > 0) {
55
- if ((count & 1) === 1) {
56
- s += value;
57
- }
58
- value += value;
59
- count >>>= 1;
60
- }
61
- return s;
62
- }
63
-
64
- export function isWhitespaceOnly(string_: string) {
65
- return /^\s*$/.test(string_);
66
- }
67
-
68
- export function isEOL(content: string, offset: number) {
69
- return isNewlineCharacter(content.codePointAt(offset)!);
70
- }
71
-
72
- const CR = "\r".codePointAt(0);
73
- const NL = "\n".codePointAt(0);
74
- export function isNewlineCharacter(charCode: number) {
75
- return charCode === CR || charCode === NL;
76
- }