Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(language-server): architecture improvements #167

Merged
merged 3 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 26 additions & 27 deletions packages/language-server/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from 'vscode-languageserver/browser';
export * from './index';
export * from './lib/project/simpleProjectProvider';
export * from './lib/project/typescriptProjectProvider';
export * from './lib/server';

export function createConnection() {

Expand All @@ -20,34 +21,32 @@ export function createConnection() {

export function createServer(connection: vscode.Connection) {
return createServerBase(connection, () => ({
fs: {
async stat(uri) {
if (uri.startsWith('http://') || uri.startsWith('https://')) { // perf
const text = await this.readFile(uri);
if (text !== undefined) {
return {
type: FileType.File,
size: text.length,
ctime: -1,
mtime: -1,
};
}
return undefined;
async stat(uri) {
if (uri.startsWith('http://') || uri.startsWith('https://')) { // perf
const text = await this.readFile(uri);
if (text !== undefined) {
return {
type: FileType.File,
size: text.length,
ctime: -1,
mtime: -1,
};
}
return await connection.sendRequest(FsStatRequest.type, uri);
},
async readFile(uri) {
if (uri.startsWith('http://') || uri.startsWith('https://')) { // perf
return await httpSchemaRequestHandler(uri);
}
return await connection.sendRequest(FsReadFileRequest.type, uri) ?? undefined;
},
async readDirectory(uri) {
if (uri.startsWith('http://') || uri.startsWith('https://')) { // perf
return [];
}
return await connection.sendRequest(FsReadDirectoryRequest.type, uri);
},
return undefined;
}
return await connection.sendRequest(FsStatRequest.type, uri);
},
async readFile(uri) {
if (uri.startsWith('http://') || uri.startsWith('https://')) { // perf
return await httpSchemaRequestHandler(uri);
}
return await connection.sendRequest(FsReadFileRequest.type, uri) ?? undefined;
},
async readDirectory(uri) {
if (uri.startsWith('http://') || uri.startsWith('https://')) { // perf
return [];
}
return await connection.sendRequest(FsReadDirectoryRequest.type, uri);
},
}));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type * as ts from 'typescript';
import type { ServerContext } from '../server';
import type { ServerBase } from '../types';

export async function getInferredCompilerOptions(context: ServerContext) {
export async function getInferredCompilerOptions(server: ServerBase) {

const [
implicitProjectConfig_1 = {},
implicitProjectConfig_2 = {},
] = await Promise.all([
context.getConfiguration<ts.CompilerOptions>('js/ts.implicitProjectConfig'),
context.getConfiguration<ts.CompilerOptions>('javascript.implicitProjectConfig'),
server.getConfiguration<ts.CompilerOptions>('js/ts.implicitProjectConfig'),
server.getConfiguration<ts.CompilerOptions>('javascript.implicitProjectConfig'),
]);
const checkJs = readCheckJs();
const experimentalDecorators = readExperimentalDecorators();
Expand Down
17 changes: 6 additions & 11 deletions packages/language-server/lib/project/simpleProject.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import { LanguageService, ServiceEnvironment, LanguageServicePlugin, createLanguage, createLanguageService } from '@volar/language-service';
import type { ServerContext, ServerOptions } from '../server';
import type { ServerProject } from '../types';
import { LanguagePlugin, LanguageService, ServiceEnvironment, createLanguage, createLanguageService } from '@volar/language-service';
import type { ServerBase, ServerProject } from '../types';

export async function createSimpleServerProject(
context: ServerContext,
server: ServerBase,
serviceEnv: ServiceEnvironment,
servicePlugins: LanguageServicePlugin[],
getLanguagePlugins: ServerOptions['getLanguagePlugins'],
languagePlugins: LanguagePlugin[],
): Promise<ServerProject> {

let languageService: LanguageService | undefined;

const languagePlugins = await getLanguagePlugins(serviceEnv, {});

return {
getLanguageService,
getLanguageServiceDontCreate: () => languageService,
Expand All @@ -24,7 +19,7 @@ export async function createSimpleServerProject(
function getLanguageService() {
if (!languageService) {
const language = createLanguage(languagePlugins, false, uri => {
const script = context.documents.get(uri);
const script = server.documents.get(uri);
if (script) {
language.scripts.set(uri, script.languageId, script.getSnapshot());
}
Expand All @@ -34,7 +29,7 @@ export async function createSimpleServerProject(
});
languageService = createLanguageService(
language,
servicePlugins,
server.languageServicePlugins,
serviceEnv,
);
}
Expand Down
73 changes: 27 additions & 46 deletions packages/language-server/lib/project/simpleProjectProvider.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,43 @@
import type { ServiceEnvironment } from '@volar/language-service';
import type { LanguagePlugin, ServiceEnvironment } from '@volar/language-service';
import { URI } from 'vscode-uri';
import type { ServerProject, ServerProjectProvider, ServerProjectProviderFactory } from '../types';
import { createSimpleServerProject } from './simpleProject';
import type { ServerContext } from '../server';
import type { ServerBase, ServerProject, ServerProjectProvider } from '../types';
import { fileNameToUri, uriToFileName } from '../uri';
import type { UriMap } from '../utils/uriMap';
import { createSimpleServerProject } from './simpleProject';

export function createSimpleProjectProviderFactory(): ServerProjectProviderFactory {
return (context, servicePlugins, getLanguagePlugins): ServerProjectProvider => {

const projects = new Map<string, Promise<ServerProject>>();

return {
getProject(uri) {

const workspaceFolder = getWorkspaceFolder(uri, context.workspaceFolders);

let projectPromise = projects.get(workspaceFolder);
if (!projectPromise) {
const serviceEnv = createServiceEnvironment(context, workspaceFolder);
projectPromise = createSimpleServerProject(context, serviceEnv, servicePlugins, getLanguagePlugins);
projects.set(workspaceFolder, projectPromise);
}

return projectPromise;
},
async getProjects() {
return await Promise.all([...projects.values()]);
},
reloadProjects() {

for (const project of projects.values()) {
project.then(project => project.dispose());
}

projects.clear();

context.reloadDiagnostics();
},
};
export function createSimpleProjectProvider(languagePlugins: LanguagePlugin[]): ServerProjectProvider {
const map = new Map<string, Promise<ServerProject>>();
return {
get(uri) {
const workspaceFolder = getWorkspaceFolder(uri, this.workspaceFolders);
let projectPromise = map.get(workspaceFolder);
if (!projectPromise) {
const serviceEnv = createServiceEnvironment(this, workspaceFolder);
projectPromise = createSimpleServerProject(this, serviceEnv, languagePlugins);
map.set(workspaceFolder, projectPromise);
}
return projectPromise;
},
async all() {
return await Promise.all([...map.values()]);
},
};
}

export function createServiceEnvironment(context: ServerContext, workspaceFolder: string) {
const env: ServiceEnvironment = {
export function createServiceEnvironment(server: ServerBase, workspaceFolder: string): ServiceEnvironment {
return {
workspaceFolder,
fs: context.runtimeEnv.fs,
locale: context.initializeParams.locale,
clientCapabilities: context.initializeParams.capabilities,
getConfiguration: context.getConfiguration,
onDidChangeConfiguration: context.onDidChangeConfiguration,
onDidChangeWatchedFiles: context.onDidChangeWatchedFiles,
fs: server.fs,
locale: server.initializeParams?.locale,
clientCapabilities: server.initializeParams?.capabilities,
getConfiguration: server.getConfiguration,
onDidChangeConfiguration: server.onDidChangeConfiguration,
onDidChangeWatchedFiles: server.onDidChangeWatchedFiles,
typescript: {
fileNameToUri: fileNameToUri,
uriToFileName: uriToFileName,
},
};
return env;
}

export function getWorkspaceFolder(uri: string, workspaceFolders: UriMap<boolean>) {
Expand Down
35 changes: 17 additions & 18 deletions packages/language-server/lib/project/typescriptProject.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { LanguagePlugin, LanguageService, ServiceEnvironment, LanguageServicePlugin, TypeScriptProjectHost, createLanguageService } from '@volar/language-service';
import { createTypeScriptLanguage, createSys } from '@volar/typescript';
import { LanguagePlugin, LanguageService, ProviderResult, ServiceEnvironment, TypeScriptProjectHost, createLanguageService, resolveCommonLanguageId } from '@volar/language-service';
import { createSys, createTypeScriptLanguage } from '@volar/typescript';
import * as path from 'path-browserify';
import type * as ts from 'typescript';
import * as vscode from 'vscode-languageserver';
import type { ServerProject } from '../types';
import { UriMap, createUriMap } from '../utils/uriMap';
import type { ServerContext, ServerOptions } from '../server';
import type { ServerBase, ServerProject } from '../types';
import { fileNameToUri, uriToFileName } from '../uri';
import { UriMap, createUriMap } from '../utils/uriMap';

export interface TypeScriptServerProject extends ServerProject {
askedFiles: UriMap<boolean>;
Expand All @@ -18,11 +17,13 @@ export async function createTypeScriptServerProject(
ts: typeof import('typescript'),
tsLocalized: ts.MapLike<string> | undefined,
tsconfig: string | ts.CompilerOptions,
context: ServerContext,
server: ServerBase,
serviceEnv: ServiceEnvironment,
servicePlugins: LanguageServicePlugin[],
getLanguagePlugins: ServerOptions['getLanguagePlugins'],
getLanguageId: (uri: string) => string,
getLanguagePlugins: (serviceEnv: ServiceEnvironment, projectContext: {
configFileName: string | undefined;
host: TypeScriptProjectHost;
sys: ReturnType<typeof createSys>;
}) => ProviderResult<LanguagePlugin[]>,
): Promise<TypeScriptServerProject> {

let parsedCommandLine: ts.ParsedCommandLine;
Expand Down Expand Up @@ -50,7 +51,7 @@ export async function createTypeScriptServerProject(
},
getScriptSnapshot(fileName) {
askedFiles.pathSet(fileName, true);
const doc = context.documents.get(fileNameToUri(fileName));
const doc = server.documents.get(fileNameToUri(fileName));
if (doc) {
return doc.getSnapshot();
}
Expand All @@ -63,20 +64,18 @@ export async function createTypeScriptServerProject(
return parsedCommandLine.projectReferences;
},
getLanguageId(uri) {
return context.documents.get(uri)?.languageId ?? getLanguageId(uri);
return server.documents.get(uri)?.languageId ?? resolveCommonLanguageId(uri);
},
fileNameToScriptId: serviceEnv.typescript!.fileNameToUri,
scriptIdToFileName: serviceEnv.typescript!.uriToFileName,
};
const languagePlugins = await getLanguagePlugins(serviceEnv, {
typescript: {
configFileName: typeof tsconfig === 'string' ? tsconfig : undefined,
host,
sys,
},
configFileName: typeof tsconfig === 'string' ? tsconfig : undefined,
host,
sys,
});
const askedFiles = createUriMap<boolean>(fileNameToUri);
const docChangeWatcher = context.documents.onDidChangeContent(() => {
const docChangeWatcher = server.documents.onDidChangeContent(() => {
projectVersion++;
});
const fileWatch = serviceEnv.onDidChangeWatchedFiles?.(params => {
Expand Down Expand Up @@ -118,7 +117,7 @@ export async function createTypeScriptServerProject(
);
languageService = createLanguageService(
language,
servicePlugins,
server.languageServicePlugins,
serviceEnv,
);
}
Expand Down
Loading
Loading