Skip to content

Commit

Permalink
Add support for cross-file semantic capabilities
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsoncodehk committed Jun 23, 2024
1 parent aa90c7b commit 03d3a32
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 116 deletions.
108 changes: 46 additions & 62 deletions extensions/html-language-features/server/src/modes/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,83 +3,67 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { LanguagePlugin, LanguageServer, LanguageServerProject } from '@volar/language-server';
import { createLanguageServiceEnvironment, createUriConverter } from '@volar/language-server/node';
import { createLanguage, createLanguageService, createUriMap, LanguageService } from '@volar/language-service';
import { createLanguageServiceHost, resolveFileLanguageId } from '@volar/typescript';
import { LanguageServer, LanguageServerProject } from '@volar/language-server';
import { createLanguageServiceEnvironment, createUriConverter, getWorkspaceFolder } from '@volar/language-server/browser';
import { createTypeScriptLS, TypeScriptProjectLS } from '@volar/language-server/lib/project/typescriptProjectLs';
import { createUriMap, LanguagePlugin } from '@volar/language-service';
import * as ts from 'typescript';
import { TextDocument } from 'vscode-html-languageservice';
import { URI } from 'vscode-uri';
import { createProjectHost } from './projectHost';
import { JQUERY_PATH } from './javascriptLibs';

