diff --git a/extensions/html-language-features/server/src/browser/htmlServerMain.ts b/extensions/html-language-features/server/src/browser/htmlServerMain.ts index 1a3041aad688e..309c629f08f0d 100644 --- a/extensions/html-language-features/server/src/browser/htmlServerMain.ts +++ b/extensions/html-language-features/server/src/browser/htmlServerMain.ts @@ -4,9 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { createServer, createConnection, createSimpleProjectProvider } from '@volar/language-server/node'; -import { create as createCssServicePlugin } from 'volar-service-css'; -import { create as createHtmlServicePlugin } from 'volar-service-html'; import { htmlLanguagePlugin } from '../modes/languagePlugin'; +import { getLanguageServices } from '../modes/servicePlugins'; const connection = createConnection(); const server = createServer(connection); @@ -17,10 +16,7 @@ connection.onInitialize(params => { return [htmlLanguagePlugin]; }, getServicePlugins() { - return [ - createCssServicePlugin(), - createHtmlServicePlugin(), - ]; + return getLanguageServices(server.modules.typescript!); }, }); }); diff --git a/extensions/html-language-features/server/src/modes/project.ts b/extensions/html-language-features/server/src/modes/project.ts new file mode 100644 index 0000000000000..5b278f35b0706 --- /dev/null +++ b/extensions/html-language-features/server/src/modes/project.ts @@ -0,0 +1,68 @@ +import { ServerProject } from '@volar/language-server'; +import { ServerContext, ServerOptions } from '@volar/language-server/lib/server'; +import { LanguageService, ServiceEnvironment, ServicePlugin, createLanguageService } from '@volar/language-service'; +import type { SnapshotDocument } from '@volar/snapshot-document'; +import { createLanguage, createSys } from '@volar/typescript'; +import { createProjectHost } from './projectHost'; + +export async function createProject( + context: ServerContext, + serviceEnv: ServiceEnvironment, + getLanguagePlugins: ServerOptions['getLanguagePlugins'], + servicePlugins: ServicePlugin[], + getCurrentTextDocument: () => SnapshotDocument, +): Promise { + + let languageService: LanguageService | undefined; + + const ts = context.ts!; + const sys = createSys(ts, serviceEnv, ''); + const host = createProjectHost( + serviceEnv.typescript!, + fileName => sys.readFile(fileName), + getCurrentTextDocument, + () => getCurrentTextDocument().getSnapshot(), + context.tsLocalized, + ) + const languagePlugins = await getLanguagePlugins(serviceEnv, { + typescript: { + configFileName: undefined, + host, + sys, + }, + }); + + return { + serviceEnv, + getLanguageService, + getLanguageServiceDontCreate: () => languageService, + dispose, + }; + + function getLanguageService() { + if (!languageService) { + const language = createLanguage( + ts, + sys, + languagePlugins, + undefined, + host, + { + fileNameToFileId: serviceEnv.typescript!.fileNameToUri, + fileIdToFileName: serviceEnv.typescript!.uriToFileName, + }, + ); + languageService = createLanguageService( + language, + servicePlugins, + serviceEnv, + ); + } + return languageService; + } + + function dispose() { + sys.dispose(); + languageService?.dispose(); + } +} diff --git a/extensions/html-language-features/server/src/modes/projectHost.ts b/extensions/html-language-features/server/src/modes/projectHost.ts new file mode 100644 index 0000000000000..0b897ae4fea5d --- /dev/null +++ b/extensions/html-language-features/server/src/modes/projectHost.ts @@ -0,0 +1,48 @@ +import { ServiceEnvironment, TypeScriptProjectHost, resolveCommonLanguageId, TextDocument } from '@volar/language-service'; +import type * as ts from 'typescript'; +import { JQUERY_PATH } from './javascriptLibs'; + +export function createProjectHost( + { uriToFileName, fileNameToUri }: NonNullable, + readFile: (fileName: string) => string | undefined, + getCurrentTextDocument: () => TextDocument, + getCurrentDocumentSnapshot: () => ts.IScriptSnapshot, + tsLocalized?: ts.MapLike, +) { + const libSnapshots = new Map(); + 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 host: TypeScriptProjectHost = { + getCompilationSettings: () => compilerOptions, + getScriptFileNames: () => [uriToFileName(getCurrentTextDocument().uri), JQUERY_PATH], + getCurrentDirectory: () => '', + getProjectVersion: () => getCurrentTextDocument().uri + ',' + getCurrentTextDocument().version.toString(), + getScriptSnapshot: fileName => { + if (fileNameToUri(fileName) === getCurrentTextDocument().uri) { + return getCurrentDocumentSnapshot(); + } + else { + let snapshot = libSnapshots.get(fileName); + if (!snapshot) { + const text = readFile(fileName); + if (text !== undefined) { + snapshot = { + getText: (start, end) => text.substring(start, end), + getLength: () => text.length, + getChangeRange: () => undefined, + }; + } + libSnapshots.set(fileName, snapshot); + } + return snapshot; + } + }, + getLocalizedDiagnosticMessages: tsLocalized ? () => tsLocalized : undefined, + getLanguageId: uri => { + if (uri === getCurrentTextDocument().uri) { + return getCurrentTextDocument().languageId; + } + return resolveCommonLanguageId(uri) + }, + }; + return host; +} diff --git a/extensions/html-language-features/server/src/modes/projectProvider.ts b/extensions/html-language-features/server/src/modes/projectProvider.ts index e9c6039e74815..e35a4efb6bc7b 100644 --- a/extensions/html-language-features/server/src/modes/projectProvider.ts +++ b/extensions/html-language-features/server/src/modes/projectProvider.ts @@ -1,12 +1,8 @@ import { ServerProject, ServerProjectProviderFactory } from '@volar/language-server'; -import { ServerContext, ServerOptions } from '@volar/language-server/lib/server'; import { createServiceEnvironment, getWorkspaceFolder } from '@volar/language-server/node'; -import { LanguageService, ServiceEnvironment, ServicePlugin, TypeScriptProjectHost, createLanguageService, resolveCommonLanguageId } from '@volar/language-service'; -import { createLanguage, createSys } from '@volar/typescript'; -import type * as ts from 'typescript'; +import type { SnapshotDocument } from '@volar/snapshot-document'; import { URI } from 'vscode-uri'; -import type { SnapshotDocument } from '@volar/snapshot-document' -import { JQUERY_PATH } from './javascriptLibs'; +import { createProject } from './project'; export const serverProjectProviderFactory: ServerProjectProviderFactory = (context, serverOptions, servicePlugins) => { @@ -38,93 +34,15 @@ export const serverProjectProviderFactory: ServerProjectProviderFactory = (conte if (!inferredProject) { inferredProject = (async () => { const serviceEnv = createServiceEnvironment(context, workspaceFolder); - return createProject(context, serviceEnv, serverOptions.getLanguagePlugins, servicePlugins); - })(); - } - return await inferredProject; - } - - async function createProject( - context: ServerContext, - serviceEnv: ServiceEnvironment, - getLanguagePlugins: ServerOptions['getLanguagePlugins'], - servicePlugins: ServicePlugin[], - ): Promise { - - let languageService: LanguageService | undefined; - - const libSnapshots = new Map(); - const { fileNameToUri } = context.runtimeEnv; - const ts = context.ts!; - const compilerOptions: ts.CompilerOptions = { allowNonTsExtensions: true, allowJs: true, lib: ['lib.es2020.full.d.ts'], target: ts.ScriptTarget.Latest, moduleResolution: ts.ModuleResolutionKind.Classic, experimentalDecorators: false }; - const sys = createSys(ts, serviceEnv, ''); - const host: TypeScriptProjectHost = { - getCompilationSettings: () => compilerOptions, - getScriptFileNames: () => [uriToFileName(currentTextDocument!.uri), JQUERY_PATH], - getCurrentDirectory: () => '', - getProjectVersion: () => currentTextDocument!.uri + ',' + currentTextDocument!.version.toString() + ',' + sys.version, - getScriptSnapshot: fileName => { - if (fileNameToUri(fileName) === currentTextDocument!.uri) { - const document = context.documents.get(fileNameToUri(fileName)); - if (document) { - return document.getSnapshot(); - } - } - else { - let snapshot = libSnapshots.get(fileName); - if (!snapshot) { - const text = sys.readFile?.(fileName); - if (text !== undefined) { - snapshot = ts.ScriptSnapshot.fromString(text); - } - libSnapshots.set(fileName, snapshot); - } - return snapshot; - } - return undefined; - }, - getLocalizedDiagnosticMessages: context.tsLocalized ? () => context.tsLocalized : undefined, - getLanguageId: uri => context.documents.get(uri)?.languageId ?? resolveCommonLanguageId(uri), - }; - const languagePlugins = await getLanguagePlugins(serviceEnv, { - typescript: { - configFileName: undefined, - host, - sys, - }, - }); - - return { - serviceEnv, - getLanguageService, - getLanguageServiceDontCreate: () => languageService, - dispose, - }; - - function getLanguageService() { - if (!languageService) { - const language = createLanguage( - ts, - sys, - languagePlugins, - undefined, - host, - { - fileNameToFileId: serviceEnv.typescript!.fileNameToUri, - fileIdToFileName: serviceEnv.typescript!.uriToFileName, - }, - ); - languageService = createLanguageService( - language, - servicePlugins, + return createProject( + context, serviceEnv, + serverOptions.getLanguagePlugins, + servicePlugins, + () => currentTextDocument!, ); - } - return languageService; - } - function dispose() { - sys.dispose(); - languageService?.dispose(); + })(); } + return await inferredProject; } }; diff --git a/extensions/html-language-features/server/src/modes/servicePlugins.ts b/extensions/html-language-features/server/src/modes/servicePlugins.ts new file mode 100644 index 0000000000000..abb71fe67e64c --- /dev/null +++ b/extensions/html-language-features/server/src/modes/servicePlugins.ts @@ -0,0 +1,25 @@ +import type { ServicePlugin } from '@volar/language-service'; +import { create as createCssServicePlugin } from 'volar-service-css'; +import { create as createHtmlServicePlugin } from 'volar-service-html'; +import { create as createTypeScriptServicePlugin } from 'volar-service-typescript'; + +export function getLanguageServices(ts: typeof import('typescript')) { + const html1ServicePlugins: ServicePlugin[] = [ + createCssServicePlugin(), + createHtmlServicePlugin(), + createTypeScriptServicePlugin(ts), + { + create() { + return { + resolveEmbeddedCodeFormattingOptions(code, options) { + if (code.id.startsWith('css_')) { + options.initialIndentLevel++; + } + return options; + }, + }; + }, + }, + ]; + return html1ServicePlugins; +} diff --git a/extensions/html-language-features/server/src/node/htmlServerMain.ts b/extensions/html-language-features/server/src/node/htmlServerMain.ts index 56eb997bb902e..1717eca9eb5e0 100644 --- a/extensions/html-language-features/server/src/node/htmlServerMain.ts +++ b/extensions/html-language-features/server/src/node/htmlServerMain.ts @@ -4,11 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { createServer, createConnection } from '@volar/language-server/node'; -import { create as createCssServicePlugin } from 'volar-service-css'; -import { create as createHtmlServicePlugin } from 'volar-service-html'; -import { create as createTypeScriptServicePlugin } from 'volar-service-typescript'; import { htmlLanguagePlugin } from '../modes/languagePlugin'; import { serverProjectProviderFactory } from '../modes/projectProvider'; +import { getLanguageServices } from '../modes/servicePlugins'; const connection = createConnection(); const server = createServer(connection); @@ -19,23 +17,7 @@ connection.onInitialize(params => { return [htmlLanguagePlugin]; }, getServicePlugins() { - return [ - createCssServicePlugin(), - createHtmlServicePlugin(), - createTypeScriptServicePlugin(server.modules.typescript!), - { - create() { - return { - resolveEmbeddedCodeFormattingOptions(code, options) { - if (code.id.startsWith('css_')) { - options.initialIndentLevel++; - } - return options; - }, - }; - }, - }, - ]; + return getLanguageServices(server.modules.typescript!); }, }); });