plaxtony
Version:
Static code analysis of SC2 Galaxy Script
751 lines (668 loc) • 30.6 kB
text/typescript
import * as lsp from 'vscode-languageserver';
import { TextDocument } from 'vscode-languageserver-textdocument';
import * as Types from '../compiler/types';
import * as util from 'util';
import * as path from 'path';
import * as fs from 'fs-extra';
import { findAncestor } from '../compiler/utils';
import { Store, createTextDocumentFromFs, createTextDocumentFromUri } from './store';
import { getPositionOfLineAndCharacter, getLineAndCharacterOfPosition, getNodeRange, osNormalizePath } from './utils';
import { AbstractProvider, createProvider } from './provider';
import { DiagnosticsProvider, formatDiagnosticTotal } from './diagnostics';
import { NavigationProvider } from './navigation';
import { CompletionsProvider, CompletionConfig, CompletionFunctionExpand } from './completions';
import { SignaturesProvider } from './signatures';
import { DefinitionProvider } from './definitions';
import { HoverProvider } from './hover';
import { ReferencesProvider, ReferencesConfig } from './references';
import { RenameProvider } from './rename';
import { SC2Archive, SC2Workspace, resolveArchiveDirectory, resolveArchiveDependencyList, findSC2ArchiveDirectories } from '../sc2mod/archive';
import { setTimeout, clearTimeout } from 'timers';
import URI from 'vscode-uri';
import { logIt, logger } from '../common';
function translateNodeKind(node: Types.Node): lsp.SymbolKind {
switch (node.kind) {
case Types.SyntaxKind.VariableDeclaration:
const variable = <Types.VariableDeclaration>node;
const isConstant = variable.modifiers.some((value: Types.Modifier): boolean => {
return value.kind === Types.SyntaxKind.ConstKeyword;
});
return isConstant ? lsp.SymbolKind.Constant : lsp.SymbolKind.Variable;
case Types.SyntaxKind.FunctionDeclaration:
return lsp.SymbolKind.Function;
case Types.SyntaxKind.StructDeclaration:
return lsp.SymbolKind.Class;
default:
return lsp.SymbolKind.Field;
}
}
function translateDeclaratons(origDeclarations: Types.NamedDeclaration[]): lsp.SymbolInformation[] {
const symbols: lsp.SymbolInformation[] = [];
let kind: lsp.SymbolKind;
for (let node of origDeclarations) {
const sourceFile = <Types.SourceFile>findAncestor(node, (element: Types.Node): boolean => {
return element.kind === Types.SyntaxKind.SourceFile;
})
symbols.push({
kind: translateNodeKind(node),
name: node.name.name,
location: {
uri: sourceFile.fileName,
range: getNodeRange(node)
},
});
}
return symbols;
}
function mapFromObject(stuff: any) {
const m = new Map<string,string>();
Object.keys(stuff).forEach((key) => {
m.set(key, stuff[key]);
});
return m;
}
const fileChangeTypeNames: { [key: number]: string } = {
[lsp.FileChangeType.Created]: 'Created',
[lsp.FileChangeType.Changed]: 'Changed',
[lsp.FileChangeType.Deleted]: 'Deleted',
};
export enum MetadataLoadLevel {
None = 'None',
Core = 'Core',
Builtin = 'Builtin',
Default = 'Default',
}
export interface MetadataConfig {
loadLevel: keyof typeof MetadataLoadLevel;
localization: string;
}
export interface DataCatalogConfig {
enabled: boolean;
}
interface PlaxtonyConfig {
logLevel: string;
documentUpdateDelay: number;
documentDiagnosticsDelay: number | false;
archivePath: string;
dataPath: string;
fallbackDependency: string;
trace: {
server: string;
service: string;
};
metadata: MetadataConfig;
dataCatalog: DataCatalogConfig;
s2mod: {
sources: string[];
overrides: {};
extra: {};
};
completion: {
functionExpand: string;
};
references: ReferencesConfig;
}
interface DocumentUpdateRequest {
timer: NodeJS.Timer;
promise: Promise<boolean>;
content: string;
isDirty: boolean;
version: number;
}
interface InitializeParams extends lsp.InitializeParams {
initializationOptions?: {
defaultDataPath: string | null;
};
}
export class Server {
public connection: lsp.IConnection;
private store: Store = new Store();
private diagnosticsProvider: DiagnosticsProvider;
private navigationProvider: NavigationProvider;
private completionsProvider: CompletionsProvider;
private signaturesProvider: SignaturesProvider;
private definitionsProvider: DefinitionProvider;
private hoverProvider: HoverProvider;
private referenceProvider: ReferencesProvider;
private renameProvider: RenameProvider;
private documents = new lsp.TextDocuments(TextDocument);
private initParams: InitializeParams;
private indexing = false;
private ready = false;
private config: PlaxtonyConfig;
private documentUpdateRequests = new Map<string, DocumentUpdateRequest>();
private createProvider<T extends AbstractProvider>(cls: new () => T): T {
return createProvider(cls, this.store);
}
public createConnection() {
this.connection = lsp.createConnection();
this.diagnosticsProvider = this.createProvider(DiagnosticsProvider);
this.navigationProvider = this.createProvider(NavigationProvider);
this.completionsProvider = this.createProvider(CompletionsProvider);
this.signaturesProvider = this.createProvider(SignaturesProvider);
this.definitionsProvider = this.createProvider(DefinitionProvider);
this.hoverProvider = this.createProvider(HoverProvider);
this.referenceProvider = this.createProvider(ReferencesProvider);
this.renameProvider = this.createProvider(RenameProvider);
this.renameProvider.referencesProvider = this.referenceProvider;
this.documents.listen(this.connection);
this.documents.onDidChangeContent(this.onDidChangeContent.bind(this));
this.documents.onDidOpen(this.onDidOpen.bind(this));
this.documents.onDidClose(this.onDidClose.bind(this));
this.documents.onDidSave(this.onDidSave.bind(this));
this.connection.onDidChangeWatchedFiles(this.onDidChangeWatchedFiles.bind(this));
this.connection.onInitialize(this.onInitialize.bind(this));
this.connection.onInitialized(this.onInitialized.bind(this));
this.connection.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this));
this.connection.onCompletion(this.onCompletion.bind(this));
this.connection.onCompletionResolve(this.onCompletionResolve.bind(this));
this.connection.onDocumentSymbol(this.onDocumentSymbol.bind(this));
this.connection.onWorkspaceSymbol(this.onWorkspaceSymbol.bind(this));
this.connection.onSignatureHelp(this.onSignatureHelp.bind(this));
this.connection.onDefinition(this.onDefinition.bind(this));
this.connection.onHover(this.onHover.bind(this));
this.connection.onReferences(this.onReferences.bind(this));
this.connection.onRenameRequest(this.onRenameRequest.bind(this));
this.connection.onPrepareRename(this.onPrepareRename.bind(this));
this.connection.onRequest('document/checkRecursively', this.onDiagnoseDocumentRecursively.bind(this));
return this.connection;
}
public log(msg: string, ...params: any[]) {
this.connection.console.log(msg);
if (params.length) {
for (const p of params) {
this.connection.console.log(util.inspect(p, {
breakLength: 80,
compact: false,
}));
}
}
}
public showErrorMessage(msg: string) {
logger.error(`${msg}`);
this.connection.window.showErrorMessage(msg);
}
private async flushDocument(documentUri: string, isDirty = true) {
if (!this.ready) {
logger.info('Busy indexing..', { documentUri });
return false;
}
const req = this.documentUpdateRequests.get(documentUri);
if (!req) return;
if (req.promise) {
await req.promise;
}
else {
clearTimeout(req.timer);
req.isDirty = isDirty;
await this.onUpdateContent(documentUri, req);
}
}
protected async requestReindex() {
if (this.indexing) return;
const choice = await this.connection.window.showInformationMessage(
(`Workspace configuration has changed, reindex might be required. Would you like to do that now?`),
{
title: 'Yes',
},
{
title: 'No'
}
);
if (!choice || choice.title !== 'Yes') return;
await this.reindex();
}
()
private async reindex() {
let archivePath: string;
let workspace: SC2Workspace;
// signal begining of indexing
this.indexing = true;
this.connection.sendNotification('indexStart');
this.store.clear();
let projFolders = await this.connection.workspace.getWorkspaceFolders();
if (!projFolders) projFolders = [];
const archivePathToWsFolder = new Map<string, lsp.WorkspaceFolder>();
// attempt to determine active document (archivePath) for non-empty project workspace
if (projFolders.length) {
const s2archives: string[] = [];
if (this.config.archivePath) {
if (path.isAbsolute(this.config.archivePath)) {
if ((await fs.pathExists(archivePath))) {
archivePath = this.config.archivePath;
}
else {
this.showErrorMessage(`Specified archivePath '${this.config.archivePath}' resolved to '${archivePath}', but it doesn't exist.`);
}
}
else {
const archiveOsNormalPath = osNormalizePath(this.config.archivePath);
const candidates = (await Promise.all(projFolders.map(async (x) => {
const testedPath = path.join(URI.parse(x.uri).fsPath, archiveOsNormalPath);
const exists = await fs.pathExists(testedPath);
if (exists) {
return await fs.realpath(testedPath)
}
}))).filter(x => typeof x === 'string');
if (candidates.length) {
archivePath = candidates[0];
logger.info(`Configured archivePath '${archiveOsNormalPath}' resolved to ${archivePath}`);
if (candidates.length > 1) {
logger.info(`Complete list of candidates:`, ...candidates);
}
}
else {
this.showErrorMessage(`Specified archivePath '${archiveOsNormalPath}' couldn't be found.`);
}
}
}
if (!archivePath) {
const cfgFilesExclude: {[key: string]: boolean} = await this.connection.workspace.getConfiguration('files.exclude');
const excludePatterns = Object.entries(cfgFilesExclude).filter(x => x[1] === true).map(x => x[0]);
logger.info('searching workspace for s2archives..');
logger.verbose('exclude patterns', ...excludePatterns);
(await Promise.all(
projFolders.map(async wsFolder => {
return { wsFolder, foundArchivePaths: (await findSC2ArchiveDirectories(
URI.parse(wsFolder.uri).fsPath,
{
exclude: excludePatterns,
}
)) };
}))
).forEach(result => {
for (const currArchivePath of result.foundArchivePaths) {
archivePathToWsFolder.set(currArchivePath, result.wsFolder);
}
s2archives.push(...result.foundArchivePaths);
});
logger.info('s2archives in workspace', ...s2archives);
}
if (!archivePath) {
const s2maps = s2archives.filter(v => path.extname(v).toLowerCase() === '.sc2map');
logger.info(`s2maps in workspace`, ...s2maps);
if (s2maps.length === 1) {
archivePath = s2maps.pop();
}
else if (s2archives.length === 1) {
archivePath = s2archives.pop();
}
else {
this.connection.window.showInformationMessage(`Couldn't find applicable sc2map/sc2mod in the workspace. Set it manually under "sc2galaxy.archivePath" in projects configuration.`);
}
}
}
// build list of mod sources - directories with dependencies to lookup
const modSources: string[] = [];
if (this.config.dataPath) {
if (path.isAbsolute(this.config.dataPath)) {
modSources.push(this.config.dataPath);
}
else if (projFolders.length) {
for (const wsFolder of projFolders) {
const resolvedDataPath = path.join(URI.parse(wsFolder.uri).fsPath, this.config.dataPath);
if (await fs.pathExists(resolvedDataPath)) {
modSources.push(resolvedDataPath);
}
}
}
}
if (!modSources.length) {
modSources.push(this.initParams.initializationOptions.defaultDataPath);
}
modSources.push(...this.config.s2mod.sources);
logger.info(`Workspace discovery`, ...modSources, { archivePath });
// setup s2workspace
let wsArchive: SC2Archive;
if (archivePath) {
const matchingWsFolder = archivePathToWsFolder.get(archivePath);
const name = matchingWsFolder ? path.relative(URI.parse(matchingWsFolder.uri).fsPath, archivePath) : path.basename(archivePath);
wsArchive = new SC2Archive(name, archivePath);
logger.info(`wsArchive`, wsArchive.name, wsArchive.directory, matchingWsFolder);
}
this.connection.sendNotification('indexProgress', `Resolving dependencies..`);
const fallbackDep = new SC2Archive(this.config.fallbackDependency, await resolveArchiveDirectory(this.config.fallbackDependency, modSources));
const depLinks = await resolveArchiveDependencyList(wsArchive ? wsArchive : fallbackDep, modSources, {
overrides: mapFromObject(this.config.s2mod.overrides),
fallbackResolve: async (depName: string) => {
while (depName.length) {
for (const wsFolder of projFolders) {
const fsPath = URI.parse(wsFolder.uri).fsPath;
if (depName.toLowerCase() === wsFolder.name.toLowerCase()) {
return fsPath;
}
else {
const result = await resolveArchiveDirectory(depName, [fsPath]);
if (result) return result;
}
}
depName = depName.split('/').slice(1).join('/');
}
return void 0;
}
});
for (const [name, src] of mapFromObject(this.config.s2mod.extra)) {
if (!fs.existsSync(src)) {
this.showErrorMessage(`Extra archive [${name}] '${src}' doesn't exist. Skipping.`);
continue;
}
depLinks.list.push({
name: name,
src: src
});
}
if (depLinks.unresolvedNames.length > 0) {
this.showErrorMessage(
`Some SC2 archives couldn't be found [${depLinks.unresolvedNames.map((s) => `'${s}'`).join(', ')}]. By a result certain intellisense capabilities might not function properly.`
);
}
let depList = depLinks.list.map((item) => new SC2Archive(item.name, item.src));
if (!wsArchive && !depList.find((item) => item.name === fallbackDep.name)) {
depList.push(fallbackDep);
}
workspace = new SC2Workspace(wsArchive, depList);
logger.info('Resolved archives', ...workspace.allArchives.map(item => {
return `${item.name} => ${item.directory}`;
}));
// process metadata files etc.
this.connection.sendNotification('indexProgress', 'Indexing trigger libraries and data catalogs..');
await this.store.updateS2Workspace(workspace);
await this.store.rebuildS2Metadata(this.config.metadata, this.config.dataCatalog);
// process .galaxy files in the workspace
this.connection.sendNotification('indexProgress', `Indexing Galaxy files..`);
for (const modArchive of workspace.allArchives) {
for (const extSrc of await modArchive.findFiles('**/*.galaxy')) {
await this.syncSourceFile({document: createTextDocumentFromFs(path.join(modArchive.directory, extSrc))});
}
}
// almost done
this.connection.sendNotification('indexProgress', 'Finalizing..');
// apply overdue updates to files issued during initial indexing
for (const documentUri of this.documentUpdateRequests.keys()) {
await this.flushDocument(documentUri);
}
// signal done
this.indexing = false;
this.ready = true;
this.connection.sendNotification('indexEnd');
}
({ level: 'verbose', profiling: false, argsDump: true, resDump: true })
private onInitialize(params: lsp.InitializeParams): lsp.InitializeResult {
this.initParams = params;
return {
capabilities: {
workspace: {
workspaceFolders: {
supported: true,
changeNotifications: true,
},
},
textDocumentSync: {
change: lsp.TextDocumentSyncKind.Incremental,
openClose: true,
},
documentSymbolProvider: true,
workspaceSymbolProvider: true,
completionProvider: {
triggerCharacters: ['.', '/'],
resolveProvider: true,
},
signatureHelpProvider: {
triggerCharacters: ['(', ','],
},
definitionProvider: true,
hoverProvider: true,
referencesProvider: true,
renameProvider: {
prepareProvider: true,
},
}
};
}
({ level: 'verbose', profiling: false, argsDump: true })
private onInitialized(params: lsp.InitializedParams) {
if (this.initParams.capabilities.workspace.workspaceFolders) {
this.connection.workspace.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders.bind(this));
}
}
({ level: 'verbose', profiling: false, argsDump: ev => ev.settings.sc2galaxy })
private onDidChangeConfiguration(ev: lsp.DidChangeConfigurationParams) {
const newConfig = <PlaxtonyConfig>ev.settings.sc2galaxy;
let reindexRequired = false;
let firstInit = !this.config;
if (
!this.config ||
this.config.archivePath !== newConfig.archivePath ||
this.config.dataPath !== newConfig.dataPath ||
this.config.fallbackDependency !== newConfig.fallbackDependency ||
(JSON.stringify(this.config.trace) !== JSON.stringify(newConfig.trace)) ||
(JSON.stringify(this.config.metadata) !== JSON.stringify(newConfig.metadata)) ||
(JSON.stringify(this.config.dataCatalog) !== JSON.stringify(newConfig.dataCatalog)) ||
(JSON.stringify(this.config.s2mod) !== JSON.stringify(newConfig.s2mod))
) {
logger.warn('Config changed, reindex required');
reindexRequired = true;
}
this.config = newConfig;
switch (this.config.completion.functionExpand) {
case "None":
this.completionsProvider.config.functionExpand = CompletionFunctionExpand.None;
break;
case "Parenthesis":
this.completionsProvider.config.functionExpand = CompletionFunctionExpand.Parenthesis;
break;
case "ArgumentsNull":
this.completionsProvider.config.functionExpand = CompletionFunctionExpand.ArgumentsNull;
break;
case "ArgumentsDefault":
this.completionsProvider.config.functionExpand = CompletionFunctionExpand.ArgumentsDefault;
break;
}
this.referenceProvider.config = this.config.references;
if (firstInit) {
this.reindex();
}
else if (reindexRequired) {
this.requestReindex();
}
}
({ profiling: false, argsDump: true })
private onDidChangeWorkspaceFolders(ev: lsp.WorkspaceFoldersChangeEvent) {
this.requestReindex();
}
({ level: 'verbose', profiling: false, argsDump: ev => {
return { uri: ev.document.uri, ver: ev.document.version };
}})
private async onDidChangeContent(ev: lsp.TextDocumentChangeEvent<TextDocument>) {
let req = this.documentUpdateRequests.get(ev.document.uri);
if (req) {
if (req.promise) {
await req.promise;
}
else {
if (req.timer) {
clearTimeout(req.timer);
}
this.documentUpdateRequests.delete(ev.document.uri);
}
req = null;
}
if (!req) {
req = <DocumentUpdateRequest>{
content: ev.document.getText(),
timer: null,
promise: null,
isDirty: true,
version: ev.document.version,
};
}
if (!this.indexing && this.ready) {
req.timer = setTimeout(this.onUpdateContent.bind(this, ev.document.uri, req), this.config.documentUpdateDelay);
}
this.documentUpdateRequests.set(ev.document.uri, req);
}
({ level: 'verbose', argsDump: (docUri: string, req: DocumentUpdateRequest) => {
return { uri: docUri, ver: req.version };
}})
private async onUpdateContent(documentUri: string, req: DocumentUpdateRequest) {
req.promise = new Promise((resolve) => {
this.store.updateDocument(<TextDocument>{
uri: documentUri,
getText: () => {
return req.content;
}
});
this.documentUpdateRequests.delete(documentUri);
const diagDelay = this.config.documentDiagnosticsDelay ? this.config.documentDiagnosticsDelay : 1;
if (this.config.documentDiagnosticsDelay !== false || !req.isDirty) {
setTimeout(this.onDiagnostics.bind(this, documentUri, req), req.isDirty ? diagDelay : 1);
}
resolve(true);
});
await req.promise;
}
({ level: 'verbose', argsDump: (docUri: string, req: DocumentUpdateRequest) => {
return { uri: docUri, ver: req.version };
}})
private onDiagnostics(documentUri: string, req: DocumentUpdateRequest) {
if (this.documentUpdateRequests.has(documentUri)) return;
if (this.documents.keys().indexOf(documentUri) === -1) return;
if (this.documents.get(documentUri).version > req.version) return;
if (this.store.isUriInWorkspace(documentUri)) {
this.diagnosticsProvider.checkFile(documentUri);
}
this.connection.sendDiagnostics({
uri: documentUri,
diagnostics: this.diagnosticsProvider.provideDiagnostics(documentUri),
});
}
({ level: 'verbose', profiling: false, argsDump: ev => ev.document.uri })
private onDidOpen(ev: lsp.TextDocumentChangeEvent<TextDocument>) {
this.store.openDocuments.add(ev.document.uri);
}
({ level: 'verbose', profiling: false, argsDump: ev => ev.document.uri })
private onDidClose(ev: lsp.TextDocumentChangeEvent<TextDocument>) {
this.store.openDocuments.delete(ev.document.uri);
if (!this.store.isUriInWorkspace(ev.document.uri)) {
this.store.removeDocument(ev.document.uri);
logger.verbose('removed from store', ev.document.uri);
}
this.connection.sendDiagnostics({
uri: ev.document.uri,
diagnostics: [],
});
}
({ level: 'verbose', profiling: false, argsDump: ev => ev.document.uri })
private async onDidSave(ev: lsp.TextDocumentChangeEvent<TextDocument>) {
await this.flushDocument(ev.document.uri, true);
}
({ level: 'verbose', profiling: false })
private async onDidChangeWatchedFiles(ev: lsp.DidChangeWatchedFilesParams) {
for (const x of ev.changes) {
const xUri = URI.parse(x.uri);
if (xUri.scheme !== 'file') continue;
if (xUri.fsPath.match(/sc2\w+\.(temp|orig)/gi)) continue;
if (!this.store.isUriInWorkspace(x.uri)) continue;
logger.verbose(`${fileChangeTypeNames[x.type]} '${x.uri}'`);
switch (x.type) {
case lsp.FileChangeType.Created:
case lsp.FileChangeType.Changed:
{
if (!this.store.openDocuments.has(x.uri)) {
this.syncSourceFile({document: createTextDocumentFromUri(x.uri)});
}
break;
}
case lsp.FileChangeType.Deleted:
{
this.store.removeDocument(x.uri)
break;
}
}
}
}
({ level: 'verbose', argsDump: (ev: lsp.TextDocumentChangeEvent<TextDocument>) => {
return { uri: ev.document.uri, ver: ev.document.version };
}})
private syncSourceFile(ev: lsp.TextDocumentChangeEvent<TextDocument>) {
this.store.updateDocument(ev.document);
}
private async onCompletion(params: lsp.TextDocumentPositionParams) {
if (!this.store.documents.has(params.textDocument.uri)) return;
await this.flushDocument(params.textDocument.uri);
let context: lsp.CompletionContext = null;
try {
if (this.initParams.capabilities.textDocument.completion.contextSupport) {
context = (<lsp.CompletionParams>params).context;
}
}
catch (e) {}
return this.completionsProvider.getCompletionsAt(
params.textDocument.uri,
getPositionOfLineAndCharacter(this.store.documents.get(params.textDocument.uri), params.position.line, params.position.character),
context
);
}
private onCompletionResolve(params: lsp.CompletionItem): lsp.CompletionItem {
return this.completionsProvider.resolveCompletion(params);
}
private onDocumentSymbol(params: lsp.DocumentSymbolParams): lsp.SymbolInformation[] {
if (!this.ready) return null;
return translateDeclaratons(this.navigationProvider.getDocumentSymbols(params.textDocument.uri));
}
private onWorkspaceSymbol(params: lsp.WorkspaceSymbolParams): lsp.SymbolInformation[] {
if (!this.ready) return null;
return translateDeclaratons(this.navigationProvider.getWorkspaceSymbols(params.query));
}
private async onSignatureHelp(params: lsp.TextDocumentPositionParams): Promise<lsp.SignatureHelp> {
if (!this.store.documents.has(params.textDocument.uri)) return null;
await this.flushDocument(params.textDocument.uri);
return this.signaturesProvider.getSignatureAt(
params.textDocument.uri,
getPositionOfLineAndCharacter(this.store.documents.get(params.textDocument.uri), params.position.line, params.position.character)
);
}
private async onDefinition(params: lsp.TextDocumentPositionParams): Promise<lsp.DefinitionLink[]> {
if (!this.store.documents.has(params.textDocument.uri)) return null;
await this.flushDocument(params.textDocument.uri);
return this.definitionsProvider.getDefinitionAt(
params.textDocument.uri,
getPositionOfLineAndCharacter(
this.store.documents.get(params.textDocument.uri), params.position.line, params.position.character)
);
}
private async onHover(params: lsp.TextDocumentPositionParams): Promise<lsp.Hover> {
await this.flushDocument(params.textDocument.uri);
return this.hoverProvider.getHoverAt(params);
}
private async onReferences(params: lsp.ReferenceParams): Promise<lsp.Location[]> {
await this.flushDocument(params.textDocument.uri);
return this.referenceProvider.onReferences(params);
}
private async onRenameRequest(params: lsp.RenameParams) {
await this.flushDocument(params.textDocument.uri);
return this.renameProvider.onRenameRequest(params);
}
private async onPrepareRename(params: lsp.TextDocumentPositionParams) {
await this.flushDocument(params.textDocument.uri);
const r = this.renameProvider.onPrepareRename(params);
if (r && (<any>r).range) {
setTimeout(() => {
this.onRenamePrefetch(params);
}, 5);
}
return r;
}
private async onRenamePrefetch(params: lsp.TextDocumentPositionParams) {
await this.flushDocument(params.textDocument.uri);
return this.renameProvider.prefetchLocations();
}
private async onDiagnoseDocumentRecursively(params: lsp.TextDocumentIdentifier) {
await this.flushDocument(params.uri);
const dtotal = this.diagnosticsProvider.checkFileRecursively(params.uri);
return formatDiagnosticTotal(dtotal);
}
}
export function createServer() {
return (new Server()).createConnection();
}