Only use the sync TSServer when on a file:// workspace

This commit is contained in:
Orta 2021-11-22 18:26:17 +00:00
parent f0f1f86033
commit 10a40964ca
3 changed files with 57 additions and 33 deletions

View file

@ -16,7 +16,8 @@ import { normalize, sep } from 'path';
import * as ts from 'typescript';
import { getSemanticTokens, getSemanticTokenLegend } from './javascriptSemanticTokens';
import { statSync } from 'fs';
import { RequestService } from '../requests';
import { NodeRequestService } from '../node/nodeFs';
const JS_WORD_REGEX = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g;
@ -32,13 +33,13 @@ function deschemeURI(uri: string) {
// Both \ and / must be escaped in regular expressions
newPath = newPath.replace(new RegExp('\\' + sep, 'g'), '/');
if (process.platform !== 'win32') { return newPath; }
if (process.platform !== 'win32') return newPath;
// Windows URIs come in like '/c%3A/Users/orta/dev/...', we need to switch it to 'c:/Users/orta/dev/...'
return newPath.slice(1).replace('%3A', ':');
}
function getLanguageServiceHost(scriptKind: ts.ScriptKind) {
function getLanguageServiceHost(scriptKind: ts.ScriptKind, fs: NodeRequestService) {
const compilerOptions: ts.CompilerOptions = { allowNonTsExtensions: true, allowJs: true, lib: ['lib.es6.d.ts'], target: ts.ScriptTarget.Latest, moduleResolution: ts.ModuleResolutionKind.Classic, experimentalDecorators: false };
let currentTextDocument = TextDocument.create('init', 'javascript', 1, '');
@ -64,15 +65,15 @@ function getLanguageServiceHost(scriptKind: ts.ScriptKind) {
return '1';
}
// Unsure how this could occur, but better to not raise with statSync
if (!ts.sys.fileExists(fileName)) { return '1'; }
if (currentWorkspace && !ts.sys.fileExists(fileName)) { return '1'; }
// Use mtime from the fs
return String(statSync(fileName).mtimeMs);
return String(fs.statSync(fileName).mtime);
},
getScriptSnapshot: (fileName: string) => {
let text = '';
if (fileName === deschemeURI(currentTextDocument.uri)) {
text = currentTextDocument.getText();
} else if (ts.sys.fileExists(fileName)) {
} else if (currentWorkspace && ts.sys.fileExists(fileName)) {
text = ts.sys.readFile(fileName, 'utf8')!;
} else {
text = libs.loadLibrary(fileName);
@ -83,16 +84,19 @@ function getLanguageServiceHost(scriptKind: ts.ScriptKind) {
getChangeRange: () => undefined
};
},
// Realistically the TSServer can only run on file:// workspaces, because it is entirely sync API
// and so `currentWorkspace` is only set when there is at least one root folder in a workspace with a file:// uri
getCurrentDirectory: () => {
const workspace = currentWorkspace && currentWorkspace.folders.find(ws => deschemeURI(currentTextDocument.uri).startsWith(deschemeURI(ws.uri)));
return workspace ? deschemeURI(workspace.uri) : '';
},
getDefaultLibFileName: (_options: ts.CompilerOptions) => 'es6',
fileExists: ts.sys.fileExists,
readFile: ts.sys.readFile,
readDirectory: ts.sys.readDirectory,
directoryExists: ts.sys.directoryExists,
getDirectories: ts.sys.getDirectories,
fileExists: currentWorkspace && ts.sys.fileExists,
readFile: currentWorkspace && ts.sys.readFile,
readDirectory: currentWorkspace && ts.sys.readDirectory,
directoryExists: currentWorkspace && ts.sys.directoryExists,
getDirectories: currentWorkspace && ts.sys.getDirectories,
};
return ts.createLanguageService(host);
@ -100,7 +104,7 @@ function getLanguageServiceHost(scriptKind: ts.ScriptKind) {
return {
async getLanguageService(jsDocument: TextDocument, workspace: Workspace): Promise<ts.LanguageService> {
currentTextDocument = jsDocument;
currentWorkspace = workspace;
if (workspace.folders.find(f => f.uri.startsWith('file://'))) currentWorkspace = workspace;
return jsLanguageService;
},
getCompilationSettings() {
@ -113,10 +117,10 @@ function getLanguageServiceHost(scriptKind: ts.ScriptKind) {
}
export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocumentRegions>, languageId: 'javascript' | 'typescript', workspace: Workspace): LanguageMode {
export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocumentRegions>, languageId: 'javascript' | 'typescript', workspace: Workspace, fs: RequestService): LanguageMode {
let jsDocuments = getLanguageModelCache<TextDocument>(10, 60, document => documentRegions.get(document).getEmbeddedDocument(languageId));
const host = getLanguageServiceHost(languageId === 'javascript' ? ts.ScriptKind.JS : ts.ScriptKind.TS);
const host = getLanguageServiceHost(languageId === 'javascript' ? ts.ScriptKind.JS : ts.ScriptKind.TS, fs as NodeRequestService);
let globalSettings: Settings = {};
return {
@ -146,7 +150,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
const filePath = deschemeURI(jsDocument.uri);
let offset = jsDocument.offsetAt(position);
let completions = jsLanguageService.getCompletionsAtPosition(filePath, offset, { includeExternalModuleExports: false, includeInsertTextCompletions: false, importModuleSpecifierEnding: 'js' });
let completions = jsLanguageService.getCompletionsAtPosition(filePath, offset, { includeExternalModuleExports: false, includeInsertTextCompletions: false });
if (!completions) {
return { isIncomplete: false, items: [] };

View file

@ -112,8 +112,8 @@ export function getLanguageModes(supportedLanguages: { [languageId: string]: boo
modes['css'] = getCSSMode(cssLanguageService, documentRegions, workspace);
}
if (supportedLanguages['javascript']) {
modes['javascript'] = getJavaScriptMode(documentRegions, 'javascript', workspace);
modes['typescript'] = getJavaScriptMode(documentRegions, 'typescript', workspace);
modes['javascript'] = getJavaScriptMode(documentRegions, 'javascript', workspace, requestService);
modes['typescript'] = getJavaScriptMode(documentRegions, 'typescript', workspace, requestService);
}
return {
async updateDataProviders(dataProviders: IHTMLDataProvider[]): Promise<void> {

View file

@ -8,13 +8,41 @@ import { URI as Uri } from 'vscode-uri';
import * as fs from 'fs';
import { FileType } from 'vscode-css-languageservice';
import { FileStat } from 'vscode-html-languageservice';
export function getNodeFSRequestService(): RequestService {
/**
* This extension is for the TSServer in the JavaScript mode which
* require sync access to stat's mtime due to TSServer API limitations
*/
export interface NodeRequestService extends RequestService {
statSync(location: string): FileStat
}
export function getNodeFSRequestService(): NodeRequestService {
function ensureFileUri(location: string) {
if (getScheme(location) !== 'file') {
throw new Error('fileRequestService can only handle file URLs');
}
}
const fsStatToFileStat = (stats: fs.Stats) => {
let type = FileType.Unknown;
if (stats.isFile()) {
type = FileType.File;
} else if (stats.isDirectory()) {
type = FileType.Directory;
} else if (stats.isSymbolicLink()) {
type = FileType.SymbolicLink;
}
return {
type,
ctime: stats.ctime.getTime(),
mtime: stats.mtime.getTime(),
size: stats.size
};
};
return {
getContent(location: string, encoding?: string) {
ensureFileUri(location);
@ -42,24 +70,16 @@ export function getNodeFSRequestService(): RequestService {
}
}
let type = FileType.Unknown;
if (stats.isFile()) {
type = FileType.File;
} else if (stats.isDirectory()) {
type = FileType.Directory;
} else if (stats.isSymbolicLink()) {
type = FileType.SymbolicLink;
}
c({
type,
ctime: stats.ctime.getTime(),
mtime: stats.mtime.getTime(),
size: stats.size
});
c(fsStatToFileStat(stats));
});
});
},
statSync(location: string) {
ensureFileUri(location);
const uri = Uri.parse(location);
const stats = fs.statSync(uri.fsPath);
return fsStatToFileStat(stats);
},
readDirectory(location: string) {
ensureFileUri(location);
return new Promise((c, e) => {