export function createHtmlProject(languagePlugins: LanguagePlugin<URI>[]): LanguageServerProject {
let server: LanguageServer;
let languageService: LanguageService | undefined;
let currentDocument: [URI, string, TextDocument, ts.IScriptSnapshot] | undefined;
let tsLocalized: any;

const { asFileName, asUri } = createUriConverter();
const inferredProjects = createUriMap<Promise<TypeScriptProjectLS>>();

return {
setup(_server) {
server = _server;
},
getLanguageService(uri) {
const document = server.documents.get(server.getSyncedDocumentKey(uri) ?? uri.toString())!;
currentDocument = [uri, asFileName(uri), document, document.getSnapshot()];
if (!languageService) {
const projectHost = createProjectHost(() => currentDocument!);
const language = createLanguage(
[
{ getLanguageId: uri => server.documents.get(server.getSyncedDocumentKey(uri) ?? uri.toString())?.languageId },
...languagePlugins,
{ getLanguageId: uri => resolveFileLanguageId(uri.path) },
],
createUriMap(),
uri => {
const documentUri = server.getSyncedDocumentKey(uri);
const syncedDocument = documentUri ? server.documents.get(documentUri) : undefined;

let snapshot: ts.IScriptSnapshot | undefined;

if (syncedDocument) {
snapshot = syncedDocument.getSnapshot();
}
else {
snapshot = projectHost.getScriptSnapshot(asFileName(uri));
}

if (snapshot) {
language.scripts.set(uri, snapshot);
}
else {
language.scripts.delete(uri);
}
}
);
language.typescript = {
configFileName: undefined,
sys: ts.sys,
asFileName: asFileName,
asScriptId: asUri,
...createLanguageServiceHost(ts, ts.sys, language, asUri, projectHost),
};
languageService = createLanguageService(
language,
server.languageServicePlugins,
createLanguageServiceEnvironment(server, [...server.workspaceFolders.keys()])
);
if (server.initializeParams.locale) {
try {
tsLocalized = require(`typescript/lib/${server.initializeParams.locale}/diagnosticMessages.generated.json`);
} catch { }
}
return languageService;
},
getExistingLanguageServices() {
if (languageService) {
return [languageService];
}
return [];
async getLanguageService(uri) {
const workspaceFolder = getWorkspaceFolder(uri, server.workspaceFolders);
const project = await getOrCreateInferredProject(server, workspaceFolder);
project.tryAddFile(asFileName(uri));
return project.languageService;
},
async getExistingLanguageServices() {
const projects = await Promise.all(inferredProjects.values());
return projects.map(project => project.languageService);
},
reload() {
languageService?.dispose();
languageService = undefined;
for (const project of inferredProjects.values()) {
project.then(p => p.dispose());
}
inferredProjects.clear();
},
};

async function getOrCreateInferredProject(server: LanguageServer, workspaceFolder: URI) {
if (!inferredProjects.has(workspaceFolder)) {
inferredProjects.set(workspaceFolder, (async () => {
const inferOptions = { allowNonTsExtensions: true, allowJs: true, lib: ['lib.es2020.full.d.ts'], target: 99 satisfies ts.ScriptTarget.Latest, moduleResolution: 1 satisfies ts.ModuleResolutionKind.Classic, experimentalDecorators: false };
const serviceEnv = createLanguageServiceEnvironment(server, [workspaceFolder]);
const project = await createTypeScriptLS(
ts,
tsLocalized,
inferOptions,
server,
serviceEnv,
workspaceFolder,
() => languagePlugins,
{ asUri, asFileName }
);
project.tryAddFile(JQUERY_PATH);
return project;
})());
}
return inferredProjects.get(workspaceFolder)!;
}
}
47 changes: 0 additions & 47 deletions extensions/html-language-features/server/src/modes/projectHost.ts

This file was deleted.

38 changes: 31 additions & 7 deletions extensions/html-language-features/server/src/test/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,45 @@
import { ClientCapabilities, LanguageServiceEnvironment, TextDocument } from '@volar/language-server';
import { createUriConverter, fs } from '@volar/language-server/node';
import { createLanguage, createLanguageService, createUriMap, LanguageService } from '@volar/language-service';
import { createLanguageServiceHost, resolveFileLanguageId } from '@volar/typescript';
import { createLanguageServiceHost, resolveFileLanguageId, TypeScriptProjectHost } from '@volar/typescript';
import * as ts from 'typescript';
import { URI } from 'vscode-uri';
import { htmlLanguagePlugin } from '../modes/languagePlugin';
import { createProjectHost } from '../modes/projectHost';
import { getLanguageServicePlugins } from '../modes/servicePlugins';
import { JQUERY_PATH } from '../modes/javascriptLibs';

let currentDocument: [URI, string, TextDocument, ts.IScriptSnapshot];
let languageService: LanguageService;

const { asFileName, asUri } = createUriConverter();
const serviceEnv: LanguageServiceEnvironment = {
workspaceFolders: [],
fs,
const serviceEnv: LanguageServiceEnvironment = { workspaceFolders: [], fs };
const libSnapshots = new Map<string, ts.IScriptSnapshot | undefined>();
const compilerOptions: ts.CompilerOptions = { allowNonTsExtensions: true, allowJs: true, lib: ['lib.es2020.full.d.ts'], target: 99 satisfies ts.ScriptTarget.Latest, moduleResolution: 1 satisfies ts.ModuleResolutionKind.Classic, experimentalDecorators: false };
const projectHost: TypeScriptProjectHost = {
getCompilationSettings: () => compilerOptions,
getScriptFileNames: () => [currentDocument[1], JQUERY_PATH],
getCurrentDirectory: () => '',
getProjectVersion: () => currentDocument[1] + ',' + currentDocument[2].version,
getScriptSnapshot: fileName => {
if (fileName === currentDocument[1]) {
return currentDocument[3];
}
else {
let snapshot = libSnapshots.get(fileName);
if (!snapshot) {
const text = ts.sys.readFile(fileName);
if (text !== undefined) {
snapshot = {
getText: (start, end) => text.substring(start, end),
getLength: () => text.length,
getChangeRange: () => undefined,
};
}
libSnapshots.set(fileName, snapshot);
}
return snapshot;
}
},
};

export const languageServicePlugins = getLanguageServicePlugins({
Expand Down Expand Up @@ -50,7 +75,6 @@ export async function getTestService({
serviceEnv.workspaceFolders = [URI.parse(workspaceFolder)];
serviceEnv.clientCapabilities = clientCapabilities;
if (!languageService) {
const projectHost = createProjectHost(() => currentDocument);
const language = createLanguage(
[
htmlLanguagePlugin,
Expand Down Expand Up @@ -83,7 +107,7 @@ export async function getTestService({
sys: ts.sys,
asFileName: asFileName,
asScriptId: asUri,
...createLanguageServiceHost(ts, ts.sys, language, asUri, createProjectHost(() => currentDocument!)),
...createLanguageServiceHost(ts, ts.sys, language, asUri, projectHost),
};
languageService = createLanguageService(language, languageServicePlugins, serviceEnv);
}
Expand Down

0 comments on commit 03d3a32

Please sign in to comment.