-
-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(language-server): split code based on logical concerns (#231)
- Loading branch information
1 parent
7f93034
commit 682605b
Showing
25 changed files
with
1,473 additions
and
1,443 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import * as vscode from 'vscode-languageserver'; | ||
import { LanguageServerState } from '../types'; | ||
|
||
export function register(server: LanguageServerState) { | ||
const configurations = new Map<string, Promise<any>>(); | ||
const didChangeCallbacks = new Set<vscode.NotificationHandler<vscode.DidChangeConfigurationParams>>(); | ||
|
||
server.onInitialized(() => { | ||
server.connection.onDidChangeConfiguration(params => { | ||
configurations.clear(); // TODO: clear only the configurations that changed | ||
for (const cb of didChangeCallbacks) { | ||
cb(params); | ||
} | ||
}); | ||
const didChangeConfiguration = server.initializeParams.capabilities.workspace?.didChangeConfiguration; | ||
if (didChangeConfiguration?.dynamicRegistration) { | ||
server.connection.client.register(vscode.DidChangeConfigurationNotification.type); | ||
} | ||
}); | ||
|
||
return { | ||
get, | ||
onDidChange, | ||
}; | ||
|
||
function get<T>(section: string, scopeUri?: string): Promise<T | undefined> { | ||
if (!server.initializeParams.capabilities.workspace?.configuration) { | ||
return Promise.resolve(undefined); | ||
} | ||
const didChangeConfiguration = server.initializeParams.capabilities.workspace?.didChangeConfiguration; | ||
if (!scopeUri && didChangeConfiguration) { | ||
if (!configurations.has(section)) { | ||
configurations.set(section, getConfigurationWorker(section, scopeUri)); | ||
} | ||
return configurations.get(section)!; | ||
} | ||
return getConfigurationWorker(section, scopeUri); | ||
} | ||
|
||
function onDidChange(cb: vscode.NotificationHandler<vscode.DidChangeConfigurationParams>) { | ||
didChangeCallbacks.add(cb); | ||
return { | ||
dispose() { | ||
didChangeCallbacks.delete(cb); | ||
}, | ||
}; | ||
} | ||
|
||
async function getConfigurationWorker(section: string, scopeUri?: string) { | ||
return (await server.connection.workspace.getConfiguration({ scopeUri, section })) ?? undefined /* replace null to undefined */; | ||
} | ||
} |
216 changes: 216 additions & 0 deletions
216
packages/language-server/lib/features/editorFeatures.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
import type { CodeMapping, VirtualCode } from '@volar/language-core'; | ||
import { createUriMap } from '@volar/language-service'; | ||
import type * as ts from 'typescript'; | ||
import { URI } from 'vscode-uri'; | ||
import { | ||
GetMatchTsConfigRequest, | ||
GetServicePluginsRequest, | ||
GetVirtualCodeRequest, | ||
GetVirtualFileRequest, | ||
LoadedTSFilesMetaRequest, | ||
UpdateServicePluginStateNotification, | ||
UpdateVirtualCodeStateNotification, | ||
WriteVirtualFilesNotification | ||
} from '../../protocol'; | ||
import type { LanguageServerState } from '../types'; | ||
|
||
export function register(server: LanguageServerState) { | ||
server.onInitialize(() => { | ||
const { project } = server; | ||
const scriptVersions = createUriMap<number>(); | ||
const scriptVersionSnapshots = new WeakSet<ts.IScriptSnapshot>(); | ||
|
||
server.connection.onRequest(GetMatchTsConfigRequest.type, async params => { | ||
const uri = URI.parse(params.uri); | ||
const languageService = (await project.getLanguageService(uri)); | ||
const tsProject = languageService.context.project.typescript; | ||
if (tsProject?.configFileName) { | ||
const { configFileName, uriConverter } = tsProject; | ||
return { uri: uriConverter.asUri(configFileName).toString() }; | ||
} | ||
}); | ||
server.connection.onRequest(GetVirtualFileRequest.type, async document => { | ||
const uri = URI.parse(document.uri); | ||
const languageService = (await project.getLanguageService(uri)); | ||
const documentUri = URI.parse(document.uri); | ||
const sourceScript = languageService.context.language.scripts.get(documentUri); | ||
if (sourceScript?.generated) { | ||
return prune(sourceScript.generated.root); | ||
} | ||
|
||
function prune(virtualCode: VirtualCode): GetVirtualFileRequest.VirtualCodeInfo { | ||
const uri = languageService.context.encodeEmbeddedDocumentUri(sourceScript!.id, virtualCode.id); | ||
let version = scriptVersions.get(uri) ?? 0; | ||
if (!scriptVersionSnapshots.has(virtualCode.snapshot)) { | ||
version++; | ||
scriptVersions.set(uri, version); | ||
scriptVersionSnapshots.add(virtualCode.snapshot); | ||
} | ||
return { | ||
fileUri: sourceScript!.id.toString(), | ||
virtualCodeId: virtualCode.id, | ||
languageId: virtualCode.languageId, | ||
embeddedCodes: virtualCode.embeddedCodes?.map(prune) || [], | ||
version, | ||
disabled: languageService.context.disabledEmbeddedDocumentUris.has(uri), | ||
}; | ||
} | ||
}); | ||
server.connection.onRequest(GetVirtualCodeRequest.type, async params => { | ||
const uri = URI.parse(params.fileUri); | ||
const languageService = (await project.getLanguageService(uri)); | ||
const sourceScript = languageService.context.language.scripts.get(URI.parse(params.fileUri)); | ||
const virtualCode = sourceScript?.generated?.embeddedCodes.get(params.virtualCodeId); | ||
if (virtualCode) { | ||
const mappings: Record<string, CodeMapping[]> = {}; | ||
for (const [sourceScript, map] of languageService.context.language.maps.forEach(virtualCode)) { | ||
mappings[sourceScript.id.toString()] = map.mappings; | ||
} | ||
return { | ||
content: virtualCode.snapshot.getText(0, virtualCode.snapshot.getLength()), | ||
mappings, | ||
}; | ||
} | ||
}); | ||
server.connection.onNotification(WriteVirtualFilesNotification.type, async params => { | ||
// webpack compatibility | ||
const _require: NodeRequire = eval('require'); | ||
const fs = _require('fs'); | ||
const uri = URI.parse(params.uri); | ||
const languageService = (await project.getLanguageService(uri)); | ||
const tsProject = languageService.context.project.typescript; | ||
|
||
if (tsProject) { | ||
|
||
const { languageServiceHost } = tsProject; | ||
|
||
for (const fileName of languageServiceHost.getScriptFileNames()) { | ||
if (!fs.existsSync(fileName)) { | ||
// global virtual files | ||
const snapshot = languageServiceHost.getScriptSnapshot(fileName); | ||
if (snapshot) { | ||
fs.writeFile(fileName, snapshot.getText(0, snapshot.getLength()), () => { }); | ||
} | ||
} | ||
else { | ||
const uri = tsProject.uriConverter.asUri(fileName); | ||
const sourceScript = languageService.context.language.scripts.get(uri); | ||
if (sourceScript?.generated) { | ||
const serviceScript = sourceScript.generated.languagePlugin.typescript?.getServiceScript(sourceScript.generated.root); | ||
if (serviceScript) { | ||
const { snapshot } = serviceScript.code; | ||
fs.writeFile(fileName + serviceScript.extension, snapshot.getText(0, snapshot.getLength()), () => { }); | ||
} | ||
if (sourceScript.generated.languagePlugin.typescript?.getExtraServiceScripts) { | ||
for (const extraServiceScript of sourceScript.generated.languagePlugin.typescript.getExtraServiceScripts(uri.toString(), sourceScript.generated.root)) { | ||
const { snapshot } = extraServiceScript.code; | ||
fs.writeFile(fileName, snapshot.getText(0, snapshot.getLength()), () => { }); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
server.connection.onRequest(LoadedTSFilesMetaRequest.type, async () => { | ||
|
||
const sourceFilesData = new Map<ts.SourceFile, { | ||
projectNames: string[]; | ||
size: number; | ||
}>(); | ||
|
||
for (const languageService of await project.getExistingLanguageServices()) { | ||
const tsLanguageService: ts.LanguageService | undefined = languageService.context.inject<any>('typescript/languageService'); | ||
const program = tsLanguageService?.getProgram(); | ||
const tsProject = languageService.context.project.typescript; | ||
if (program && tsProject) { | ||
const { languageServiceHost, configFileName } = tsProject; | ||
const projectName = configFileName ?? (languageServiceHost.getCurrentDirectory() + '(inferred)'); | ||
const sourceFiles = program.getSourceFiles() ?? []; | ||
for (const sourceFile of sourceFiles) { | ||
if (!sourceFilesData.has(sourceFile)) { | ||
let nodes = 0; | ||
sourceFile.forEachChild(function walk(node) { | ||
nodes++; | ||
node.forEachChild(walk); | ||
}); | ||
sourceFilesData.set(sourceFile, { | ||
projectNames: [], | ||
size: nodes * 128, | ||
}); | ||
} | ||
sourceFilesData.get(sourceFile)!.projectNames.push(projectName); | ||
}; | ||
} | ||
} | ||
|
||
const result: { | ||
inputs: {}; | ||
outputs: Record<string, { | ||
imports: string[]; | ||
exports: string[]; | ||
entryPoint: string; | ||
inputs: Record<string, { bytesInOutput: number; }>; | ||
bytes: number; | ||
}>; | ||
} = { | ||
inputs: {}, | ||
outputs: {}, | ||
}; | ||
|
||
for (const [sourceFile, fileData] of sourceFilesData) { | ||
let key = fileData.projectNames.sort().join(', '); | ||
if (fileData.projectNames.length >= 2) { | ||
key = `Shared in ${fileData.projectNames.length} projects (${key})`; | ||
} | ||
result.outputs[key] ??= { | ||
imports: [], | ||
exports: [], | ||
entryPoint: '', | ||
inputs: {}, | ||
bytes: 0, | ||
}; | ||
result.outputs[key].inputs[sourceFile.fileName] = { bytesInOutput: fileData.size }; | ||
} | ||
|
||
return result; | ||
}); | ||
server.connection.onNotification(UpdateVirtualCodeStateNotification.type, async params => { | ||
const uri = URI.parse(params.fileUri); | ||
const languageService = await project.getLanguageService(uri); | ||
const virtualFileUri = languageService.context.encodeEmbeddedDocumentUri(URI.parse(params.fileUri), params.virtualCodeId); | ||
if (params.disabled) { | ||
languageService.context.disabledEmbeddedDocumentUris.set(virtualFileUri, true); | ||
} | ||
else { | ||
languageService.context.disabledEmbeddedDocumentUris.delete(virtualFileUri); | ||
} | ||
}); | ||
server.connection.onNotification(UpdateServicePluginStateNotification.type, async params => { | ||
const uri = URI.parse(params.uri); | ||
const languageService = await project.getLanguageService(uri); | ||
const plugin = languageService.context.plugins[params.serviceId][1]; | ||
if (params.disabled) { | ||
languageService.context.disabledServicePlugins.add(plugin); | ||
} | ||
else { | ||
languageService.context.disabledServicePlugins.delete(plugin); | ||
} | ||
}); | ||
server.connection.onRequest(GetServicePluginsRequest.type, async params => { | ||
const uri = URI.parse(params.uri); | ||
const languageService = await project.getLanguageService(uri); | ||
const result: GetServicePluginsRequest.ResponseType = []; | ||
for (let pluginIndex = 0; pluginIndex < languageService.context.plugins.length; pluginIndex++) { | ||
const plugin = languageService.context.plugins[pluginIndex]; | ||
result.push({ | ||
id: pluginIndex, | ||
name: plugin[0].name, | ||
disabled: languageService.context.disabledServicePlugins.has(plugin[1]), | ||
features: Object.keys(plugin[1]), | ||
}); | ||
} | ||
return result; | ||
}); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { createUriMap, FileSystem } from '@volar/language-service'; | ||
import * as vscode from 'vscode-languageserver'; | ||
import { URI } from 'vscode-uri'; | ||
|
||
export function register( | ||
documents: ReturnType<typeof import('./textDocuments').register>, | ||
fileWatcher: ReturnType<typeof import('./fileWatcher').register> | ||
) { | ||
const providers = new Map<string, FileSystem>(); | ||
const readFileCache = createUriMap<ReturnType<FileSystem['readFile']>>(); | ||
const statCache = createUriMap<ReturnType<FileSystem['stat']>>(); | ||
const readDirectoryCache = createUriMap<ReturnType<FileSystem['readDirectory']>>(); | ||
|
||
documents.onDidSave(({ document }) => { | ||
const uri = URI.parse(document.uri); | ||
readFileCache.set(uri, document.getText()); | ||
statCache.delete(uri); | ||
}); | ||
|
||
fileWatcher.onDidChangeWatchedFiles(({ changes }) => { | ||
for (const change of changes) { | ||
const changeUri = URI.parse(change.uri); | ||
const dir = URI.parse(change.uri.substring(0, change.uri.lastIndexOf('/'))); | ||
if (change.type === vscode.FileChangeType.Deleted) { | ||
readFileCache.set(changeUri, undefined); | ||
statCache.set(changeUri, undefined); | ||
readDirectoryCache.delete(dir); | ||
} | ||
else if (change.type === vscode.FileChangeType.Changed) { | ||
readFileCache.delete(changeUri); | ||
statCache.delete(changeUri); | ||
} | ||
else if (change.type === vscode.FileChangeType.Created) { | ||
readFileCache.delete(changeUri); | ||
statCache.delete(changeUri); | ||
readDirectoryCache.delete(dir); | ||
} | ||
} | ||
}); | ||
|
||
return { | ||
readFile(uri: URI) { | ||
if (!readFileCache.has(uri)) { | ||
readFileCache.set(uri, providers.get(uri.scheme)?.readFile(uri)); | ||
} | ||
return readFileCache.get(uri)!; | ||
}, | ||
stat(uri: URI) { | ||
if (!statCache.has(uri)) { | ||
statCache.set(uri, providers.get(uri.scheme)?.stat(uri)); | ||
} | ||
return statCache.get(uri)!; | ||
}, | ||
readDirectory(uri: URI) { | ||
if (!readDirectoryCache.has(uri)) { | ||
readDirectoryCache.set(uri, providers.get(uri.scheme)?.readDirectory(uri) ?? []); | ||
} | ||
return readDirectoryCache.get(uri)!; | ||
}, | ||
install(scheme: string, provider: FileSystem) { | ||
providers.set(scheme, provider); | ||
}, | ||
}; | ||
} |
Oops, something went wrong.