Skip to content

Commit

Permalink
refactor(language-server): architecture improvements (#167)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsoncodehk authored Apr 29, 2024
1 parent e08a7f3 commit 665e137
Show file tree
Hide file tree
Showing 16 changed files with 487 additions and 651 deletions.
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

0 comments on commit 665e137

Please sign in to comment.