mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-22 09:38:38 +01:00
Merge remote-tracking branch 'origin/master' into pr/limerickgds/51557
This commit is contained in:
@@ -4,13 +4,14 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
import TypeScriptServiceClientHost from './typeScriptServiceClientHost';
|
||||
import { Command } from './utils/commandManager';
|
||||
import { Lazy } from './utils/lazy';
|
||||
import { openOrCreateConfigFile, isImplicitProjectConfigFile } from './utils/tsconfig';
|
||||
import { isImplicitProjectConfigFile, openOrCreateConfigFile } from './utils/tsconfig';
|
||||
import { nulToken } from './utils/cancellation';
|
||||
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
||||
@@ -121,7 +122,7 @@ async function goToProjectConfig(
|
||||
|
||||
const file = client.toPath(resource);
|
||||
// TSServer errors when 'projectInfo' is invoked on a non js/ts file
|
||||
if (!file || !clientHost.handles(resource)) {
|
||||
if (!file || !await clientHost.handles(resource)) {
|
||||
vscode.window.showWarningMessage(
|
||||
localize(
|
||||
'typescript.projectConfigUnsupportedFile',
|
||||
@@ -131,7 +132,7 @@ async function goToProjectConfig(
|
||||
|
||||
let res: protocol.ProjectInfoResponse | undefined = undefined;
|
||||
try {
|
||||
res = await client.execute('projectInfo', { file, needFileNameList: false } as protocol.ProjectInfoRequestArgs);
|
||||
res = await client.execute('projectInfo', { file, needFileNameList: false }, nulToken);
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import LogDirectoryProvider from './utils/logDirectoryProvider';
|
||||
import ManagedFileContextManager from './utils/managedFileContext';
|
||||
import { getContributedTypeScriptServerPlugins, TypeScriptServerPlugin } from './utils/plugins';
|
||||
import * as ProjectStatus from './utils/projectStatus';
|
||||
import { flatten } from './utils/arrays';
|
||||
|
||||
|
||||
export function activate(
|
||||
@@ -32,7 +33,14 @@ export function activate(
|
||||
context.subscriptions.push(new TypeScriptTaskProviderManager(lazyClientHost.map(x => x.serviceClient)));
|
||||
context.subscriptions.push(new LanguageConfigurationManager());
|
||||
|
||||
const supportedLanguage = [].concat.apply([], standardLanguageDescriptions.map(x => x.modeIds).concat(plugins.map(x => x.languages)));
|
||||
import('./features/tsconfig').then(module => {
|
||||
context.subscriptions.push(module.register());
|
||||
});
|
||||
|
||||
const supportedLanguage = flatten([
|
||||
...standardLanguageDescriptions.map(x => x.modeIds),
|
||||
...plugins.map(x => x.languages)
|
||||
]);
|
||||
function didOpenTextDocument(textDocument: vscode.TextDocument): boolean {
|
||||
if (isSupportedDocument(supportedLanguage, textDocument)) {
|
||||
openListener.dispose();
|
||||
|
||||
@@ -3,18 +3,18 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CodeLensProvider, CodeLens, CancellationToken, TextDocument, Range, Uri, Position, Event, EventEmitter } from 'vscode';
|
||||
import * as vscode from 'vscode';
|
||||
import * as Proto from '../protocol';
|
||||
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
import { escapeRegExp } from '../utils/regexp';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
|
||||
export class ReferencesCodeLens extends CodeLens {
|
||||
|
||||
export class ReferencesCodeLens extends vscode.CodeLens {
|
||||
constructor(
|
||||
public document: Uri,
|
||||
public document: vscode.Uri,
|
||||
public file: string,
|
||||
range: Range
|
||||
range: vscode.Range
|
||||
) {
|
||||
super(range);
|
||||
}
|
||||
@@ -26,7 +26,7 @@ export class CachedNavTreeResponse {
|
||||
private document: string = '';
|
||||
|
||||
public execute(
|
||||
document: TextDocument,
|
||||
document: vscode.TextDocument,
|
||||
f: () => Promise<Proto.NavTreeResponse>
|
||||
) {
|
||||
if (this.matches(document)) {
|
||||
@@ -36,12 +36,12 @@ export class CachedNavTreeResponse {
|
||||
return this.update(document, f());
|
||||
}
|
||||
|
||||
private matches(document: TextDocument): boolean {
|
||||
private matches(document: vscode.TextDocument): boolean {
|
||||
return this.version === document.version && this.document === document.uri.toString();
|
||||
}
|
||||
|
||||
private update(
|
||||
document: TextDocument,
|
||||
document: vscode.TextDocument,
|
||||
response: Promise<Proto.NavTreeResponse>
|
||||
): Promise<Proto.NavTreeResponse> {
|
||||
this.response = response;
|
||||
@@ -51,19 +51,19 @@ export class CachedNavTreeResponse {
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class TypeScriptBaseCodeLensProvider implements CodeLensProvider {
|
||||
private onDidChangeCodeLensesEmitter = new EventEmitter<void>();
|
||||
export abstract class TypeScriptBaseCodeLensProvider implements vscode.CodeLensProvider {
|
||||
private onDidChangeCodeLensesEmitter = new vscode.EventEmitter<void>();
|
||||
|
||||
public constructor(
|
||||
protected client: ITypeScriptServiceClient,
|
||||
private cachedResponse: CachedNavTreeResponse
|
||||
) { }
|
||||
|
||||
public get onDidChangeCodeLenses(): Event<void> {
|
||||
public get onDidChangeCodeLenses(): vscode.Event<void> {
|
||||
return this.onDidChangeCodeLensesEmitter.event;
|
||||
}
|
||||
|
||||
async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
|
||||
async provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.CodeLens[]> {
|
||||
const filepath = this.client.toPath(document.uri);
|
||||
if (!filepath) {
|
||||
return [];
|
||||
@@ -76,7 +76,7 @@ export abstract class TypeScriptBaseCodeLensProvider implements CodeLensProvider
|
||||
}
|
||||
|
||||
const tree = response.body;
|
||||
const referenceableSpans: Range[] = [];
|
||||
const referenceableSpans: vscode.Range[] = [];
|
||||
if (tree && tree.childItems) {
|
||||
tree.childItems.forEach(item => this.walkNavTree(document, item, null, referenceableSpans));
|
||||
}
|
||||
@@ -87,16 +87,16 @@ export abstract class TypeScriptBaseCodeLensProvider implements CodeLensProvider
|
||||
}
|
||||
|
||||
protected abstract extractSymbol(
|
||||
document: TextDocument,
|
||||
document: vscode.TextDocument,
|
||||
item: Proto.NavigationTree,
|
||||
parent: Proto.NavigationTree | null
|
||||
): Range | null;
|
||||
): vscode.Range | null;
|
||||
|
||||
private walkNavTree(
|
||||
document: TextDocument,
|
||||
document: vscode.TextDocument,
|
||||
item: Proto.NavigationTree,
|
||||
parent: Proto.NavigationTree | null,
|
||||
results: Range[]
|
||||
results: vscode.Range[]
|
||||
): void {
|
||||
if (!item) {
|
||||
return;
|
||||
@@ -109,16 +109,17 @@ export abstract class TypeScriptBaseCodeLensProvider implements CodeLensProvider
|
||||
|
||||
(item.childItems || []).forEach(child => this.walkNavTree(document, child, item, results));
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: TS currently requires the position for 'references 'to be inside of the identifer
|
||||
* Massage the range to make sure this is the case
|
||||
*/
|
||||
protected getSymbolRange(document: TextDocument, item: Proto.NavigationTree): Range | null {
|
||||
protected getSymbolRange(document: vscode.TextDocument, item: Proto.NavigationTree): vscode.Range | null {
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TS 3.0+ provides a span for just the symbol
|
||||
if (item.nameSpan) {
|
||||
return typeConverters.Range.fromTextSpan((item as any).nameSpan);
|
||||
}
|
||||
|
||||
// In older versions, we have to calculate this manually. See #23924
|
||||
const span = item.spans && item.spans[0];
|
||||
if (!span) {
|
||||
return null;
|
||||
@@ -130,8 +131,8 @@ export abstract class TypeScriptBaseCodeLensProvider implements CodeLensProvider
|
||||
const identifierMatch = new RegExp(`^(.*?(\\b|\\W))${escapeRegExp(item.text || '')}(\\b|\\W)`, 'gm');
|
||||
const match = identifierMatch.exec(text);
|
||||
const prefixLength = match ? match.index + match[1].length : 0;
|
||||
const startOffset = document.offsetAt(new Position(range.start.line, range.start.character)) + prefixLength;
|
||||
return new Range(
|
||||
const startOffset = document.offsetAt(new vscode.Position(range.start.line, range.start.character)) + prefixLength;
|
||||
return new vscode.Range(
|
||||
document.positionAt(startOffset),
|
||||
document.positionAt(startOffset + item.text.length));
|
||||
}
|
||||
|
||||
@@ -4,25 +4,21 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import { CancellationTokenSource, Disposable, TextDocument, TextDocumentChangeEvent, TextDocumentContentChangeEvent, Uri, workspace, EventEmitter } from 'vscode';
|
||||
import * as vscode from 'vscode';
|
||||
import * as Proto from '../protocol';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import { Delayer } from '../utils/async';
|
||||
import { disposeAll } from '../utils/dispose';
|
||||
import * as languageModeIds from '../utils/languageModeIds';
|
||||
import API from '../utils/api';
|
||||
import { memoize } from '../utils/memoize';
|
||||
import { getTempFile } from '../utils/temp';
|
||||
import { Delayer } from '../utils/async';
|
||||
import { Disposable } from '../utils/dispose';
|
||||
import * as languageModeIds from '../utils/languageModeIds';
|
||||
import { ResourceMap } from '../utils/resourceMap';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
|
||||
enum BufferKind {
|
||||
TypeScript = 1,
|
||||
JavaScript = 2,
|
||||
}
|
||||
|
||||
interface IDiagnosticRequestor {
|
||||
requestDiagnostic(resource: Uri): void;
|
||||
}
|
||||
|
||||
function mode2ScriptKind(mode: string): 'TS' | 'TSX' | 'JS' | 'JSX' | undefined {
|
||||
switch (mode) {
|
||||
case languageModeIds.typescript: return 'TS';
|
||||
@@ -36,9 +32,8 @@ function mode2ScriptKind(mode: string): 'TS' | 'TSX' | 'JS' | 'JSX' | undefined
|
||||
class SyncedBuffer {
|
||||
|
||||
constructor(
|
||||
private readonly document: TextDocument,
|
||||
private readonly document: vscode.TextDocument,
|
||||
public readonly filepath: string,
|
||||
private readonly diagnosticRequestor: IDiagnosticRequestor,
|
||||
private readonly client: ITypeScriptServiceClient
|
||||
) { }
|
||||
|
||||
@@ -68,10 +63,10 @@ class SyncedBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
this.client.execute('open', args, false);
|
||||
this.client.executeWithoutWaitingForResponse('open', args);
|
||||
}
|
||||
|
||||
public get resource(): Uri {
|
||||
public get resource(): vscode.Uri {
|
||||
return this.document.uri;
|
||||
}
|
||||
|
||||
@@ -96,105 +91,123 @@ class SyncedBuffer {
|
||||
const args: Proto.FileRequestArgs = {
|
||||
file: this.filepath
|
||||
};
|
||||
this.client.execute('close', args, false);
|
||||
this.client.executeWithoutWaitingForResponse('close', args);
|
||||
}
|
||||
|
||||
public onContentChanged(events: TextDocumentContentChangeEvent[]): void {
|
||||
public onContentChanged(events: vscode.TextDocumentContentChangeEvent[]): void {
|
||||
for (const { range, text } of events) {
|
||||
const args: Proto.ChangeRequestArgs = {
|
||||
file: this.filepath,
|
||||
line: range.start.line + 1,
|
||||
offset: range.start.character + 1,
|
||||
endLine: range.end.line + 1,
|
||||
endOffset: range.end.character + 1,
|
||||
insertString: text
|
||||
insertString: text,
|
||||
...typeConverters.Range.toFormattingRequestArgs(this.filepath, range)
|
||||
};
|
||||
this.client.execute('change', args, false);
|
||||
this.client.executeWithoutWaitingForResponse('change', args);
|
||||
}
|
||||
this.diagnosticRequestor.requestDiagnostic(this.document.uri);
|
||||
}
|
||||
}
|
||||
|
||||
class SyncedBufferMap {
|
||||
private readonly _map = new Map<string, SyncedBuffer>();
|
||||
|
||||
constructor(
|
||||
private readonly _normalizePath: (resource: Uri) => string | null
|
||||
) { }
|
||||
|
||||
public has(resource: Uri): boolean {
|
||||
const file = this.toKey(resource);
|
||||
return !!file && this._map.has(file);
|
||||
}
|
||||
|
||||
public get(resource: Uri): SyncedBuffer | undefined {
|
||||
const file = this.toKey(resource);
|
||||
return file ? this._map.get(file) : undefined;
|
||||
}
|
||||
class SyncedBufferMap extends ResourceMap<SyncedBuffer> {
|
||||
|
||||
public getForPath(filePath: string): SyncedBuffer | undefined {
|
||||
return this.get(Uri.file(filePath));
|
||||
}
|
||||
|
||||
public set(resource: Uri, buffer: SyncedBuffer) {
|
||||
const file = this.toKey(resource);
|
||||
if (file) {
|
||||
this._map.set(file, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public delete(resource: Uri): void {
|
||||
const file = this.toKey(resource);
|
||||
if (file) {
|
||||
this._map.delete(file);
|
||||
}
|
||||
return this.get(vscode.Uri.file(filePath));
|
||||
}
|
||||
|
||||
public get allBuffers(): Iterable<SyncedBuffer> {
|
||||
return this._map.values();
|
||||
}
|
||||
|
||||
public get allResources(): Iterable<string> {
|
||||
return this._map.keys();
|
||||
}
|
||||
|
||||
private toKey(resource: Uri): string | null {
|
||||
return this._normalizePath(resource);
|
||||
return this.values;
|
||||
}
|
||||
}
|
||||
|
||||
class PendingDiagnostics extends ResourceMap<number> {
|
||||
public getOrderedFileSet(): ResourceMap<void> {
|
||||
const orderedResources = Array.from(this.entries)
|
||||
.sort((a, b) => a.value - b.value)
|
||||
.map(entry => entry.resource);
|
||||
|
||||
export default class BufferSyncSupport {
|
||||
const map = new ResourceMap<void>();
|
||||
for (const resource of orderedResources) {
|
||||
map.set(resource, void 0);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
class GetErrRequest {
|
||||
|
||||
public static executeGetErrRequest(
|
||||
client: ITypeScriptServiceClient,
|
||||
files: ResourceMap<void>,
|
||||
onDone: () => void
|
||||
) {
|
||||
const token = new vscode.CancellationTokenSource();
|
||||
return new GetErrRequest(client, files, token, onDone);
|
||||
}
|
||||
|
||||
private _done: boolean = false;
|
||||
|
||||
private constructor(
|
||||
client: ITypeScriptServiceClient,
|
||||
public readonly files: ResourceMap<void>,
|
||||
private readonly _token: vscode.CancellationTokenSource,
|
||||
onDone: () => void
|
||||
) {
|
||||
const args: Proto.GeterrRequestArgs = {
|
||||
delay: 0,
|
||||
files: Array.from(files.entries)
|
||||
.map(entry => client.normalizedPath(entry.resource))
|
||||
.filter(x => !!x) as string[]
|
||||
};
|
||||
|
||||
client.executeAsync('geterr', args, _token.token)
|
||||
.then(undefined, () => { })
|
||||
.then(() => {
|
||||
if (this._done) {
|
||||
return;
|
||||
}
|
||||
this._done = true;
|
||||
onDone();
|
||||
});
|
||||
}
|
||||
|
||||
public cancel(): any {
|
||||
if (!this._done) {
|
||||
this._token.cancel();
|
||||
}
|
||||
|
||||
this._token.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export default class BufferSyncSupport extends Disposable {
|
||||
|
||||
private readonly client: ITypeScriptServiceClient;
|
||||
|
||||
private _validateJavaScript: boolean = true;
|
||||
private _validateTypeScript: boolean = true;
|
||||
private readonly modeIds: Set<string>;
|
||||
private readonly disposables: Disposable[] = [];
|
||||
private readonly syncedBuffers: SyncedBufferMap;
|
||||
|
||||
private readonly pendingDiagnostics = new Map<string, number>();
|
||||
private readonly pendingDiagnostics: PendingDiagnostics;
|
||||
private readonly diagnosticDelayer: Delayer<any>;
|
||||
private pendingGetErr: { request: Promise<any>, files: string[], token: CancellationTokenSource } | undefined;
|
||||
private pendingGetErr: GetErrRequest | undefined;
|
||||
private listening: boolean = false;
|
||||
|
||||
constructor(
|
||||
client: ITypeScriptServiceClient,
|
||||
modeIds: string[]
|
||||
) {
|
||||
super();
|
||||
this.client = client;
|
||||
this.modeIds = new Set<string>(modeIds);
|
||||
|
||||
this.diagnosticDelayer = new Delayer<any>(300);
|
||||
|
||||
this.syncedBuffers = new SyncedBufferMap(path => this.normalizePath(path));
|
||||
const pathNormalizer = (path: vscode.Uri) => this.client.normalizedPath(path);
|
||||
this.syncedBuffers = new SyncedBufferMap(pathNormalizer);
|
||||
this.pendingDiagnostics = new PendingDiagnostics(pathNormalizer);
|
||||
|
||||
this.updateConfiguration();
|
||||
workspace.onDidChangeConfiguration(() => this.updateConfiguration(), null);
|
||||
vscode.workspace.onDidChangeConfiguration(this.updateConfiguration, this, this._disposables);
|
||||
}
|
||||
|
||||
private readonly _onDelete = new EventEmitter<Uri>();
|
||||
private readonly _onDelete = this._register(new vscode.EventEmitter<vscode.Uri>());
|
||||
public readonly onDelete = this._onDelete.event;
|
||||
|
||||
public listen(): void {
|
||||
@@ -202,22 +215,22 @@ export default class BufferSyncSupport {
|
||||
return;
|
||||
}
|
||||
this.listening = true;
|
||||
workspace.onDidOpenTextDocument(this.openTextDocument, this, this.disposables);
|
||||
workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, this.disposables);
|
||||
workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, this.disposables);
|
||||
workspace.textDocuments.forEach(this.openTextDocument, this);
|
||||
vscode.workspace.onDidOpenTextDocument(this.openTextDocument, this, this._disposables);
|
||||
vscode.workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, this._disposables);
|
||||
vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, this._disposables);
|
||||
vscode.workspace.textDocuments.forEach(this.openTextDocument, this);
|
||||
}
|
||||
|
||||
public handles(resource: Uri): boolean {
|
||||
public handles(resource: vscode.Uri): boolean {
|
||||
return this.syncedBuffers.has(resource);
|
||||
}
|
||||
|
||||
public toResource(filePath: string): Uri {
|
||||
public toResource(filePath: string): vscode.Uri {
|
||||
const buffer = this.syncedBuffers.getForPath(filePath);
|
||||
if (buffer) {
|
||||
return buffer.resource;
|
||||
}
|
||||
return Uri.file(filePath);
|
||||
return vscode.Uri.file(filePath);
|
||||
}
|
||||
|
||||
public reOpenDocuments(): void {
|
||||
@@ -226,12 +239,7 @@ export default class BufferSyncSupport {
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
disposeAll(this.disposables);
|
||||
this._onDelete.dispose();
|
||||
}
|
||||
|
||||
public openTextDocument(document: TextDocument): void {
|
||||
public openTextDocument(document: vscode.TextDocument): void {
|
||||
if (!this.modeIds.has(document.languageId)) {
|
||||
return;
|
||||
}
|
||||
@@ -245,17 +253,18 @@ export default class BufferSyncSupport {
|
||||
return;
|
||||
}
|
||||
|
||||
const syncedBuffer = new SyncedBuffer(document, filepath, this, this.client);
|
||||
const syncedBuffer = new SyncedBuffer(document, filepath, this.client);
|
||||
this.syncedBuffers.set(resource, syncedBuffer);
|
||||
syncedBuffer.open();
|
||||
this.requestDiagnostic(resource);
|
||||
this.requestDiagnostic(syncedBuffer);
|
||||
}
|
||||
|
||||
public closeResource(resource: Uri): void {
|
||||
public closeResource(resource: vscode.Uri): void {
|
||||
const syncedBuffer = this.syncedBuffers.get(resource);
|
||||
if (!syncedBuffer) {
|
||||
return;
|
||||
}
|
||||
this.pendingDiagnostics.delete(resource);
|
||||
this.syncedBuffers.delete(resource);
|
||||
syncedBuffer.close();
|
||||
if (!fs.existsSync(resource.fsPath)) {
|
||||
@@ -264,121 +273,113 @@ export default class BufferSyncSupport {
|
||||
}
|
||||
}
|
||||
|
||||
private onDidCloseTextDocument(document: TextDocument): void {
|
||||
public interuptGetErr<R>(f: () => R): R {
|
||||
if (!this.pendingGetErr) {
|
||||
return f();
|
||||
}
|
||||
|
||||
this.pendingGetErr.cancel();
|
||||
this.pendingGetErr = undefined;
|
||||
const result = f();
|
||||
this.triggerDiagnostics();
|
||||
return result;
|
||||
}
|
||||
|
||||
private onDidCloseTextDocument(document: vscode.TextDocument): void {
|
||||
this.closeResource(document.uri);
|
||||
}
|
||||
|
||||
private onDidChangeTextDocument(e: TextDocumentChangeEvent): void {
|
||||
private onDidChangeTextDocument(e: vscode.TextDocumentChangeEvent): void {
|
||||
const syncedBuffer = this.syncedBuffers.get(e.document.uri);
|
||||
if (!syncedBuffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
syncedBuffer.onContentChanged(e.contentChanges);
|
||||
if (this.pendingGetErr) {
|
||||
this.pendingGetErr.token.cancel();
|
||||
const didTrigger = this.requestDiagnostic(syncedBuffer);
|
||||
|
||||
if (!didTrigger && this.pendingGetErr) {
|
||||
// In this case we always want to re-trigger all diagnostics
|
||||
this.pendingGetErr.cancel();
|
||||
this.pendingGetErr = undefined;
|
||||
this.triggerDiagnostics();
|
||||
}
|
||||
}
|
||||
|
||||
public requestAllDiagnostics() {
|
||||
for (const buffer of this.syncedBuffers.allBuffers) {
|
||||
if (this.shouldValidate(buffer)) {
|
||||
this.pendingDiagnostics.set(buffer.filepath, Date.now());
|
||||
this.pendingDiagnostics.set(buffer.resource, Date.now());
|
||||
}
|
||||
}
|
||||
this.diagnosticDelayer.trigger(() => {
|
||||
this.sendPendingDiagnostics();
|
||||
}, 200);
|
||||
this.triggerDiagnostics();
|
||||
}
|
||||
|
||||
public getErr(resources: Uri[]): any {
|
||||
public getErr(resources: vscode.Uri[]): any {
|
||||
const handledResources = resources.filter(resource => this.handles(resource));
|
||||
if (!handledResources.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const resource of handledResources) {
|
||||
const file = this.client.normalizedPath(resource);
|
||||
if (file) {
|
||||
this.pendingDiagnostics.set(file, Date.now());
|
||||
}
|
||||
this.pendingDiagnostics.set(resource, Date.now());
|
||||
}
|
||||
|
||||
this.diagnosticDelayer.trigger(() => {
|
||||
this.sendPendingDiagnostics();
|
||||
}, 200);
|
||||
this.triggerDiagnostics();
|
||||
}
|
||||
|
||||
public requestDiagnostic(resource: Uri): void {
|
||||
const file = this.client.normalizedPath(resource);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pendingDiagnostics.set(file, Date.now());
|
||||
const buffer = this.syncedBuffers.get(resource);
|
||||
if (!buffer || !this.shouldValidate(buffer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let delay = 300;
|
||||
const lineCount = buffer.lineCount;
|
||||
delay = Math.min(Math.max(Math.ceil(lineCount / 20), 300), 800);
|
||||
private triggerDiagnostics(delay: number = 200) {
|
||||
this.diagnosticDelayer.trigger(() => {
|
||||
this.sendPendingDiagnostics();
|
||||
}, delay);
|
||||
}
|
||||
|
||||
public hasPendingDiagnostics(resource: Uri): boolean {
|
||||
const file = this.client.normalizedPath(resource);
|
||||
return !file || this.pendingDiagnostics.has(file);
|
||||
private requestDiagnostic(buffer: SyncedBuffer): boolean {
|
||||
if (!this.shouldValidate(buffer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.pendingDiagnostics.set(buffer.resource, Date.now());
|
||||
|
||||
const delay = Math.min(Math.max(Math.ceil(buffer.lineCount / 20), 300), 800);
|
||||
this.triggerDiagnostics(delay);
|
||||
return true;
|
||||
}
|
||||
|
||||
public hasPendingDiagnostics(resource: vscode.Uri): boolean {
|
||||
return this.pendingDiagnostics.has(resource);
|
||||
}
|
||||
|
||||
private sendPendingDiagnostics(): void {
|
||||
const files = new Set(Array.from(this.pendingDiagnostics.entries())
|
||||
.sort((a, b) => a[1] - b[1])
|
||||
.map(entry => entry[0]));
|
||||
const orderedFileSet = this.pendingDiagnostics.getOrderedFileSet();
|
||||
|
||||
// Add all open TS buffers to the geterr request. They might be visible
|
||||
for (const file of this.syncedBuffers.allResources) {
|
||||
if (!this.pendingDiagnostics.get(file)) {
|
||||
files.add(file);
|
||||
for (const buffer of this.syncedBuffers.values) {
|
||||
orderedFileSet.set(buffer.resource, void 0);
|
||||
}
|
||||
|
||||
if (orderedFileSet.size) {
|
||||
if (this.pendingGetErr) {
|
||||
this.pendingGetErr.cancel();
|
||||
|
||||
for (const file of this.pendingGetErr.files.entries) {
|
||||
orderedFileSet.set(file.resource, void 0);
|
||||
}
|
||||
}
|
||||
|
||||
const getErr = this.pendingGetErr = GetErrRequest.executeGetErrRequest(this.client, orderedFileSet, () => {
|
||||
if (this.pendingGetErr === getErr) {
|
||||
this.pendingGetErr = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (this.pendingGetErr) {
|
||||
for (const file of this.pendingGetErr.files) {
|
||||
files.add(file);
|
||||
}
|
||||
}
|
||||
|
||||
if (files.size) {
|
||||
const fileList = Array.from(files);
|
||||
const args: Proto.GeterrRequestArgs = {
|
||||
delay: 0,
|
||||
files: fileList
|
||||
};
|
||||
const token = new CancellationTokenSource();
|
||||
|
||||
const getErr = this.pendingGetErr = {
|
||||
request: this.client.executeAsync('geterr', args, token.token)
|
||||
.then(undefined, () => { })
|
||||
.then(() => {
|
||||
if (this.pendingGetErr === getErr) {
|
||||
this.pendingGetErr = undefined;
|
||||
}
|
||||
}),
|
||||
files: fileList,
|
||||
token
|
||||
};
|
||||
}
|
||||
this.pendingDiagnostics.clear();
|
||||
}
|
||||
|
||||
private updateConfiguration() {
|
||||
const jsConfig = workspace.getConfiguration('javascript', null);
|
||||
const tsConfig = workspace.getConfiguration('typescript', null);
|
||||
const jsConfig = vscode.workspace.getConfiguration('javascript', null);
|
||||
const tsConfig = vscode.workspace.getConfiguration('typescript', null);
|
||||
|
||||
this._validateJavaScript = jsConfig.get<boolean>('validate.enable', true);
|
||||
this._validateTypeScript = tsConfig.get<boolean>('validate.enable', true);
|
||||
@@ -394,40 +395,4 @@ export default class BufferSyncSupport {
|
||||
return this._validateTypeScript;
|
||||
}
|
||||
}
|
||||
|
||||
private normalizePath(path: Uri): string | null {
|
||||
const key = this.client.normalizedPath(path);
|
||||
if (!key) {
|
||||
return key;
|
||||
}
|
||||
|
||||
return this.isCaseInsensitivePath(key) ? key.toLowerCase() : key;
|
||||
}
|
||||
|
||||
private isCaseInsensitivePath(path: string) {
|
||||
if (isWindowsPath(path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return path[0] === '/' && this.onIsCaseInsenitiveFileSystem;
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get onIsCaseInsenitiveFileSystem() {
|
||||
if (process.platform === 'win32') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const temp = getTempFile('typescript-case-check');
|
||||
fs.writeFileSync(temp, '');
|
||||
return fs.existsSync(temp.toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
function isWindowsPath(path: string): boolean {
|
||||
return /^[a-zA-Z]:\\/.test(path);
|
||||
}
|
||||
|
||||
@@ -4,23 +4,30 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import TypingsStatus from '../utils/typingsStatus';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as Proto from '../protocol';
|
||||
import * as PConst from '../protocol.const';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import API from '../utils/api';
|
||||
import { nulToken } from '../utils/cancellation';
|
||||
import { applyCodeAction } from '../utils/codeAction';
|
||||
import { Command, CommandManager } from '../utils/commandManager';
|
||||
import { ConfigurationDependentRegistration } from '../utils/dependentRegistration';
|
||||
import { memoize } from '../utils/memoize';
|
||||
import * as Previewer from '../utils/previewer';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
import { applyCodeAction } from '../utils/codeAction';
|
||||
import { CommandManager, Command } from '../utils/commandManager';
|
||||
import TypingsStatus from '../utils/typingsStatus';
|
||||
import FileConfigurationManager from './fileConfigurationManager';
|
||||
import API from '../utils/api';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
||||
interface CommitCharactersSettings {
|
||||
readonly isNewIdentifierLocation: boolean;
|
||||
readonly isInValidCommitCharacterContext: boolean;
|
||||
readonly enableCallCompletions: boolean;
|
||||
}
|
||||
|
||||
class MyCompletionItem extends vscode.CompletionItem {
|
||||
public readonly useCodeSnippet: boolean;
|
||||
|
||||
@@ -29,15 +36,16 @@ class MyCompletionItem extends vscode.CompletionItem {
|
||||
public readonly document: vscode.TextDocument,
|
||||
line: string,
|
||||
public readonly tsEntry: Proto.CompletionEntry,
|
||||
enableDotCompletions: boolean,
|
||||
useCodeSnippetsOnMethodSuggest: boolean
|
||||
useCodeSnippetsOnMethodSuggest: boolean,
|
||||
public readonly commitCharactersSettings: CommitCharactersSettings
|
||||
) {
|
||||
super(tsEntry.name);
|
||||
super(tsEntry.name, MyCompletionItem.convertKind(tsEntry.kind));
|
||||
|
||||
if (tsEntry.isRecommended) {
|
||||
// Make sure isRecommended property always comes first
|
||||
// https://github.com/Microsoft/vscode/issues/40325
|
||||
this.sortText = '\0' + tsEntry.sortText;
|
||||
this.sortText = tsEntry.sortText;
|
||||
this.preselect = true;
|
||||
} else if (tsEntry.source) {
|
||||
// De-prioritze auto-imports
|
||||
// https://github.com/Microsoft/vscode/issues/40311
|
||||
@@ -46,11 +54,8 @@ class MyCompletionItem extends vscode.CompletionItem {
|
||||
this.sortText = tsEntry.sortText;
|
||||
}
|
||||
|
||||
this.kind = MyCompletionItem.convertKind(tsEntry.kind);
|
||||
this.position = position;
|
||||
this.commitCharacters = MyCompletionItem.getCommitCharacters(enableDotCompletions, !useCodeSnippetsOnMethodSuggest, tsEntry.kind);
|
||||
this.useCodeSnippet = useCodeSnippetsOnMethodSuggest && (this.kind === vscode.CompletionItemKind.Function || this.kind === vscode.CompletionItemKind.Method);
|
||||
|
||||
if (tsEntry.replacementSpan) {
|
||||
this.range = typeConverters.Range.fromTextSpan(tsEntry.replacementSpan);
|
||||
}
|
||||
@@ -81,19 +86,22 @@ class MyCompletionItem extends vscode.CompletionItem {
|
||||
}
|
||||
this.label += '?';
|
||||
}
|
||||
this.resolveRange(line);
|
||||
}
|
||||
|
||||
public resolve(): void {
|
||||
if (!this.range) {
|
||||
// Try getting longer, prefix based range for completions that span words
|
||||
const wordRange = this.document.getWordRangeAtPosition(this.position);
|
||||
const text = this.document.getText(new vscode.Range(this.position.line, Math.max(0, this.position.character - this.label.length), this.position.line, this.position.character)).toLowerCase();
|
||||
const entryName = this.label.toLowerCase();
|
||||
for (let i = entryName.length; i >= 0; --i) {
|
||||
if (text.endsWith(entryName.substr(0, i)) && (!wordRange || wordRange.start.character > this.position.character - i)) {
|
||||
this.range = new vscode.Range(this.position.line, Math.max(0, this.position.character - i), this.position.line, this.position.character);
|
||||
break;
|
||||
}
|
||||
private resolveRange(line: string): void {
|
||||
if (this.range) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try getting longer, prefix based range for completions that span words
|
||||
const wordRange = this.document.getWordRangeAtPosition(this.position);
|
||||
const text = line.slice(Math.max(0, this.position.character - this.label.length), this.position.character).toLowerCase();
|
||||
const entryName = this.label.toLowerCase();
|
||||
for (let i = entryName.length; i >= 0; --i) {
|
||||
if (text.endsWith(entryName.substr(0, i)) && (!wordRange || wordRange.start.character > this.position.character - i)) {
|
||||
this.range = new vscode.Range(this.position.line, Math.max(0, this.position.character - i), this.position.line, this.position.character);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,7 +140,6 @@ class MyCompletionItem extends vscode.CompletionItem {
|
||||
case PConst.Kind.interface:
|
||||
return vscode.CompletionItemKind.Interface;
|
||||
case PConst.Kind.warning:
|
||||
case PConst.Kind.file:
|
||||
case PConst.Kind.script:
|
||||
return vscode.CompletionItemKind.File;
|
||||
case PConst.Kind.directory:
|
||||
@@ -143,12 +150,14 @@ class MyCompletionItem extends vscode.CompletionItem {
|
||||
return vscode.CompletionItemKind.Property;
|
||||
}
|
||||
|
||||
private static getCommitCharacters(
|
||||
enableDotCompletions: boolean,
|
||||
enableCallCompletions: boolean,
|
||||
kind: string
|
||||
): string[] | undefined {
|
||||
switch (kind) {
|
||||
@memoize
|
||||
public get commitCharacters(): string[] | undefined {
|
||||
if (this.commitCharactersSettings.isNewIdentifierLocation || !this.commitCharactersSettings.isInValidCommitCharacterContext) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const commitCharacters: string[] = [];
|
||||
switch (this.tsEntry.kind) {
|
||||
case PConst.Kind.memberGetAccessor:
|
||||
case PConst.Kind.memberSetAccessor:
|
||||
case PConst.Kind.constructSignature:
|
||||
@@ -156,7 +165,9 @@ class MyCompletionItem extends vscode.CompletionItem {
|
||||
case PConst.Kind.indexSignature:
|
||||
case PConst.Kind.enum:
|
||||
case PConst.Kind.interface:
|
||||
return enableDotCompletions ? ['.'] : undefined;
|
||||
commitCharacters.push('.', ';');
|
||||
|
||||
break;
|
||||
|
||||
case PConst.Kind.module:
|
||||
case PConst.Kind.alias:
|
||||
@@ -168,10 +179,14 @@ class MyCompletionItem extends vscode.CompletionItem {
|
||||
case PConst.Kind.class:
|
||||
case PConst.Kind.function:
|
||||
case PConst.Kind.memberFunction:
|
||||
return enableDotCompletions ? (enableCallCompletions ? ['.', '('] : ['.']) : undefined;
|
||||
case PConst.Kind.keyword:
|
||||
commitCharacters.push('.', ',', ';');
|
||||
if (this.commitCharactersSettings.enableCallCompletions) {
|
||||
commitCharacters.push('(');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return commitCharacters.length === 0 ? undefined : commitCharacters;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +204,7 @@ class ApplyCompletionCodeActionCommand implements Command {
|
||||
}
|
||||
|
||||
if (codeActions.length === 1) {
|
||||
return applyCodeAction(this.client, codeActions[0]);
|
||||
return applyCodeAction(this.client, codeActions[0], nulToken);
|
||||
}
|
||||
|
||||
interface MyQuickPickItem extends vscode.QuickPickItem {
|
||||
@@ -214,33 +229,39 @@ class ApplyCompletionCodeActionCommand implements Command {
|
||||
if (!action) {
|
||||
return false;
|
||||
}
|
||||
return applyCodeAction(this.client, action);
|
||||
return applyCodeAction(this.client, action, nulToken);
|
||||
}
|
||||
}
|
||||
|
||||
interface CompletionConfiguration {
|
||||
readonly useCodeSnippetsOnMethodSuggest: boolean;
|
||||
readonly nameSuggestions: boolean;
|
||||
readonly quickSuggestionsForPaths: boolean;
|
||||
readonly pathSuggestions: boolean;
|
||||
readonly autoImportSuggestions: boolean;
|
||||
}
|
||||
|
||||
namespace CompletionConfiguration {
|
||||
export const useCodeSnippetsOnMethodSuggest = 'useCodeSnippetsOnMethodSuggest';
|
||||
export const nameSuggestions = 'nameSuggestions';
|
||||
export const quickSuggestionsForPaths = 'quickSuggestionsForPaths';
|
||||
export const autoImportSuggestions = 'autoImportSuggestions.enabled';
|
||||
export const nameSuggestions = 'suggest.names';
|
||||
export const nameSuggestions_deprecated = 'nameSuggestions';
|
||||
export const pathSuggestions = 'suggest.paths';
|
||||
export const autoImportSuggestions = 'suggest.autoImports';
|
||||
export const autoImportSuggestions_deprecated = 'autoImportSuggestions.enabled';
|
||||
|
||||
export function getConfigurationForResource(
|
||||
modeId: string,
|
||||
resource: vscode.Uri
|
||||
): CompletionConfiguration {
|
||||
// TS settings are shared by both JS and TS.
|
||||
const config = vscode.workspace.getConfiguration(modeId, resource);
|
||||
|
||||
// Deprecated TS settings that were shared by both JS and TS.
|
||||
const typeScriptConfig = vscode.workspace.getConfiguration('typescript', resource);
|
||||
|
||||
return {
|
||||
useCodeSnippetsOnMethodSuggest: typeScriptConfig.get<boolean>(CompletionConfiguration.useCodeSnippetsOnMethodSuggest, false),
|
||||
quickSuggestionsForPaths: typeScriptConfig.get<boolean>(CompletionConfiguration.quickSuggestionsForPaths, true),
|
||||
autoImportSuggestions: typeScriptConfig.get<boolean>(CompletionConfiguration.autoImportSuggestions, true),
|
||||
nameSuggestions: vscode.workspace.getConfiguration('javascript', resource).get(CompletionConfiguration.nameSuggestions, true)
|
||||
pathSuggestions: config.get<boolean>(CompletionConfiguration.pathSuggestions, true),
|
||||
autoImportSuggestions: config.get<boolean>(CompletionConfiguration.autoImportSuggestions, typeScriptConfig.get<boolean>(CompletionConfiguration.autoImportSuggestions_deprecated, true)),
|
||||
nameSuggestions: config.get<boolean>(CompletionConfiguration.nameSuggestions, vscode.workspace.getConfiguration('javascript', resource).get(CompletionConfiguration.nameSuggestions_deprecated, true))
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -251,6 +272,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider
|
||||
|
||||
constructor(
|
||||
private readonly client: ITypeScriptServiceClient,
|
||||
private readonly modeId: string,
|
||||
private readonly typingsStatus: TypingsStatus,
|
||||
private readonly fileConfigurationManager: FileConfigurationManager,
|
||||
commandManager: CommandManager
|
||||
@@ -263,7 +285,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider
|
||||
position: vscode.Position,
|
||||
token: vscode.CancellationToken,
|
||||
context: vscode.CompletionContext
|
||||
): Promise<vscode.CompletionItem[]> {
|
||||
): Promise<vscode.CompletionItem[] | null> {
|
||||
if (this.typingsStatus.isAcquiringTypings) {
|
||||
return Promise.reject<vscode.CompletionItem[]>({
|
||||
label: localize(
|
||||
@@ -277,17 +299,17 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider
|
||||
|
||||
const file = this.client.toPath(document.uri);
|
||||
if (!file) {
|
||||
return [];
|
||||
return null;
|
||||
}
|
||||
|
||||
const line = document.lineAt(position.line);
|
||||
const completionConfiguration = CompletionConfiguration.getConfigurationForResource(document.uri);
|
||||
const completionConfiguration = CompletionConfiguration.getConfigurationForResource(this.modeId, document.uri);
|
||||
|
||||
if (!this.shouldTrigger(context, completionConfiguration, line, position)) {
|
||||
return [];
|
||||
if (!this.shouldTrigger(context, line, position)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
await this.fileConfigurationManager.ensureConfigurationForDocument(document, token);
|
||||
await this.client.interuptGetErr(() => this.fileConfigurationManager.ensureConfigurationForDocument(document, token));
|
||||
|
||||
const args: Proto.CompletionsRequestArgs = {
|
||||
...typeConverters.Position.toFileLocationRequestArgs(file, position),
|
||||
@@ -296,32 +318,35 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider
|
||||
triggerCharacter: context.triggerCharacter as Proto.CompletionsTriggerCharacter
|
||||
};
|
||||
|
||||
let msg: Proto.CompletionEntry[] | undefined = undefined;
|
||||
let isNewIdentifierLocation = true;
|
||||
let msg: ReadonlyArray<Proto.CompletionEntry> | undefined = undefined;
|
||||
try {
|
||||
const response = await this.client.execute('completions', args, token);
|
||||
msg = response.body;
|
||||
if (!msg) {
|
||||
return [];
|
||||
if (this.client.apiVersion.gte(API.v300)) {
|
||||
const { body } = await this.client.interuptGetErr(() => this.client.execute('completionInfo', args, token));
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
isNewIdentifierLocation = body.isNewIdentifierLocation;
|
||||
msg = body.entries;
|
||||
} else {
|
||||
const { body } = await this.client.interuptGetErr(() => this.client.execute('completions', args, token));
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
msg = body;
|
||||
}
|
||||
} catch {
|
||||
return [];
|
||||
return null;
|
||||
}
|
||||
|
||||
const enableDotCompletions = this.shouldEnableDotCompletions(document, position);
|
||||
|
||||
const completionItems: vscode.CompletionItem[] = [];
|
||||
for (const element of msg) {
|
||||
if (element.kind === PConst.Kind.warning && !completionConfiguration.nameSuggestions) {
|
||||
continue;
|
||||
}
|
||||
if (!completionConfiguration.autoImportSuggestions && element.hasAction) {
|
||||
continue;
|
||||
}
|
||||
const item = new MyCompletionItem(position, document, line.text, element, enableDotCompletions, completionConfiguration.useCodeSnippetsOnMethodSuggest);
|
||||
completionItems.push(item);
|
||||
}
|
||||
|
||||
return completionItems;
|
||||
const isInValidCommitCharacterContext = this.isInValidCommitCharacterContext(document, position);
|
||||
return msg
|
||||
.filter(entry => !shouldExcludeCompletionEntry(entry, completionConfiguration))
|
||||
.map(entry => new MyCompletionItem(position, document, line.text, entry, completionConfiguration.useCodeSnippetsOnMethodSuggest, {
|
||||
isNewIdentifierLocation,
|
||||
isInValidCommitCharacterContext,
|
||||
enableCallCompletions: !completionConfiguration.useCodeSnippetsOnMethodSuggest
|
||||
}));
|
||||
}
|
||||
|
||||
public async resolveCompletionItem(
|
||||
@@ -337,8 +362,6 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider
|
||||
return undefined;
|
||||
}
|
||||
|
||||
item.resolve();
|
||||
|
||||
const args: Proto.CompletionDetailsRequestArgs = {
|
||||
...typeConverters.Position.toFileLocationRequestArgs(filepath, item.position),
|
||||
entryNames: [
|
||||
@@ -346,14 +369,14 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider
|
||||
]
|
||||
};
|
||||
|
||||
let response: Proto.CompletionDetailsResponse;
|
||||
let details: Proto.CompletionEntryDetails[] | undefined;
|
||||
try {
|
||||
response = await this.client.execute('completionEntryDetails', args, token);
|
||||
const { body } = await this.client.execute('completionEntryDetails', args, token);
|
||||
details = body;
|
||||
} catch {
|
||||
return item;
|
||||
}
|
||||
|
||||
const details = response.body;
|
||||
if (!details || !details.length || !details[0]) {
|
||||
return item;
|
||||
}
|
||||
@@ -366,7 +389,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider
|
||||
item.additionalTextEdits = additionalTextEdits;
|
||||
|
||||
if (detail && item.useCodeSnippet) {
|
||||
const shouldCompleteFunction = await this.isValidFunctionCompletionContext(filepath, item.position);
|
||||
const shouldCompleteFunction = await this.isValidFunctionCompletionContext(filepath, item.position, token);
|
||||
if (shouldCompleteFunction) {
|
||||
item.insertText = this.snippetForFunctionCall(item, detail);
|
||||
}
|
||||
@@ -425,7 +448,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider
|
||||
};
|
||||
}
|
||||
|
||||
private shouldEnableDotCompletions(
|
||||
private isInValidCommitCharacterContext(
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position
|
||||
): boolean {
|
||||
@@ -436,7 +459,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider
|
||||
const preText = document.getText(new vscode.Range(
|
||||
position.line, 0,
|
||||
position.line, position.character - 1));
|
||||
return preText.match(/[a-z_$\)\]\}]\s*$/ig) !== null;
|
||||
return preText.match(/(^|[a-z_$\(\)\[\]\{\}])\s*$/ig) !== null;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -444,46 +467,39 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider
|
||||
|
||||
private shouldTrigger(
|
||||
context: vscode.CompletionContext,
|
||||
config: CompletionConfiguration,
|
||||
line: vscode.TextLine,
|
||||
position: vscode.Position
|
||||
): boolean {
|
||||
if ((context.triggerCharacter === '"' || context.triggerCharacter === '\'') && !this.client.apiVersion.gte(API.v290)) {
|
||||
if (!config.quickSuggestionsForPaths) {
|
||||
return false;
|
||||
if (context.triggerCharacter && !this.client.apiVersion.gte(API.v290)) {
|
||||
if ((context.triggerCharacter === '"' || context.triggerCharacter === '\'')) {
|
||||
// make sure we are in something that looks like the start of an import
|
||||
const pre = line.text.slice(0, position.character);
|
||||
if (!pre.match(/\b(from|import)\s*["']$/) && !pre.match(/\b(import|require)\(['"]$/)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// make sure we are in something that looks like the start of an import
|
||||
const pre = line.text.slice(0, position.character);
|
||||
if (!pre.match(/\b(from|import)\s*["']$/) && !pre.match(/\b(import|require)\(['"]$/)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (context.triggerCharacter === '/') {
|
||||
if (!config.quickSuggestionsForPaths) {
|
||||
return false;
|
||||
if (context.triggerCharacter === '/') {
|
||||
// make sure we are in something that looks like an import path
|
||||
const pre = line.text.slice(0, position.character);
|
||||
if (!pre.match(/\b(from|import)\s*["'][^'"]*$/) && !pre.match(/\b(import|require)\(['"][^'"]*$/)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// make sure we are in something that looks like an import path
|
||||
const pre = line.text.slice(0, position.character);
|
||||
if (!pre.match(/\b(from|import)\s*["'][^'"]*$/) && !pre.match(/\b(import|require)\(['"][^'"]*$/)) {
|
||||
if (context.triggerCharacter === '@') {
|
||||
// make sure we are in something that looks like the start of a jsdoc comment
|
||||
const pre = line.text.slice(0, position.character);
|
||||
if (!pre.match(/^\s*\*[ ]?@/) && !pre.match(/\/\*\*+[ ]?@/)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (context.triggerCharacter === '<') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (context.triggerCharacter === '@' && !this.client.apiVersion.gte(API.v290)) {
|
||||
// make sure we are in something that looks like the start of a jsdoc comment
|
||||
const pre = line.text.slice(0, position.character);
|
||||
if (!pre.match(/^\s*\*[ ]?@/) && !pre.match(/\/\*\*+[ ]?@/)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (context.triggerCharacter === '<') {
|
||||
return this.client.apiVersion.gte(API.v290);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -504,14 +520,14 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider
|
||||
|
||||
private async isValidFunctionCompletionContext(
|
||||
filepath: string,
|
||||
position: vscode.Position
|
||||
position: vscode.Position,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<boolean> {
|
||||
// Workaround for https://github.com/Microsoft/TypeScript/issues/12677
|
||||
// Don't complete function calls inside of destructive assigments or imports
|
||||
try {
|
||||
const infoResponse = await this.client.execute('quickinfo', typeConverters.Position.toFileLocationRequestArgs(filepath, position));
|
||||
const info = infoResponse.body;
|
||||
switch (info && info.kind) {
|
||||
const { body } = await this.client.execute('quickinfo', typeConverters.Position.toFileLocationRequestArgs(filepath, position), token);
|
||||
switch (body && body.kind) {
|
||||
case 'var':
|
||||
case 'let':
|
||||
case 'const':
|
||||
@@ -583,15 +599,28 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider
|
||||
}
|
||||
}
|
||||
|
||||
function shouldExcludeCompletionEntry(
|
||||
element: Proto.CompletionEntry,
|
||||
completionConfiguration: CompletionConfiguration
|
||||
) {
|
||||
return (
|
||||
(!completionConfiguration.nameSuggestions && element.kind === PConst.Kind.warning)
|
||||
|| (!completionConfiguration.pathSuggestions &&
|
||||
(element.kind === PConst.Kind.directory || element.kind === PConst.Kind.script || element.kind === PConst.Kind.externalModuleName))
|
||||
|| (!completionConfiguration.autoImportSuggestions && element.hasAction)
|
||||
);
|
||||
}
|
||||
|
||||
export function register(
|
||||
selector: vscode.DocumentSelector,
|
||||
modeId: string,
|
||||
client: ITypeScriptServiceClient,
|
||||
typingsStatus: TypingsStatus,
|
||||
fileConfigurationManager: FileConfigurationManager,
|
||||
commandManager: CommandManager,
|
||||
) {
|
||||
return vscode.languages.registerCompletionItemProvider(selector,
|
||||
new TypeScriptCompletionItemProvider(client, typingsStatus, fileConfigurationManager, commandManager),
|
||||
...TypeScriptCompletionItemProvider.triggerCharacters);
|
||||
}
|
||||
return new ConfigurationDependentRegistration(modeId, 'suggest.enabled', () =>
|
||||
vscode.languages.registerCompletionItemProvider(selector,
|
||||
new TypeScriptCompletionItemProvider(client, modeId, typingsStatus, fileConfigurationManager, commandManager),
|
||||
...TypeScriptCompletionItemProvider.triggerCharacters));
|
||||
}
|
||||
|
||||
@@ -3,23 +3,23 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TextDocument, Position, CancellationToken, Location } from 'vscode';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as Proto from '../protocol';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
|
||||
|
||||
export default class TypeScriptDefinitionProviderBase {
|
||||
constructor(
|
||||
private readonly client: ITypeScriptServiceClient
|
||||
protected readonly client: ITypeScriptServiceClient
|
||||
) { }
|
||||
|
||||
protected async getSymbolLocations(
|
||||
definitionType: 'definition' | 'implementation' | 'typeDefinition',
|
||||
document: TextDocument,
|
||||
position: Position,
|
||||
token: CancellationToken | boolean
|
||||
): Promise<Location[] | undefined> {
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<vscode.Location[] | undefined> {
|
||||
const filepath = this.client.toPath(document.uri);
|
||||
if (!filepath) {
|
||||
return undefined;
|
||||
@@ -32,7 +32,7 @@ export default class TypeScriptDefinitionProviderBase {
|
||||
return locations.map(location =>
|
||||
typeConverters.Location.fromTextSpan(this.client.toResource(location.file), location));
|
||||
} catch {
|
||||
return [];
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,50 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import API from '../utils/api';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
import DefinitionProviderBase from './definitionProviderBase';
|
||||
|
||||
class TypeScriptDefinitionProvider extends DefinitionProviderBase implements vscode.DefinitionProvider {
|
||||
public provideDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken | boolean): Promise<vscode.Definition | undefined> {
|
||||
export default class TypeScriptDefinitionProvider extends DefinitionProviderBase implements vscode.DefinitionProvider {
|
||||
constructor(
|
||||
client: ITypeScriptServiceClient
|
||||
) {
|
||||
super(client);
|
||||
}
|
||||
|
||||
public async provideDefinition(
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<vscode.DefinitionLink[] | vscode.Definition | undefined> {
|
||||
if (this.client.apiVersion.gte(API.v270)) {
|
||||
const filepath = this.client.toPath(document.uri);
|
||||
if (!filepath) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
|
||||
try {
|
||||
const { body } = await this.client.execute('definitionAndBoundSpan', args, token);
|
||||
if (!body) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const span = body.textSpan ? typeConverters.Range.fromTextSpan(body.textSpan) : undefined;
|
||||
return body.definitions
|
||||
.map(location => {
|
||||
const target = typeConverters.Location.fromTextSpan(this.client.toResource(location.file), location);
|
||||
return <vscode.DefinitionLink>{
|
||||
originSelectionRange: span,
|
||||
targetRange: target.range,
|
||||
targetUri: target.uri,
|
||||
};
|
||||
});
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
return this.getSymbolLocations('definition', document, position, token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,127 +3,203 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as vscode from 'vscode';
|
||||
import { ResourceMap } from '../utils/resourceMap';
|
||||
import { DiagnosticLanguage, allDiagnosticLangauges } from '../utils/languageDescription';
|
||||
|
||||
export class DiagnosticSet {
|
||||
private _map: ObjectMap<vscode.Diagnostic[]> = Object.create(null);
|
||||
|
||||
public set(
|
||||
file: vscode.Uri,
|
||||
diagnostics: vscode.Diagnostic[]
|
||||
) {
|
||||
this._map[this.key(file)] = diagnostics;
|
||||
}
|
||||
|
||||
public get(file: vscode.Uri): vscode.Diagnostic[] {
|
||||
return this._map[this.key(file)] || [];
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._map = Object.create(null);
|
||||
}
|
||||
|
||||
private key(file: vscode.Uri): string {
|
||||
return file.toString(true);
|
||||
}
|
||||
}
|
||||
|
||||
export enum DiagnosticKind {
|
||||
export const enum DiagnosticKind {
|
||||
Syntax,
|
||||
Semantic,
|
||||
Suggestion
|
||||
}
|
||||
|
||||
const allDiagnosticKinds = [DiagnosticKind.Syntax, DiagnosticKind.Semantic, DiagnosticKind.Suggestion];
|
||||
class FileDiagnostics {
|
||||
private readonly _diagnostics = new Map<DiagnosticKind, vscode.Diagnostic[]>();
|
||||
|
||||
constructor(
|
||||
public readonly file: vscode.Uri,
|
||||
public language: DiagnosticLanguage
|
||||
) { }
|
||||
|
||||
public updateDiagnostics(
|
||||
language: DiagnosticLanguage,
|
||||
kind: DiagnosticKind,
|
||||
diagnostics: vscode.Diagnostic[]
|
||||
): boolean {
|
||||
if (language !== this.language) {
|
||||
this._diagnostics.clear();
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
if (diagnostics.length === 0) {
|
||||
const existing = this._diagnostics.get(kind);
|
||||
if (!existing || existing && existing.length === 0) {
|
||||
// No need to update
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this._diagnostics.set(kind, diagnostics);
|
||||
return true;
|
||||
}
|
||||
|
||||
public getDiagnostics(settings: DiagnosticSettings): vscode.Diagnostic[] {
|
||||
if (!settings.getValidate(this.language)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
...this.get(DiagnosticKind.Syntax),
|
||||
...this.get(DiagnosticKind.Semantic),
|
||||
...this.getSuggestionDiagnostics(settings),
|
||||
];
|
||||
}
|
||||
|
||||
private getSuggestionDiagnostics(settings: DiagnosticSettings) {
|
||||
const enableSuggestions = settings.getEnableSuggestions(this.language);
|
||||
return this.get(DiagnosticKind.Suggestion).filter(x => {
|
||||
if (!enableSuggestions) {
|
||||
// Still show unused
|
||||
return x.tags && x.tags.indexOf(vscode.DiagnosticTag.Unnecessary) !== -1;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private get(kind: DiagnosticKind): vscode.Diagnostic[] {
|
||||
return this._diagnostics.get(kind) || [];
|
||||
}
|
||||
}
|
||||
|
||||
interface LangaugeDiagnosticSettings {
|
||||
readonly validate: boolean;
|
||||
readonly enableSuggestions: boolean;
|
||||
}
|
||||
|
||||
class DiagnosticSettings {
|
||||
private static readonly defaultSettings: LangaugeDiagnosticSettings = {
|
||||
validate: true,
|
||||
enableSuggestions: true
|
||||
};
|
||||
|
||||
private readonly _languageSettings = new Map<DiagnosticLanguage, LangaugeDiagnosticSettings>();
|
||||
|
||||
constructor() {
|
||||
for (const language of allDiagnosticLangauges) {
|
||||
this._languageSettings.set(language, DiagnosticSettings.defaultSettings);
|
||||
}
|
||||
}
|
||||
|
||||
public getValidate(language: DiagnosticLanguage): boolean {
|
||||
return this.get(language).validate;
|
||||
}
|
||||
|
||||
public setValidate(language: DiagnosticLanguage, value: boolean): boolean {
|
||||
return this.update(language, settings => ({
|
||||
validate: value,
|
||||
enableSuggestions: settings.enableSuggestions
|
||||
}));
|
||||
}
|
||||
|
||||
public getEnableSuggestions(language: DiagnosticLanguage): boolean {
|
||||
return this.get(language).enableSuggestions;
|
||||
}
|
||||
|
||||
public setEnableSuggestions(language: DiagnosticLanguage, value: boolean): boolean {
|
||||
return this.update(language, settings => ({
|
||||
validate: settings.validate,
|
||||
enableSuggestions: value
|
||||
}));
|
||||
}
|
||||
|
||||
private get(language: DiagnosticLanguage): LangaugeDiagnosticSettings {
|
||||
return this._languageSettings.get(language) || DiagnosticSettings.defaultSettings;
|
||||
}
|
||||
|
||||
private update(language: DiagnosticLanguage, f: (x: LangaugeDiagnosticSettings) => LangaugeDiagnosticSettings): boolean {
|
||||
const currentSettings = this.get(language);
|
||||
const newSettings = f(currentSettings);
|
||||
this._languageSettings.set(language, newSettings);
|
||||
return currentSettings.validate === newSettings.validate
|
||||
&& currentSettings.enableSuggestions && currentSettings.enableSuggestions;
|
||||
}
|
||||
}
|
||||
|
||||
export class DiagnosticsManager {
|
||||
|
||||
private readonly _diagnostics = new Map<DiagnosticKind, DiagnosticSet>();
|
||||
private readonly _diagnostics = new ResourceMap<FileDiagnostics>();
|
||||
private readonly _settings = new DiagnosticSettings();
|
||||
private readonly _currentDiagnostics: vscode.DiagnosticCollection;
|
||||
private readonly _pendingUpdates: { [key: string]: any } = Object.create(null);
|
||||
private _validate: boolean = true;
|
||||
private _enableSuggestions: boolean = true;
|
||||
private _pendingUpdates = new ResourceMap<any>();
|
||||
|
||||
private readonly updateDelay = 50;
|
||||
private readonly _updateDelay = 50;
|
||||
|
||||
constructor(
|
||||
owner: string
|
||||
) {
|
||||
for (const kind of allDiagnosticKinds) {
|
||||
this._diagnostics.set(kind, new DiagnosticSet());
|
||||
}
|
||||
|
||||
this._currentDiagnostics = vscode.languages.createDiagnosticCollection(owner);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this._currentDiagnostics.dispose();
|
||||
|
||||
for (const key of Object.keys(this._pendingUpdates)) {
|
||||
clearTimeout(this._pendingUpdates[key]);
|
||||
delete this._pendingUpdates[key];
|
||||
for (const value of this._pendingUpdates.values) {
|
||||
clearTimeout(value);
|
||||
}
|
||||
this._pendingUpdates = new ResourceMap<any>();
|
||||
}
|
||||
|
||||
public reInitialize(): void {
|
||||
this._currentDiagnostics.clear();
|
||||
this._diagnostics.clear();
|
||||
}
|
||||
|
||||
for (const diagnosticSet of this._diagnostics.values()) {
|
||||
diagnosticSet.clear();
|
||||
public setValidate(language: DiagnosticLanguage, value: boolean) {
|
||||
const didUpdate = this._settings.setValidate(language, value);
|
||||
if (didUpdate) {
|
||||
this.rebuild();
|
||||
}
|
||||
}
|
||||
|
||||
public set validate(value: boolean) {
|
||||
if (this._validate === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._validate = value;
|
||||
if (!value) {
|
||||
this._currentDiagnostics.clear();
|
||||
public setEnableSuggestions(language: DiagnosticLanguage, value: boolean) {
|
||||
const didUpdate = this._settings.setEnableSuggestions(language, value);
|
||||
if (didUpdate) {
|
||||
this.rebuild();
|
||||
}
|
||||
}
|
||||
|
||||
public set enableSuggestions(value: boolean) {
|
||||
if (this._enableSuggestions === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._enableSuggestions = value;
|
||||
if (!value) {
|
||||
this._currentDiagnostics.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public diagnosticsReceived(
|
||||
public updateDiagnostics(
|
||||
file: vscode.Uri,
|
||||
language: DiagnosticLanguage,
|
||||
kind: DiagnosticKind,
|
||||
diagnostics: vscode.Diagnostic[]
|
||||
): void {
|
||||
|
||||
let didUpdate = false;
|
||||
const entry = this._diagnostics.get(file);
|
||||
if (entry) {
|
||||
didUpdate = entry.updateDiagnostics(language, kind, diagnostics);
|
||||
} else if (diagnostics.length) {
|
||||
const fileDiagnostics = new FileDiagnostics(file, language);
|
||||
fileDiagnostics.updateDiagnostics(language, kind, diagnostics);
|
||||
this._diagnostics.set(file, fileDiagnostics);
|
||||
didUpdate = true;
|
||||
}
|
||||
|
||||
if (didUpdate) {
|
||||
this.scheduleDiagnosticsUpdate(file);
|
||||
}
|
||||
}
|
||||
|
||||
public configFileDiagnosticsReceived(
|
||||
file: vscode.Uri,
|
||||
diagnostics: vscode.Diagnostic[]
|
||||
): void {
|
||||
const collection = this._diagnostics.get(kind);
|
||||
if (!collection) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (diagnostics.length === 0) {
|
||||
const existing = collection.get(file);
|
||||
if (existing.length === 0) {
|
||||
// No need to update
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
collection.set(file, diagnostics);
|
||||
|
||||
this.scheduleDiagnosticsUpdate(file);
|
||||
}
|
||||
|
||||
public configFileDiagnosticsReceived(file: vscode.Uri, diagnostics: vscode.Diagnostic[]): void {
|
||||
this._currentDiagnostics.set(file, diagnostics);
|
||||
}
|
||||
|
||||
public delete(resource: vscode.Uri): void {
|
||||
this._currentDiagnostics.delete(resource);
|
||||
this._diagnostics.delete(resource);
|
||||
}
|
||||
|
||||
public getDiagnostics(file: vscode.Uri): vscode.Diagnostic[] {
|
||||
@@ -131,37 +207,25 @@ export class DiagnosticsManager {
|
||||
}
|
||||
|
||||
private scheduleDiagnosticsUpdate(file: vscode.Uri) {
|
||||
const key = file.fsPath;
|
||||
if (!this._pendingUpdates[key]) {
|
||||
this._pendingUpdates[key] = setTimeout(() => this.updateCurrentDiagnostics(file), this.updateDelay);
|
||||
if (!this._pendingUpdates.has(file)) {
|
||||
this._pendingUpdates.set(file, setTimeout(() => this.updateCurrentDiagnostics(file), this._updateDelay));
|
||||
}
|
||||
}
|
||||
|
||||
private updateCurrentDiagnostics(file: vscode.Uri) {
|
||||
if (this._pendingUpdates[file.fsPath]) {
|
||||
clearTimeout(this._pendingUpdates[file.fsPath]);
|
||||
delete this._pendingUpdates[file.fsPath];
|
||||
private updateCurrentDiagnostics(file: vscode.Uri): void {
|
||||
if (this._pendingUpdates.has(file)) {
|
||||
clearTimeout(this._pendingUpdates.get(file));
|
||||
this._pendingUpdates.delete(file);
|
||||
}
|
||||
|
||||
if (!this._validate) {
|
||||
return;
|
||||
}
|
||||
|
||||
const allDiagnostics = [
|
||||
...this._diagnostics.get(DiagnosticKind.Syntax)!.get(file),
|
||||
...this._diagnostics.get(DiagnosticKind.Semantic)!.get(file),
|
||||
...this.getSuggestionDiagnostics(file),
|
||||
];
|
||||
this._currentDiagnostics.set(file, allDiagnostics);
|
||||
const fileDiagnostics = this._diagnostics.get(file);
|
||||
this._currentDiagnostics.set(file, fileDiagnostics ? fileDiagnostics.getDiagnostics(this._settings) : []);
|
||||
}
|
||||
|
||||
private getSuggestionDiagnostics(file: vscode.Uri) {
|
||||
return this._diagnostics.get(DiagnosticKind.Suggestion)!.get(file).filter(x => {
|
||||
if (!this._enableSuggestions) {
|
||||
// Still show unused
|
||||
return x.customTags && x.customTags.indexOf(vscode.DiagnosticTag.Unnecessary) !== -1;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
private rebuild(): void {
|
||||
this._currentDiagnostics.clear();
|
||||
for (const fileDiagnostic of Array.from(this._diagnostics.values)) {
|
||||
this._currentDiagnostics.set(fileDiagnostic.file, fileDiagnostic.getDiagnostics(this._settings));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,14 +6,14 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import { VersionDependentRegistration } from '../utils/dependentRegistration';
|
||||
import API from '../utils/api';
|
||||
import { VersionDependentRegistration } from '../utils/dependentRegistration';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
interface Directive {
|
||||
value: string;
|
||||
description: string;
|
||||
readonly value: string;
|
||||
readonly description: string;
|
||||
}
|
||||
|
||||
const directives: Directive[] = [
|
||||
@@ -21,17 +21,17 @@ const directives: Directive[] = [
|
||||
value: '@ts-check',
|
||||
description: localize(
|
||||
'ts-check',
|
||||
'Enables semantic checking in a JavaScript file. Must be at the top of a file.')
|
||||
"Enables semantic checking in a JavaScript file. Must be at the top of a file.")
|
||||
}, {
|
||||
value: '@ts-nocheck',
|
||||
description: localize(
|
||||
'ts-nocheck',
|
||||
'Disables semantic checking in a JavaScript file. Must be at the top of a file.')
|
||||
"Disables semantic checking in a JavaScript file. Must be at the top of a file.")
|
||||
}, {
|
||||
value: '@ts-ignore',
|
||||
description: localize(
|
||||
'ts-ignore',
|
||||
'Suppresses @ts-check errors on the next line of a file.')
|
||||
"Suppresses @ts-check errors on the next line of a file.")
|
||||
}
|
||||
];
|
||||
|
||||
@@ -63,13 +63,6 @@ class DirectiveCommentCompletionProvider implements vscode.CompletionItemProvide
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public resolveCompletionItem(
|
||||
item: vscode.CompletionItem,
|
||||
_token: vscode.CancellationToken
|
||||
) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
export function register(
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import * as Proto from '../protocol';
|
||||
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
|
||||
|
||||
|
||||
class TypeScriptDocumentHighlightProvider implements vscode.DocumentHighlightProvider {
|
||||
public constructor(
|
||||
private readonly client: ITypeScriptServiceClient
|
||||
@@ -26,18 +26,20 @@ class TypeScriptDocumentHighlightProvider implements vscode.DocumentHighlightPro
|
||||
}
|
||||
|
||||
const args = typeConverters.Position.toFileLocationRequestArgs(file, position);
|
||||
let items: Proto.OccurrencesResponseItem[];
|
||||
try {
|
||||
const response = await this.client.execute('occurrences', args, token);
|
||||
if (response && response.body) {
|
||||
return response.body
|
||||
.filter(x => !x.isInString)
|
||||
.map(documentHighlightFromOccurance);
|
||||
const { body } = await this.client.execute('occurrences', args, token);
|
||||
if (!body) {
|
||||
return [];
|
||||
}
|
||||
items = body;
|
||||
} catch {
|
||||
// noop
|
||||
return [];
|
||||
}
|
||||
|
||||
return [];
|
||||
return items
|
||||
.filter(x => !x.isInString)
|
||||
.map(documentHighlightFromOccurance);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,10 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import * as Proto from '../protocol';
|
||||
import * as PConst from '../protocol.const';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
import API from '../utils/api';
|
||||
|
||||
const getSymbolKind = (kind: string): vscode.SymbolKind => {
|
||||
switch (kind) {
|
||||
@@ -33,82 +31,63 @@ const getSymbolKind = (kind: string): vscode.SymbolKind => {
|
||||
|
||||
class TypeScriptDocumentSymbolProvider implements vscode.DocumentSymbolProvider {
|
||||
public constructor(
|
||||
private readonly client: ITypeScriptServiceClient) { }
|
||||
private readonly client: ITypeScriptServiceClient
|
||||
) { }
|
||||
|
||||
public async provideDocumentSymbols(resource: vscode.TextDocument, token: vscode.CancellationToken): Promise<any> { // todo@joh `any[]` temporary hack to make typescript happy...
|
||||
const filepath = this.client.toPath(resource.uri);
|
||||
if (!filepath) {
|
||||
return [];
|
||||
public async provideDocumentSymbols(resource: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.DocumentSymbol[] | undefined> {
|
||||
const file = this.client.toPath(resource.uri);
|
||||
if (!file) {
|
||||
return undefined;
|
||||
}
|
||||
const args: Proto.FileRequestArgs = {
|
||||
file: filepath
|
||||
};
|
||||
|
||||
let tree: Proto.NavigationTree;
|
||||
try {
|
||||
if (this.client.apiVersion.gte(API.v206)) {
|
||||
const response = await this.client.execute('navtree', args, token);
|
||||
if (response.body) {
|
||||
// The root represents the file. Ignore this when showing in the UI
|
||||
const tree = response.body;
|
||||
if (tree.childItems) {
|
||||
const result = new Array<vscode.SymbolInformation2>();
|
||||
tree.childItems.forEach(item => TypeScriptDocumentSymbolProvider.convertNavTree(resource.uri, result, item));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const response = await this.client.execute('navbar', args, token);
|
||||
if (response.body) {
|
||||
const result = new Array<vscode.SymbolInformation>();
|
||||
const foldingMap: ObjectMap<vscode.SymbolInformation> = Object.create(null);
|
||||
response.body.forEach(item => TypeScriptDocumentSymbolProvider.convertNavBar(resource.uri, 0, foldingMap, result as vscode.SymbolInformation[], item));
|
||||
return result;
|
||||
}
|
||||
const args: Proto.FileRequestArgs = { file };
|
||||
const { body } = await this.client.execute('navtree', args, token);
|
||||
if (!body) {
|
||||
return undefined;
|
||||
}
|
||||
return [];
|
||||
} catch (e) {
|
||||
return [];
|
||||
tree = body;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (tree && tree.childItems) {
|
||||
// The root represents the file. Ignore this when showing in the UI
|
||||
const result: vscode.DocumentSymbol[] = [];
|
||||
tree.childItems.forEach(item => TypeScriptDocumentSymbolProvider.convertNavTree(resource.uri, result, item));
|
||||
return result;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private static convertNavBar(resource: vscode.Uri, indent: number, foldingMap: ObjectMap<vscode.SymbolInformation>, bucket: vscode.SymbolInformation[], item: Proto.NavigationBarItem, containerLabel?: string): void {
|
||||
const realIndent = indent + item.indent;
|
||||
const key = `${realIndent}|${item.text}`;
|
||||
if (realIndent !== 0 && !foldingMap[key] && TypeScriptDocumentSymbolProvider.shouldInclueEntry(item)) {
|
||||
const result = new vscode.SymbolInformation(item.text,
|
||||
getSymbolKind(item.kind),
|
||||
containerLabel ? containerLabel : '',
|
||||
typeConverters.Location.fromTextSpan(resource, item.spans[0]));
|
||||
foldingMap[key] = result;
|
||||
bucket.push(result);
|
||||
}
|
||||
if (item.childItems && item.childItems.length > 0) {
|
||||
for (const child of item.childItems) {
|
||||
TypeScriptDocumentSymbolProvider.convertNavBar(resource, realIndent + 1, foldingMap, bucket, child, item.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static convertNavTree(resource: vscode.Uri, bucket: vscode.SymbolInformation[], item: Proto.NavigationTree): boolean {
|
||||
const symbolInfo = new vscode.SymbolInformation2(
|
||||
item.text,
|
||||
getSymbolKind(item.kind),
|
||||
'', // no container name
|
||||
typeConverters.Location.fromTextSpan(resource, item.spans[0]),
|
||||
);
|
||||
|
||||
private static convertNavTree(resource: vscode.Uri, bucket: vscode.DocumentSymbol[], item: Proto.NavigationTree): boolean {
|
||||
let shouldInclude = TypeScriptDocumentSymbolProvider.shouldInclueEntry(item);
|
||||
|
||||
if (item.childItems) {
|
||||
for (const child of item.childItems) {
|
||||
const includedChild = TypeScriptDocumentSymbolProvider.convertNavTree(resource, symbolInfo.children, child);
|
||||
shouldInclude = shouldInclude || includedChild;
|
||||
const children = new Set(item.childItems || []);
|
||||
for (const span of item.spans) {
|
||||
const range = typeConverters.Range.fromTextSpan(span);
|
||||
const symbolInfo = new vscode.DocumentSymbol(
|
||||
item.text,
|
||||
'',
|
||||
getSymbolKind(item.kind),
|
||||
range,
|
||||
range);
|
||||
|
||||
for (const child of children) {
|
||||
if (child.spans.some(span => !!range.intersection(typeConverters.Range.fromTextSpan(span)))) {
|
||||
const includedChild = TypeScriptDocumentSymbolProvider.convertNavTree(resource, symbolInfo.children, child);
|
||||
shouldInclude = shouldInclude || includedChild;
|
||||
children.delete(child);
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldInclude) {
|
||||
bucket.push(symbolInfo);
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldInclude) {
|
||||
bucket.push(symbolInfo);
|
||||
}
|
||||
return shouldInclude;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { workspace as Workspace, FormattingOptions, TextDocument, CancellationToken, window, Disposable, workspace, WorkspaceConfiguration } from 'vscode';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as Proto from '../protocol';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import * as languageIds from '../utils/languageModeIds';
|
||||
import API from '../utils/api';
|
||||
import { isTypeScriptDocument } from '../utils/languageModeIds';
|
||||
import { ResourceMap } from '../utils/resourceMap';
|
||||
|
||||
|
||||
function objsAreEqual<T>(a: T, b: T): boolean {
|
||||
let keys = Object.keys(a);
|
||||
@@ -22,8 +23,8 @@ function objsAreEqual<T>(a: T, b: T): boolean {
|
||||
}
|
||||
|
||||
interface FileConfiguration {
|
||||
formatOptions: Proto.FormatCodeSettings;
|
||||
preferences: Proto.UserPreferences;
|
||||
readonly formatOptions: Proto.FormatCodeSettings;
|
||||
readonly preferences: Proto.UserPreferences;
|
||||
}
|
||||
|
||||
function areFileConfigurationsEqual(a: FileConfiguration, b: FileConfiguration): boolean {
|
||||
@@ -34,19 +35,18 @@ function areFileConfigurationsEqual(a: FileConfiguration, b: FileConfiguration):
|
||||
}
|
||||
|
||||
export default class FileConfigurationManager {
|
||||
private onDidCloseTextDocumentSub: Disposable | undefined;
|
||||
private formatOptions: { [key: string]: FileConfiguration | undefined } = Object.create(null);
|
||||
private onDidCloseTextDocumentSub: vscode.Disposable | undefined;
|
||||
private formatOptions = new ResourceMap<FileConfiguration>();
|
||||
|
||||
public constructor(
|
||||
private readonly client: ITypeScriptServiceClient
|
||||
) {
|
||||
this.onDidCloseTextDocumentSub = Workspace.onDidCloseTextDocument((textDocument) => {
|
||||
const key = textDocument.uri.toString();
|
||||
this.onDidCloseTextDocumentSub = vscode.workspace.onDidCloseTextDocument((textDocument) => {
|
||||
// When a document gets closed delete the cached formatting options.
|
||||
// This is necessary since the tsserver now closed a project when its
|
||||
// last file in it closes which drops the stored formatting options
|
||||
// as well.
|
||||
delete this.formatOptions[key];
|
||||
this.formatOptions.delete(textDocument.uri);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -58,53 +58,74 @@ export default class FileConfigurationManager {
|
||||
}
|
||||
|
||||
public async ensureConfigurationForDocument(
|
||||
document: TextDocument,
|
||||
token: CancellationToken | undefined
|
||||
document: vscode.TextDocument,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<void> {
|
||||
const editor = window.visibleTextEditors.find(editor => editor.document.fileName === document.fileName);
|
||||
if (editor) {
|
||||
const formattingOptions = {
|
||||
tabSize: editor.options.tabSize,
|
||||
insertSpaces: editor.options.insertSpaces
|
||||
} as FormattingOptions;
|
||||
const formattingOptions = this.getFormattingOptions(document);
|
||||
if (formattingOptions) {
|
||||
return this.ensureConfigurationOptions(document, formattingOptions, token);
|
||||
}
|
||||
}
|
||||
|
||||
private getFormattingOptions(
|
||||
document: vscode.TextDocument
|
||||
): vscode.FormattingOptions | undefined {
|
||||
const editor = vscode.window.visibleTextEditors.find(editor => editor.document.fileName === document.fileName);
|
||||
return editor
|
||||
? {
|
||||
tabSize: editor.options.tabSize,
|
||||
insertSpaces: editor.options.insertSpaces
|
||||
} as vscode.FormattingOptions
|
||||
: undefined;
|
||||
}
|
||||
|
||||
public async ensureConfigurationOptions(
|
||||
document: TextDocument,
|
||||
options: FormattingOptions,
|
||||
token: CancellationToken | undefined
|
||||
document: vscode.TextDocument,
|
||||
options: vscode.FormattingOptions,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<void> {
|
||||
const file = this.client.toPath(document.uri);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = document.uri.toString();
|
||||
const cachedOptions = this.formatOptions[key];
|
||||
const cachedOptions = this.formatOptions.get(document.uri);
|
||||
const currentOptions = this.getFileOptions(document, options);
|
||||
|
||||
if (cachedOptions && areFileConfigurationsEqual(cachedOptions, currentOptions)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.formatOptions.set(document.uri, currentOptions);
|
||||
const args: Proto.ConfigureRequestArguments = {
|
||||
file,
|
||||
...currentOptions,
|
||||
};
|
||||
await this.client.execute('configure', args, token);
|
||||
this.formatOptions[key] = currentOptions;
|
||||
}
|
||||
|
||||
public async setGlobalConfigurationFromDocument(
|
||||
document: vscode.TextDocument,
|
||||
token: vscode.CancellationToken,
|
||||
): Promise<void> {
|
||||
const formattingOptions = this.getFormattingOptions(document);
|
||||
if (!formattingOptions) {
|
||||
return;
|
||||
}
|
||||
|
||||
const args: Proto.ConfigureRequestArguments = {
|
||||
file: undefined /*global*/,
|
||||
...this.getFileOptions(document, formattingOptions),
|
||||
};
|
||||
await this.client.execute('configure', args, token);
|
||||
}
|
||||
|
||||
public reset() {
|
||||
this.formatOptions = Object.create(null);
|
||||
this.formatOptions.clear();
|
||||
}
|
||||
|
||||
|
||||
private getFileOptions(
|
||||
document: TextDocument,
|
||||
options: FormattingOptions
|
||||
document: vscode.TextDocument,
|
||||
options: vscode.FormattingOptions
|
||||
): FileConfiguration {
|
||||
return {
|
||||
formatOptions: this.getFormatOptions(document, options),
|
||||
@@ -113,10 +134,10 @@ export default class FileConfigurationManager {
|
||||
}
|
||||
|
||||
private getFormatOptions(
|
||||
document: TextDocument,
|
||||
options: FormattingOptions
|
||||
document: vscode.TextDocument,
|
||||
options: vscode.FormattingOptions
|
||||
): Proto.FormatCodeSettings {
|
||||
const config = workspace.getConfiguration(
|
||||
const config = vscode.workspace.getConfiguration(
|
||||
isTypeScriptDocument(document) ? 'typescript.format' : 'javascript.format',
|
||||
document.uri);
|
||||
|
||||
@@ -144,12 +165,12 @@ export default class FileConfigurationManager {
|
||||
};
|
||||
}
|
||||
|
||||
private getPreferences(document: TextDocument): Proto.UserPreferences {
|
||||
private getPreferences(document: vscode.TextDocument): Proto.UserPreferences {
|
||||
if (!this.client.apiVersion.gte(API.v290)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const preferences = workspace.getConfiguration(
|
||||
const preferences = vscode.workspace.getConfiguration(
|
||||
isTypeScriptDocument(document) ? 'typescript.preferences' : 'javascript.preferences',
|
||||
document.uri);
|
||||
|
||||
@@ -161,7 +182,7 @@ export default class FileConfigurationManager {
|
||||
}
|
||||
}
|
||||
|
||||
function getQuoteStylePreference(config: WorkspaceConfiguration) {
|
||||
function getQuoteStylePreference(config: vscode.WorkspaceConfiguration) {
|
||||
switch (config.get<string>('quoteStyle')) {
|
||||
case 'single': return 'single';
|
||||
case 'double': return 'double';
|
||||
@@ -169,14 +190,10 @@ function getQuoteStylePreference(config: WorkspaceConfiguration) {
|
||||
}
|
||||
}
|
||||
|
||||
function getImportModuleSpecifierPreference(config: WorkspaceConfiguration) {
|
||||
function getImportModuleSpecifierPreference(config: vscode.WorkspaceConfiguration) {
|
||||
switch (config.get<string>('importModuleSpecifier')) {
|
||||
case 'relative': return 'relative';
|
||||
case 'non-relative': return 'non-relative';
|
||||
default: return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function isTypeScriptDocument(document: TextDocument) {
|
||||
return document.languageId === languageIds.typescript || document.languageId === languageIds.typescriptreact;
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,9 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as Proto from '../protocol';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
import { VersionDependentRegistration } from '../utils/dependentRegistration';
|
||||
import API from '../utils/api';
|
||||
import { VersionDependentRegistration } from '../utils/dependentRegistration';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
|
||||
class TypeScriptFoldingProvider implements vscode.FoldingRangeProvider {
|
||||
public constructor(
|
||||
@@ -26,12 +26,12 @@ class TypeScriptFoldingProvider implements vscode.FoldingRangeProvider {
|
||||
}
|
||||
|
||||
const args: Proto.FileRequestArgs = { file };
|
||||
const response: Proto.OutliningSpansResponse = await this.client.execute('getOutliningSpans', args, token);
|
||||
if (!response || !response.body) {
|
||||
const { body } = await this.client.execute('getOutliningSpans', args, token);
|
||||
if (!body) {
|
||||
return;
|
||||
}
|
||||
|
||||
return response.body
|
||||
return body
|
||||
.map(span => this.convertOutliningSpan(span, document))
|
||||
.filter(foldingRange => !!foldingRange) as vscode.FoldingRange[];
|
||||
}
|
||||
|
||||
@@ -10,59 +10,38 @@ import { ConfigurationDependentRegistration } from '../utils/dependentRegistrati
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
import FileConfigurationManager from './fileConfigurationManager';
|
||||
|
||||
|
||||
class TypeScriptFormattingProvider implements vscode.DocumentRangeFormattingEditProvider, vscode.OnTypeFormattingEditProvider {
|
||||
private enabled: boolean = true;
|
||||
|
||||
public constructor(
|
||||
private readonly client: ITypeScriptServiceClient,
|
||||
private readonly formattingOptionsManager: FileConfigurationManager
|
||||
) { }
|
||||
|
||||
public updateConfiguration(config: vscode.WorkspaceConfiguration): void {
|
||||
this.enabled = config.get('format.enable', true);
|
||||
}
|
||||
|
||||
public isEnabled(): boolean {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
private async doFormat(
|
||||
document: vscode.TextDocument,
|
||||
options: vscode.FormattingOptions,
|
||||
args: Proto.FormatRequestArgs,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<vscode.TextEdit[]> {
|
||||
await this.formattingOptionsManager.ensureConfigurationOptions(document, options, token);
|
||||
try {
|
||||
const response = await this.client.execute('format', args, token);
|
||||
if (response.body) {
|
||||
return response.body.map(typeConverters.TextEdit.fromCodeEdit);
|
||||
}
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public async provideDocumentRangeFormattingEdits(
|
||||
document: vscode.TextDocument,
|
||||
range: vscode.Range,
|
||||
options: vscode.FormattingOptions,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<vscode.TextEdit[]> {
|
||||
const absPath = this.client.toPath(document.uri);
|
||||
if (!absPath) {
|
||||
return [];
|
||||
): Promise<vscode.TextEdit[] | undefined> {
|
||||
const file = this.client.toPath(document.uri);
|
||||
if (!file) {
|
||||
return undefined;
|
||||
}
|
||||
const args: Proto.FormatRequestArgs = {
|
||||
file: absPath,
|
||||
line: range.start.line + 1,
|
||||
offset: range.start.character + 1,
|
||||
endLine: range.end.line + 1,
|
||||
endOffset: range.end.character + 1
|
||||
};
|
||||
return this.doFormat(document, options, args, token);
|
||||
|
||||
await this.formattingOptionsManager.ensureConfigurationOptions(document, options, token);
|
||||
|
||||
let edits: Proto.CodeEdit[];
|
||||
try {
|
||||
const args = typeConverters.Range.toFormattingRequestArgs(file, range);
|
||||
const { body } = await this.client.execute('format', args, token);
|
||||
if (!body) {
|
||||
return undefined;
|
||||
}
|
||||
edits = body;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return edits.map(typeConverters.TextEdit.fromCodeEdit);
|
||||
}
|
||||
|
||||
public async provideOnTypeFormattingEdits(
|
||||
@@ -72,22 +51,20 @@ class TypeScriptFormattingProvider implements vscode.DocumentRangeFormattingEdit
|
||||
options: vscode.FormattingOptions,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<vscode.TextEdit[]> {
|
||||
const filepath = this.client.toPath(document.uri);
|
||||
if (!filepath) {
|
||||
const file = this.client.toPath(document.uri);
|
||||
if (!file) {
|
||||
return [];
|
||||
}
|
||||
|
||||
await this.formattingOptionsManager.ensureConfigurationOptions(document, options, token);
|
||||
|
||||
const args: Proto.FormatOnKeyRequestArgs = {
|
||||
file: filepath,
|
||||
line: position.line + 1,
|
||||
offset: position.character + 1,
|
||||
...typeConverters.Position.toFileLocationRequestArgs(file, position),
|
||||
key: ch
|
||||
};
|
||||
try {
|
||||
const response = await this.client.execute('formatonkey', args, token);
|
||||
const edits = response.body;
|
||||
const { body } = await this.client.execute('formatonkey', args, token);
|
||||
const edits = body;
|
||||
const result: vscode.TextEdit[] = [];
|
||||
if (!edits) {
|
||||
return result;
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import * as Proto from '../protocol';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import { tagsMarkdownPreview } from '../utils/previewer';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
|
||||
|
||||
class TypeScriptHoverProvider implements vscode.HoverProvider {
|
||||
|
||||
public constructor(
|
||||
@@ -25,14 +25,14 @@ class TypeScriptHoverProvider implements vscode.HoverProvider {
|
||||
if (!filepath) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
|
||||
try {
|
||||
const response = await this.client.execute('quickinfo', args, token);
|
||||
if (response && response.body) {
|
||||
const data = response.body;
|
||||
const { body } = await this.client.interuptGetErr(() => this.client.execute('quickinfo', args, token));
|
||||
if (body) {
|
||||
return new vscode.Hover(
|
||||
TypeScriptHoverProvider.getContents(data),
|
||||
typeConverters.Range.fromTextSpan(data));
|
||||
TypeScriptHoverProvider.getContents(body),
|
||||
typeConverters.Range.fromTextSpan(body));
|
||||
}
|
||||
} catch (e) {
|
||||
// noop
|
||||
|
||||
@@ -10,7 +10,7 @@ import { VersionDependentRegistration } from '../utils/dependentRegistration';
|
||||
import DefinitionProviderBase from './definitionProviderBase';
|
||||
|
||||
class TypeScriptImplementationProvider extends DefinitionProviderBase implements vscode.ImplementationProvider {
|
||||
public provideImplementation(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken | boolean): Promise<vscode.Definition | undefined> {
|
||||
public provideImplementation(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.Definition | undefined> {
|
||||
return this.getSymbolLocations('implementation', document, position, token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,50 +10,45 @@ import * as PConst from '../protocol.const';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import API from '../utils/api';
|
||||
import { ConfigurationDependentRegistration, VersionDependentRegistration } from '../utils/dependentRegistration';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
import { CachedNavTreeResponse, ReferencesCodeLens, TypeScriptBaseCodeLensProvider } from './baseCodeLensProvider';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export default class TypeScriptImplementationsCodeLensProvider extends TypeScriptBaseCodeLensProvider {
|
||||
|
||||
public resolveCodeLens(inputCodeLens: vscode.CodeLens, token: vscode.CancellationToken): Promise<vscode.CodeLens> {
|
||||
public async resolveCodeLens(
|
||||
inputCodeLens: vscode.CodeLens,
|
||||
_token: vscode.CancellationToken,
|
||||
): Promise<vscode.CodeLens> {
|
||||
const codeLens = inputCodeLens as ReferencesCodeLens;
|
||||
const args = typeConverters.Position.toFileLocationRequestArgs(codeLens.file, codeLens.range.start);
|
||||
return this.client.execute('implementation', args, token).then(response => {
|
||||
if (!response || !response.body) {
|
||||
throw codeLens;
|
||||
try {
|
||||
const locations: vscode.Location[] | undefined = await vscode.commands.executeCommand<vscode.Location[]>('vscode.executeImplementationProvider', codeLens.document, codeLens.range.start);
|
||||
if (locations) {
|
||||
codeLens.command = this.getCommand(locations, codeLens);
|
||||
return codeLens;
|
||||
}
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
|
||||
const locations = response.body
|
||||
.map(reference =>
|
||||
// Only take first line on implementation: https://github.com/Microsoft/vscode/issues/23924
|
||||
new vscode.Location(this.client.toResource(reference.file),
|
||||
reference.start.line === reference.end.line
|
||||
? typeConverters.Range.fromTextSpan(reference)
|
||||
: new vscode.Range(
|
||||
reference.start.line - 1, reference.start.offset - 1,
|
||||
reference.start.line, 0)))
|
||||
// Exclude original from implementations
|
||||
.filter(location =>
|
||||
!(location.uri.toString() === codeLens.document.toString() &&
|
||||
location.range.start.line === codeLens.range.start.line &&
|
||||
location.range.start.character === codeLens.range.start.character));
|
||||
codeLens.command = {
|
||||
title: localize('implementationsErrorLabel', 'Could not determine implementations'),
|
||||
command: ''
|
||||
};
|
||||
return codeLens;
|
||||
}
|
||||
|
||||
codeLens.command = {
|
||||
title: locations.length === 1
|
||||
? localize('oneImplementationLabel', '1 implementation')
|
||||
: localize('manyImplementationLabel', '{0} implementations', locations.length),
|
||||
command: locations.length ? 'editor.action.showReferences' : '',
|
||||
arguments: [codeLens.document, codeLens.range.start, locations]
|
||||
};
|
||||
return codeLens;
|
||||
}).catch(() => {
|
||||
codeLens.command = {
|
||||
title: localize('implementationsErrorLabel', 'Could not determine implementations'),
|
||||
command: ''
|
||||
};
|
||||
return codeLens;
|
||||
});
|
||||
private getCommand(locations: vscode.Location[], codeLens: ReferencesCodeLens): vscode.Command | undefined {
|
||||
return {
|
||||
title: this.getTitle(locations),
|
||||
command: locations.length ? 'editor.action.showReferences' : '',
|
||||
arguments: [codeLens.document, codeLens.range.start, locations]
|
||||
};
|
||||
}
|
||||
|
||||
private getTitle(locations: vscode.Location[]): string {
|
||||
return locations.length === 1
|
||||
? localize('oneImplementationLabel', '1 implementation')
|
||||
: localize('manyImplementationLabel', '{0} implementations', locations.length);
|
||||
}
|
||||
|
||||
protected extractSymbol(
|
||||
|
||||
@@ -3,192 +3,102 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken, CompletionItem, CompletionItemKind, CompletionItemProvider, Disposable, DocumentSelector, languages, Position, Range, SnippetString, TextDocument, TextEditor, Uri, window } from 'vscode';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as Proto from '../protocol';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import { Command, CommandManager } from '../utils/commandManager';
|
||||
import { ConfigurationDependentRegistration } from '../utils/dependentRegistration';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
class JsDocCompletionItem extends CompletionItem {
|
||||
const defaultJsDoc = new vscode.SnippetString(`/**\n * $0\n */`);
|
||||
|
||||
class JsDocCompletionItem extends vscode.CompletionItem {
|
||||
constructor(
|
||||
document: TextDocument,
|
||||
position: Position
|
||||
public readonly document: vscode.TextDocument,
|
||||
public readonly position: vscode.Position
|
||||
) {
|
||||
super('/** */', CompletionItemKind.Snippet);
|
||||
super('/** */', vscode.CompletionItemKind.Snippet);
|
||||
this.detail = localize('typescript.jsDocCompletionItem.documentation', 'JSDoc comment');
|
||||
this.insertText = '';
|
||||
this.sortText = '\0';
|
||||
|
||||
const line = document.lineAt(position.line).text;
|
||||
const prefix = line.slice(0, position.character).match(/\/\**\s*$/);
|
||||
const suffix = line.slice(position.character).match(/^\s*\**\//);
|
||||
const start = position.translate(0, prefix ? -prefix[0].length : 0);
|
||||
this.range = new Range(
|
||||
this.range = new vscode.Range(
|
||||
start,
|
||||
position.translate(0, suffix ? suffix[0].length : 0));
|
||||
|
||||
this.command = {
|
||||
title: 'Try Complete JSDoc',
|
||||
command: TryCompleteJsDocCommand.COMMAND_NAME,
|
||||
arguments: [document.uri, start]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class JsDocCompletionProvider implements CompletionItemProvider {
|
||||
class JsDocCompletionProvider implements vscode.CompletionItemProvider {
|
||||
|
||||
constructor(
|
||||
private readonly client: ITypeScriptServiceClient,
|
||||
commandManager: CommandManager
|
||||
) {
|
||||
commandManager.register(new TryCompleteJsDocCommand(client));
|
||||
}
|
||||
) { }
|
||||
|
||||
public async provideCompletionItems(
|
||||
document: TextDocument,
|
||||
position: Position,
|
||||
token: CancellationToken
|
||||
): Promise<CompletionItem[]> {
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<vscode.CompletionItem[] | undefined> {
|
||||
const file = this.client.toPath(document.uri);
|
||||
if (!file) {
|
||||
return [];
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!this.isValidCursorPosition(document, position)) {
|
||||
return [];
|
||||
if (!this.isPotentiallyValidDocCompletionPosition(document, position)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!await this.isCommentableLocation(file, position, token)) {
|
||||
return [];
|
||||
const args = typeConverters.Position.toFileLocationRequestArgs(file, position);
|
||||
let res: Proto.DocCommandTemplateResponse | undefined;
|
||||
try {
|
||||
res = await this.client.execute('docCommentTemplate', args, token);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [new JsDocCompletionItem(document, position)];
|
||||
if (!res.body) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const item = new JsDocCompletionItem(document, position);
|
||||
|
||||
// Workaround for #43619
|
||||
// docCommentTemplate previously returned undefined for empty jsdoc templates.
|
||||
// TS 2.7 now returns a single line doc comment, which breaks indentation.
|
||||
if (res.body.newText === '/** */') {
|
||||
item.insertText = defaultJsDoc;
|
||||
} else {
|
||||
item.insertText = templateToSnippet(res.body.newText);
|
||||
}
|
||||
|
||||
return [item];
|
||||
}
|
||||
|
||||
private async isCommentableLocation(
|
||||
file: string,
|
||||
position: Position,
|
||||
token: CancellationToken
|
||||
): Promise<boolean> {
|
||||
const args: Proto.FileRequestArgs = {
|
||||
file
|
||||
};
|
||||
const response = await Promise.race([
|
||||
this.client.execute('navtree', args, token),
|
||||
new Promise<Proto.NavTreeResponse>((resolve) => setTimeout(resolve, 250))
|
||||
]);
|
||||
|
||||
if (!response || !response.body) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const body = response.body;
|
||||
|
||||
function matchesPosition(tree: Proto.NavigationTree): boolean {
|
||||
if (!tree.spans.length) {
|
||||
return false;
|
||||
}
|
||||
const span = typeConverters.Range.fromTextSpan(tree.spans[0]);
|
||||
if (position.line === span.start.line - 1 || position.line === span.start.line) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return tree.childItems ? tree.childItems.some(matchesPosition) : false;
|
||||
}
|
||||
|
||||
return matchesPosition(body);
|
||||
}
|
||||
|
||||
private isValidCursorPosition(document: TextDocument, position: Position): boolean {
|
||||
private isPotentiallyValidDocCompletionPosition(
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position
|
||||
): boolean {
|
||||
// Only show the JSdoc completion when the everything before the cursor is whitespace
|
||||
// or could be the opening of a comment
|
||||
const line = document.lineAt(position.line).text;
|
||||
const prefix = line.slice(0, position.character);
|
||||
return prefix.match(/^\s*$|\/\*\*\s*$|^\s*\/\*\*+\s*$/) !== null;
|
||||
}
|
||||
if (prefix.match(/^\s*$|\/\*\*\s*$|^\s*\/\*\*+\s*$/) === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public resolveCompletionItem(item: CompletionItem, _token: CancellationToken) {
|
||||
return item;
|
||||
// And everything after is possibly a closing comment or more whitespace
|
||||
const suffix = line.slice(position.character);
|
||||
return suffix.match(/^\s*\*+\//) !== null;
|
||||
}
|
||||
}
|
||||
|
||||
class TryCompleteJsDocCommand implements Command {
|
||||
public static readonly COMMAND_NAME = '_typeScript.tryCompleteJsDoc';
|
||||
public readonly id = TryCompleteJsDocCommand.COMMAND_NAME;
|
||||
|
||||
constructor(
|
||||
private readonly client: ITypeScriptServiceClient
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Try to insert a jsdoc comment, using a template provide by typescript
|
||||
* if possible, otherwise falling back to a default comment format.
|
||||
*/
|
||||
public async execute(resource: Uri, start: Position): Promise<boolean> {
|
||||
const file = this.client.toPath(resource);
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor || editor.document.uri.fsPath !== resource.fsPath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const didInsertFromTemplate = await this.tryInsertJsDocFromTemplate(editor, file, start);
|
||||
if (didInsertFromTemplate) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.tryInsertDefaultDoc(editor, start);
|
||||
}
|
||||
|
||||
private async tryInsertJsDocFromTemplate(editor: TextEditor, file: string, position: Position): Promise<boolean> {
|
||||
const snippet = await TryCompleteJsDocCommand.getSnippetTemplate(this.client, file, position);
|
||||
if (!snippet) {
|
||||
return false;
|
||||
}
|
||||
return editor.insertSnippet(
|
||||
snippet,
|
||||
position,
|
||||
{ undoStopBefore: false, undoStopAfter: true });
|
||||
}
|
||||
|
||||
public static getSnippetTemplate(client: ITypeScriptServiceClient, file: string, position: Position): Promise<SnippetString | undefined> {
|
||||
const args = typeConverters.Position.toFileLocationRequestArgs(file, position);
|
||||
return Promise.race([
|
||||
client.execute('docCommentTemplate', args),
|
||||
new Promise<Proto.DocCommandTemplateResponse>((_, reject) => setTimeout(reject, 250))
|
||||
]).then((res: Proto.DocCommandTemplateResponse) => {
|
||||
if (!res || !res.body) {
|
||||
return undefined;
|
||||
}
|
||||
// Workaround for #43619
|
||||
// docCommentTemplate previously returned undefined for empty jsdoc templates.
|
||||
// TS 2.7 now returns a single line doc comment, which breaks indentation.
|
||||
if (res.body.newText === '/** */') {
|
||||
return undefined;
|
||||
}
|
||||
return templateToSnippet(res.body.newText);
|
||||
}, () => undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the default JSDoc
|
||||
*/
|
||||
private tryInsertDefaultDoc(editor: TextEditor, position: Position): Thenable<boolean> {
|
||||
const snippet = new SnippetString(`/**\n * $0\n */`);
|
||||
return editor.insertSnippet(snippet, position, { undoStopBefore: false, undoStopAfter: true });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function templateToSnippet(template: string): SnippetString {
|
||||
export function templateToSnippet(template: string): vscode.SnippetString {
|
||||
// TODO: use append placeholder
|
||||
let snippetIndex = 1;
|
||||
template = template.replace(/\$/g, '\\$');
|
||||
@@ -204,17 +114,16 @@ export function templateToSnippet(template: string): SnippetString {
|
||||
out += post + ` \${${snippetIndex++}}`;
|
||||
return out;
|
||||
});
|
||||
return new SnippetString(template);
|
||||
return new vscode.SnippetString(template);
|
||||
}
|
||||
|
||||
export function register(
|
||||
selector: DocumentSelector,
|
||||
selector: vscode.DocumentSelector,
|
||||
client: ITypeScriptServiceClient,
|
||||
commandManager: CommandManager
|
||||
): Disposable {
|
||||
): vscode.Disposable {
|
||||
return new ConfigurationDependentRegistration('jsDocCompletion', 'enabled', () => {
|
||||
return languages.registerCompletionItemProvider(selector,
|
||||
new JsDocCompletionProvider(client, commandManager),
|
||||
return vscode.languages.registerCompletionItemProvider(selector,
|
||||
new JsDocCompletionProvider(client),
|
||||
'*');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,14 +9,12 @@
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { disposeAll } from '../utils/dispose';
|
||||
import { Disposable } from '../utils/dispose';
|
||||
import * as languageModeIds from '../utils/languageModeIds';
|
||||
|
||||
const jsTsLanguageConfiguration: vscode.LanguageConfiguration = {
|
||||
indentationRules: {
|
||||
// ^(.*\*/)?\s*\}.*$
|
||||
decreaseIndentPattern: /^((?!.*?\/\*).*\*\/)?\s*[\}\]\)].*$/,
|
||||
// ^.*\{[^}"']*$
|
||||
decreaseIndentPattern: /^((?!.*?\/\*).*\*\/)?\s*[\}\]].*$/,
|
||||
increaseIndentPattern: /^((?!\/\/).)*(\{[^}"'`]*|\([^)"'`]*|\[[^\]"'`]*)$/
|
||||
},
|
||||
wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,
|
||||
@@ -65,10 +63,10 @@ const jsxTagsLanguageConfiguration: vscode.LanguageConfiguration = {
|
||||
],
|
||||
};
|
||||
|
||||
export class LanguageConfigurationManager {
|
||||
private readonly _registrations: vscode.Disposable[] = [];
|
||||
export class LanguageConfigurationManager extends Disposable {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const standardLanguages = [
|
||||
languageModeIds.javascript,
|
||||
languageModeIds.javascriptreact,
|
||||
@@ -83,10 +81,6 @@ export class LanguageConfigurationManager {
|
||||
}
|
||||
|
||||
private registerConfiguration(language: string, config: vscode.LanguageConfiguration) {
|
||||
this._registrations.push(vscode.languages.setLanguageConfiguration(language, config));
|
||||
}
|
||||
|
||||
dispose() {
|
||||
disposeAll(this._registrations);
|
||||
this._register(vscode.languages.setLanguageConfiguration(language, config));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,13 @@ import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as Proto from '../protocol';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import API from '../utils/api';
|
||||
import { Command, CommandManager } from '../utils/commandManager';
|
||||
import { VersionDependentRegistration } from '../utils/dependentRegistration';
|
||||
import * as typeconverts from '../utils/typeConverters';
|
||||
import FileConfigurationManager from './fileConfigurationManager';
|
||||
import { VersionDependentRegistration } from '../utils/dependentRegistration';
|
||||
import API from '../utils/api';
|
||||
import TelemetryReporter from '../utils/telemetry';
|
||||
import { nulToken } from '../utils/cancellation';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -22,10 +24,20 @@ class OrganizeImportsCommand implements Command {
|
||||
public readonly id = OrganizeImportsCommand.Id;
|
||||
|
||||
constructor(
|
||||
private readonly client: ITypeScriptServiceClient
|
||||
private readonly client: ITypeScriptServiceClient,
|
||||
private readonly telemetryReporter: TelemetryReporter,
|
||||
) { }
|
||||
|
||||
public async execute(file: string): Promise<boolean> {
|
||||
/* __GDPR__
|
||||
"organizeImports.execute" : {
|
||||
"${include}": [
|
||||
"${TypeScriptCommonProperties}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.logTelemetry('organizeImports.execute', {});
|
||||
|
||||
const args: Proto.OrganizeImportsRequestArgs = {
|
||||
scope: {
|
||||
type: 'file',
|
||||
@@ -34,13 +46,9 @@ class OrganizeImportsCommand implements Command {
|
||||
}
|
||||
}
|
||||
};
|
||||
const response = await this.client.execute('organizeImports', args);
|
||||
if (!response || !response.success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const edits = typeconverts.WorkspaceEdit.fromFromFileCodeEdits(this.client, response.body);
|
||||
return await vscode.workspace.applyEdit(edits);
|
||||
const { body } = await this.client.execute('organizeImports', args, nulToken);
|
||||
const edits = typeconverts.WorkspaceEdit.fromFileCodeEdits(this.client, body);
|
||||
return vscode.workspace.applyEdit(edits);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,8 +57,10 @@ export class OrganizeImportsCodeActionProvider implements vscode.CodeActionProvi
|
||||
private readonly client: ITypeScriptServiceClient,
|
||||
commandManager: CommandManager,
|
||||
private readonly fileConfigManager: FileConfigurationManager,
|
||||
telemetryReporter: TelemetryReporter,
|
||||
|
||||
) {
|
||||
commandManager.register(new OrganizeImportsCommand(client));
|
||||
commandManager.register(new OrganizeImportsCommand(client, telemetryReporter));
|
||||
}
|
||||
|
||||
public readonly metadata: vscode.CodeActionProviderMetadata = {
|
||||
@@ -60,7 +70,7 @@ export class OrganizeImportsCodeActionProvider implements vscode.CodeActionProvi
|
||||
public provideCodeActions(
|
||||
document: vscode.TextDocument,
|
||||
_range: vscode.Range,
|
||||
_context: vscode.CodeActionContext,
|
||||
context: vscode.CodeActionContext,
|
||||
token: vscode.CancellationToken
|
||||
): vscode.CodeAction[] {
|
||||
const file = this.client.toPath(document.uri);
|
||||
@@ -68,6 +78,10 @@ export class OrganizeImportsCodeActionProvider implements vscode.CodeActionProvi
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!context.only || !context.only.contains(vscode.CodeActionKind.SourceOrganizeImports)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
this.fileConfigManager.ensureConfigurationForDocument(document, token);
|
||||
|
||||
const action = new vscode.CodeAction(
|
||||
@@ -82,10 +96,11 @@ export function register(
|
||||
selector: vscode.DocumentSelector,
|
||||
client: ITypeScriptServiceClient,
|
||||
commandManager: CommandManager,
|
||||
fileConfigurationManager: FileConfigurationManager
|
||||
fileConfigurationManager: FileConfigurationManager,
|
||||
telemetryReporter: TelemetryReporter,
|
||||
) {
|
||||
return new VersionDependentRegistration(client, API.v280, () => {
|
||||
const organizeImportsProvider = new OrganizeImportsCodeActionProvider(client, commandManager, fileConfigurationManager);
|
||||
const organizeImportsProvider = new OrganizeImportsCodeActionProvider(client, commandManager, fileConfigurationManager, telemetryReporter);
|
||||
return vscode.languages.registerCodeActionsProvider(selector,
|
||||
organizeImportsProvider,
|
||||
organizeImportsProvider.metadata);
|
||||
|
||||
@@ -10,10 +10,12 @@ import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import API from '../utils/api';
|
||||
import { applyCodeActionCommands, getEditForCodeAction } from '../utils/codeAction';
|
||||
import { Command, CommandManager } from '../utils/commandManager';
|
||||
import { VersionDependentRegistration } from '../utils/dependentRegistration';
|
||||
import TelemetryReporter from '../utils/telemetry';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
import { DiagnosticsManager } from './diagnostics';
|
||||
import FileConfigurationManager from './fileConfigurationManager';
|
||||
import { nulToken } from '../utils/cancellation';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -29,20 +31,19 @@ class ApplyCodeActionCommand implements Command {
|
||||
public async execute(
|
||||
action: Proto.CodeFixAction
|
||||
): Promise<boolean> {
|
||||
if (action.fixName) {
|
||||
/* __GDPR__
|
||||
"quickFix.execute" : {
|
||||
"fixName" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
|
||||
"${include}": [
|
||||
"${TypeScriptCommonProperties}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.logTelemetry('quickFix.execute', {
|
||||
fixName: action.fixName
|
||||
});
|
||||
}
|
||||
return applyCodeActionCommands(this.client, action);
|
||||
/* __GDPR__
|
||||
"quickFix.execute" : {
|
||||
"fixName" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
|
||||
"${include}": [
|
||||
"${TypeScriptCommonProperties}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.logTelemetry('quickFix.execute', {
|
||||
fixName: action.fixName
|
||||
});
|
||||
|
||||
return applyCodeActionCommands(this.client, action.commands, nulToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,19 +65,17 @@ class ApplyFixAllCodeAction implements Command {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tsAction.fixName) {
|
||||
/* __GDPR__
|
||||
"quickFixAll.execute" : {
|
||||
"fixName" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
|
||||
"${include}": [
|
||||
"${TypeScriptCommonProperties}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.logTelemetry('quickFixAll.execute', {
|
||||
fixName: tsAction.fixName
|
||||
});
|
||||
}
|
||||
/* __GDPR__
|
||||
"quickFixAll.execute" : {
|
||||
"fixName" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
|
||||
"${include}": [
|
||||
"${TypeScriptCommonProperties}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.logTelemetry('quickFixAll.execute', {
|
||||
fixName: tsAction.fixName
|
||||
});
|
||||
|
||||
const args: Proto.GetCombinedCodeFixRequestArgs = {
|
||||
scope: {
|
||||
@@ -87,17 +86,14 @@ class ApplyFixAllCodeAction implements Command {
|
||||
};
|
||||
|
||||
try {
|
||||
const combinedCodeFixesResponse = await this.client.execute('getCombinedCodeFix', args);
|
||||
if (!combinedCodeFixesResponse.body) {
|
||||
const { body } = await this.client.execute('getCombinedCodeFix', args, nulToken);
|
||||
if (!body) {
|
||||
return;
|
||||
}
|
||||
|
||||
const edit = typeConverters.WorkspaceEdit.fromFromFileCodeEdits(this.client, combinedCodeFixesResponse.body.changes);
|
||||
const edit = typeConverters.WorkspaceEdit.fromFileCodeEdits(this.client, body.changes);
|
||||
await vscode.workspace.applyEdit(edit);
|
||||
|
||||
if (combinedCodeFixesResponse.command) {
|
||||
await vscode.commands.executeCommand(ApplyCodeActionCommand.ID, combinedCodeFixesResponse.command);
|
||||
}
|
||||
await applyCodeActionCommands(this.client, body.commands, nulToken);
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
@@ -128,6 +124,34 @@ class DiagnosticsSet {
|
||||
public get values(): Iterable<vscode.Diagnostic> {
|
||||
return this._values.values();
|
||||
}
|
||||
|
||||
public get size() {
|
||||
return this._values.size;
|
||||
}
|
||||
}
|
||||
|
||||
class CodeActionSet {
|
||||
private _actions: vscode.CodeAction[] = [];
|
||||
private _fixAllActions = new Set<{}>();
|
||||
|
||||
public get values() {
|
||||
return this._actions;
|
||||
}
|
||||
|
||||
public addAction(action: vscode.CodeAction) {
|
||||
this._actions.push(action);
|
||||
}
|
||||
|
||||
public addFixAllAction(fixId: {}, action: vscode.CodeAction) {
|
||||
if (!this.hasFixAllAction(fixId)) {
|
||||
this.addAction(action);
|
||||
this._fixAllActions.add(fixId);
|
||||
}
|
||||
}
|
||||
|
||||
public hasFixAllAction(fixId: {}) {
|
||||
return this._fixAllActions.has(fixId);
|
||||
}
|
||||
}
|
||||
|
||||
class SupportedCodeActionProvider {
|
||||
@@ -137,15 +161,14 @@ class SupportedCodeActionProvider {
|
||||
private readonly client: ITypeScriptServiceClient
|
||||
) { }
|
||||
|
||||
public async getFixableDiagnosticsForContext(context: vscode.CodeActionContext): Promise<vscode.Diagnostic[]> {
|
||||
public async getFixableDiagnosticsForContext(context: vscode.CodeActionContext): Promise<DiagnosticsSet> {
|
||||
const supportedActions = await this.supportedCodeActions;
|
||||
const fixableDiagnostics = DiagnosticsSet.from(context.diagnostics.filter(diagnostic => supportedActions.has(+(diagnostic.code!))));
|
||||
return Array.from(fixableDiagnostics.values);
|
||||
return DiagnosticsSet.from(context.diagnostics.filter(diagnostic => supportedActions.has(+(diagnostic.code!))));
|
||||
}
|
||||
|
||||
private get supportedCodeActions(): Thenable<Set<number>> {
|
||||
if (!this._supportedCodeActions) {
|
||||
this._supportedCodeActions = this.client.execute('getSupportedCodeFixes', null, undefined)
|
||||
this._supportedCodeActions = this.client.execute('getSupportedCodeFixes', null, nulToken)
|
||||
.then(response => response.body || [])
|
||||
.then(codes => codes.map(code => +code).filter(code => !isNaN(code)))
|
||||
.then(codes => new Set(codes));
|
||||
@@ -156,6 +179,10 @@ class SupportedCodeActionProvider {
|
||||
|
||||
class TypeScriptQuickFixProvider implements vscode.CodeActionProvider {
|
||||
|
||||
public static readonly metadata: vscode.CodeActionProviderMetadata = {
|
||||
providedCodeActionKinds: [vscode.CodeActionKind.QuickFix]
|
||||
};
|
||||
|
||||
private readonly supportedCodeActionProvider: SupportedCodeActionProvider;
|
||||
|
||||
constructor(
|
||||
@@ -177,17 +204,13 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider {
|
||||
context: vscode.CodeActionContext,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<vscode.CodeAction[]> {
|
||||
if (!this.client.apiVersion.gte(API.v213)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const file = this.client.toPath(document.uri);
|
||||
if (!file) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const fixableDiagnostics = await this.supportedCodeActionProvider.getFixableDiagnosticsForContext(context);
|
||||
if (!fixableDiagnostics.length) {
|
||||
if (!fixableDiagnostics.size) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -198,7 +221,7 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider {
|
||||
await this.formattingConfigurationManager.ensureConfigurationForDocument(document, token);
|
||||
|
||||
const results: vscode.CodeAction[] = [];
|
||||
for (const diagnostic of fixableDiagnostics) {
|
||||
for (const diagnostic of fixableDiagnostics.values) {
|
||||
results.push(...await this.getFixesForDiagnostic(document, file, diagnostic, token));
|
||||
}
|
||||
return results;
|
||||
@@ -214,26 +237,28 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider {
|
||||
...typeConverters.Range.toFileRangeRequestArgs(file, diagnostic.range),
|
||||
errorCodes: [+(diagnostic.code!)]
|
||||
};
|
||||
const codeFixesResponse = await this.client.execute('getCodeFixes', args, token);
|
||||
if (codeFixesResponse.body) {
|
||||
const results: vscode.CodeAction[] = [];
|
||||
for (const tsCodeFix of codeFixesResponse.body) {
|
||||
results.push(...await this.getAllFixesForTsCodeAction(document, file, diagnostic, tsCodeFix));
|
||||
}
|
||||
return results;
|
||||
const { body } = await this.client.execute('getCodeFixes', args, token);
|
||||
if (!body) {
|
||||
return [];
|
||||
}
|
||||
return [];
|
||||
|
||||
const results = new CodeActionSet();
|
||||
for (const tsCodeFix of body) {
|
||||
this.addAllFixesForTsCodeAction(results, document, file, diagnostic, tsCodeFix);
|
||||
}
|
||||
return results.values;
|
||||
}
|
||||
|
||||
private async getAllFixesForTsCodeAction(
|
||||
private addAllFixesForTsCodeAction(
|
||||
results: CodeActionSet,
|
||||
document: vscode.TextDocument,
|
||||
file: string,
|
||||
diagnostic: vscode.Diagnostic,
|
||||
tsAction: Proto.CodeAction
|
||||
): Promise<Iterable<vscode.CodeAction>> {
|
||||
const singleFix = this.getSingleFixForTsCodeAction(diagnostic, tsAction);
|
||||
const fixAll = await this.getFixAllForTsCodeAction(document, file, diagnostic, tsAction as Proto.CodeFixAction);
|
||||
return fixAll ? [singleFix, fixAll] : [singleFix];
|
||||
): CodeActionSet {
|
||||
results.addAction(this.getSingleFixForTsCodeAction(diagnostic, tsAction));
|
||||
this.addFixAllForTsCodeAction(results, document, file, diagnostic, tsAction as Proto.CodeFixAction);
|
||||
return results;
|
||||
}
|
||||
|
||||
private getSingleFixForTsCodeAction(
|
||||
@@ -243,42 +268,41 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider {
|
||||
const codeAction = new vscode.CodeAction(tsAction.description, vscode.CodeActionKind.QuickFix);
|
||||
codeAction.edit = getEditForCodeAction(this.client, tsAction);
|
||||
codeAction.diagnostics = [diagnostic];
|
||||
if (tsAction.commands) {
|
||||
codeAction.command = {
|
||||
command: ApplyCodeActionCommand.ID,
|
||||
arguments: [tsAction],
|
||||
title: tsAction.description
|
||||
};
|
||||
}
|
||||
codeAction.command = {
|
||||
command: ApplyCodeActionCommand.ID,
|
||||
arguments: [tsAction],
|
||||
title: ''
|
||||
};
|
||||
return codeAction;
|
||||
}
|
||||
|
||||
private async getFixAllForTsCodeAction(
|
||||
private addFixAllForTsCodeAction(
|
||||
results: CodeActionSet,
|
||||
document: vscode.TextDocument,
|
||||
file: string,
|
||||
diagnostic: vscode.Diagnostic,
|
||||
tsAction: Proto.CodeFixAction,
|
||||
): Promise<vscode.CodeAction | undefined> {
|
||||
if (!tsAction.fixId || !this.client.apiVersion.gte(API.v270)) {
|
||||
return undefined;
|
||||
): CodeActionSet {
|
||||
if (!tsAction.fixId || !this.client.apiVersion.gte(API.v270) || results.hasFixAllAction(results)) {
|
||||
return results;
|
||||
}
|
||||
|
||||
// Make sure there are multiple diagnostics of the same type in the file
|
||||
if (!this.diagnosticsManager.getDiagnostics(document.uri).some(x => x.code === diagnostic.code && x !== diagnostic)) {
|
||||
return;
|
||||
return results;
|
||||
}
|
||||
|
||||
const action = new vscode.CodeAction(
|
||||
tsAction.fixAllDescription || localize('fixAllInFileLabel', '{0} (Fix all in file)', tsAction.description),
|
||||
vscode.CodeActionKind.QuickFix);
|
||||
action.diagnostics = [diagnostic];
|
||||
|
||||
action.command = {
|
||||
command: ApplyFixAllCodeAction.ID,
|
||||
arguments: [file, tsAction],
|
||||
title: ''
|
||||
};
|
||||
return action;
|
||||
results.addFixAllAction(tsAction.fixId, action);
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,6 +314,8 @@ export function register(
|
||||
diagnosticsManager: DiagnosticsManager,
|
||||
telemetryReporter: TelemetryReporter
|
||||
) {
|
||||
return vscode.languages.registerCodeActionsProvider(selector,
|
||||
new TypeScriptQuickFixProvider(client, fileConfigurationManager, commandManager, diagnosticsManager, telemetryReporter));
|
||||
return new VersionDependentRegistration(client, API.v213, () =>
|
||||
vscode.languages.registerCodeActionsProvider(selector,
|
||||
new TypeScriptQuickFixProvider(client, fileConfigurationManager, commandManager, diagnosticsManager, telemetryReporter),
|
||||
TypeScriptQuickFixProvider.metadata));
|
||||
}
|
||||
|
||||
@@ -4,22 +4,24 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
|
||||
import * as Proto from '../protocol';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import API from '../utils/api';
|
||||
import { Command, CommandManager } from '../utils/commandManager';
|
||||
import { VersionDependentRegistration } from '../utils/dependentRegistration';
|
||||
import TelemetryReporter from '../utils/telemetry';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
import FormattingOptionsManager from './fileConfigurationManager';
|
||||
import { CommandManager, Command } from '../utils/commandManager';
|
||||
import { VersionDependentRegistration } from '../utils/dependentRegistration';
|
||||
import API from '../utils/api';
|
||||
import { nulToken } from '../utils/cancellation';
|
||||
|
||||
|
||||
class ApplyRefactoringCommand implements Command {
|
||||
public static readonly ID = '_typescript.applyRefactoring';
|
||||
public readonly id = ApplyRefactoringCommand.ID;
|
||||
|
||||
constructor(
|
||||
private readonly client: ITypeScriptServiceClient
|
||||
private readonly client: ITypeScriptServiceClient,
|
||||
private readonly telemetryReporter: TelemetryReporter
|
||||
) { }
|
||||
|
||||
public async execute(
|
||||
@@ -29,36 +31,34 @@ class ApplyRefactoringCommand implements Command {
|
||||
action: string,
|
||||
range: vscode.Range
|
||||
): Promise<boolean> {
|
||||
/* __GDPR__
|
||||
"refactor.execute" : {
|
||||
"action" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
|
||||
"${include}": [
|
||||
"${TypeScriptCommonProperties}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.logTelemetry('refactor.execute', {
|
||||
action: action
|
||||
});
|
||||
|
||||
const args: Proto.GetEditsForRefactorRequestArgs = {
|
||||
...typeConverters.Range.toFileRangeRequestArgs(file, range),
|
||||
refactor,
|
||||
action
|
||||
};
|
||||
const response = await this.client.execute('getEditsForRefactor', args);
|
||||
if (!response || !response.body || !response.body.edits.length) {
|
||||
const { body } = await this.client.execute('getEditsForRefactor', args, nulToken);
|
||||
if (!body || !body.edits.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const edit of response.body.edits) {
|
||||
try {
|
||||
await vscode.workspace.openTextDocument(edit.fileName);
|
||||
} catch {
|
||||
try {
|
||||
if (!fs.existsSync(edit.fileName)) {
|
||||
fs.writeFileSync(edit.fileName, '');
|
||||
}
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const edit = typeConverters.WorkspaceEdit.fromFromFileCodeEdits(this.client, response.body.edits);
|
||||
if (!(await vscode.workspace.applyEdit(edit))) {
|
||||
const workspaceEdit = await this.toWorkspaceEdit(body);
|
||||
if (!(await vscode.workspace.applyEdit(workspaceEdit))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const renameLocation = response.body.renameLocation;
|
||||
const renameLocation = body.renameLocation;
|
||||
if (renameLocation) {
|
||||
await vscode.commands.executeCommand('editor.action.rename', [
|
||||
document.uri,
|
||||
@@ -67,6 +67,15 @@ class ApplyRefactoringCommand implements Command {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private async toWorkspaceEdit(body: Proto.RefactorEditInfo) {
|
||||
const workspaceEdit = new vscode.WorkspaceEdit();
|
||||
for (const edit of body.edits) {
|
||||
workspaceEdit.createFile(this.client.toResource(edit.fileName), { ignoreIfExists: true });
|
||||
}
|
||||
typeConverters.WorkspaceEdit.withFileCodeEdits(workspaceEdit, this.client, body.edits);
|
||||
return workspaceEdit;
|
||||
}
|
||||
}
|
||||
|
||||
class SelectRefactorCommand implements Command {
|
||||
@@ -102,9 +111,10 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider {
|
||||
constructor(
|
||||
private readonly client: ITypeScriptServiceClient,
|
||||
private readonly formattingOptionsManager: FormattingOptionsManager,
|
||||
commandManager: CommandManager
|
||||
commandManager: CommandManager,
|
||||
telemetryReporter: TelemetryReporter
|
||||
) {
|
||||
const doRefactoringCommand = commandManager.register(new ApplyRefactoringCommand(this.client));
|
||||
const doRefactoringCommand = commandManager.register(new ApplyRefactoringCommand(this.client, telemetryReporter));
|
||||
commandManager.register(new SelectRefactorCommand(doRefactoringCommand));
|
||||
}
|
||||
|
||||
@@ -127,20 +137,21 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
await this.formattingOptionsManager.ensureConfigurationForDocument(document, undefined);
|
||||
await this.formattingOptionsManager.ensureConfigurationForDocument(document, token);
|
||||
|
||||
const args: Proto.GetApplicableRefactorsRequestArgs = typeConverters.Range.toFileRangeRequestArgs(file, rangeOrSelection);
|
||||
let response: Proto.GetApplicableRefactorsResponse;
|
||||
let refactorings: Proto.ApplicableRefactorInfo[];
|
||||
try {
|
||||
response = await this.client.execute('getApplicableRefactors', args, token);
|
||||
if (!response || !response.body) {
|
||||
const { body } = await this.client.execute('getApplicableRefactors', args, token);
|
||||
if (!body) {
|
||||
return undefined;
|
||||
}
|
||||
refactorings = body;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.convertApplicableRefactors(response.body, document, file, rangeOrSelection);
|
||||
return this.convertApplicableRefactors(refactorings, document, file, rangeOrSelection);
|
||||
}
|
||||
|
||||
private convertApplicableRefactors(
|
||||
@@ -189,7 +200,7 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider {
|
||||
return false;
|
||||
}
|
||||
|
||||
return rangeOrSelection instanceof vscode.Selection && (!rangeOrSelection.isEmpty || context.triggerKind === vscode.CodeActionTrigger.Manual);
|
||||
return rangeOrSelection instanceof vscode.Selection && !rangeOrSelection.isEmpty;
|
||||
}
|
||||
|
||||
private static getKind(refactor: Proto.RefactorActionInfo) {
|
||||
@@ -209,10 +220,11 @@ export function register(
|
||||
client: ITypeScriptServiceClient,
|
||||
formattingOptionsManager: FormattingOptionsManager,
|
||||
commandManager: CommandManager,
|
||||
telemetryReporter: TelemetryReporter,
|
||||
) {
|
||||
return new VersionDependentRegistration(client, API.v240, () => {
|
||||
return vscode.languages.registerCodeActionsProvider(selector,
|
||||
new TypeScriptRefactorProvider(client, formattingOptionsManager, commandManager),
|
||||
new TypeScriptRefactorProvider(client, formattingOptionsManager, commandManager, telemetryReporter),
|
||||
TypeScriptRefactorProvider.metadata);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
import API from '../utils/api';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
|
||||
|
||||
class TypeScriptReferenceSupport implements vscode.ReferenceProvider {
|
||||
public constructor(
|
||||
@@ -26,13 +26,13 @@ class TypeScriptReferenceSupport implements vscode.ReferenceProvider {
|
||||
|
||||
const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
|
||||
try {
|
||||
const msg = await this.client.execute('references', args, token);
|
||||
if (!msg.body) {
|
||||
const { body } = await this.client.execute('references', args, token);
|
||||
if (!body) {
|
||||
return [];
|
||||
}
|
||||
const result: vscode.Location[] = [];
|
||||
const has203Features = this.client.apiVersion.gte(API.v203);
|
||||
for (const ref of msg.body.refs) {
|
||||
for (const ref of body.refs) {
|
||||
if (!options.includeDeclaration && has203Features && ref.isDefinition) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -10,38 +10,33 @@ import * as PConst from '../protocol.const';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import API from '../utils/api';
|
||||
import { ConfigurationDependentRegistration, VersionDependentRegistration } from '../utils/dependentRegistration';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
import { CachedNavTreeResponse, ReferencesCodeLens, TypeScriptBaseCodeLensProvider } from './baseCodeLensProvider';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLensProvider {
|
||||
|
||||
public resolveCodeLens(inputCodeLens: vscode.CodeLens, token: vscode.CancellationToken): Promise<vscode.CodeLens> {
|
||||
public resolveCodeLens(inputCodeLens: vscode.CodeLens, _token: vscode.CancellationToken): Thenable<vscode.CodeLens> {
|
||||
const codeLens = inputCodeLens as ReferencesCodeLens;
|
||||
const args = typeConverters.Position.toFileLocationRequestArgs(codeLens.file, codeLens.range.start);
|
||||
return this.client.execute('references', args, token).then(response => {
|
||||
if (!response || !response.body) {
|
||||
return vscode.commands.executeCommand<vscode.Location[]>('vscode.executeReferenceProvider', codeLens.document, codeLens.range.start).then((locations: vscode.Location[] | undefined) => {
|
||||
if (!locations) {
|
||||
throw codeLens;
|
||||
}
|
||||
|
||||
const locations = response.body.refs
|
||||
.map(reference =>
|
||||
typeConverters.Location.fromTextSpan(this.client.toResource(reference.file), reference))
|
||||
.filter(location =>
|
||||
// Exclude original definition from references
|
||||
!(location.uri.toString() === codeLens.document.toString() &&
|
||||
location.range.start.isEqual(codeLens.range.start)));
|
||||
const referenceLocations = locations.filter(location =>
|
||||
// Exclude original definition from references
|
||||
!(location.uri.toString() === codeLens.document.toString() &&
|
||||
location.range.start.isEqual(codeLens.range.start)));
|
||||
|
||||
codeLens.command = {
|
||||
title: locations.length === 1
|
||||
title: referenceLocations.length === 1
|
||||
? localize('oneReferenceLabel', '1 reference')
|
||||
: localize('manyReferenceLabel', '{0} references', locations.length),
|
||||
command: locations.length ? 'editor.action.showReferences' : '',
|
||||
arguments: [codeLens.document, codeLens.range.start, locations]
|
||||
: localize('manyReferenceLabel', '{0} references', referenceLocations.length),
|
||||
command: referenceLocations.length ? 'editor.action.showReferences' : '',
|
||||
arguments: [codeLens.document, codeLens.range.start, referenceLocations]
|
||||
};
|
||||
return codeLens;
|
||||
}).catch(() => {
|
||||
}).then(undefined, () => {
|
||||
codeLens.command = {
|
||||
title: localize('referenceErrorLabel', 'Could not determine references'),
|
||||
command: ''
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import * as Proto from '../protocol';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
|
||||
|
||||
class TypeScriptRenameProvider implements vscode.RenameProvider {
|
||||
public constructor(
|
||||
private readonly client: ITypeScriptServiceClient
|
||||
@@ -31,22 +31,22 @@ class TypeScriptRenameProvider implements vscode.RenameProvider {
|
||||
findInComments: false
|
||||
};
|
||||
|
||||
let body: Proto.RenameResponseBody | undefined;
|
||||
try {
|
||||
const response = await this.client.execute('rename', args, token);
|
||||
if (!response.body) {
|
||||
body = (await this.client.execute('rename', args, token)).body;
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const renameInfo = response.body.info;
|
||||
if (!renameInfo.canRename) {
|
||||
return Promise.reject<vscode.WorkspaceEdit>(renameInfo.localizedErrorMessage);
|
||||
}
|
||||
|
||||
return this.toWorkspaceEdit(response.body.locs, newName);
|
||||
} catch {
|
||||
// noop
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
|
||||
const renameInfo = body.info;
|
||||
if (!renameInfo.canRename) {
|
||||
return Promise.reject<vscode.WorkspaceEdit>(renameInfo.localizedErrorMessage);
|
||||
}
|
||||
return this.toWorkspaceEdit(body.locs, newName);
|
||||
}
|
||||
|
||||
private toWorkspaceEdit(
|
||||
|
||||
@@ -20,21 +20,25 @@ class TypeScriptSignatureHelpProvider implements vscode.SignatureHelpProvider {
|
||||
public async provideSignatureHelp(
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position,
|
||||
token: vscode.CancellationToken
|
||||
token: vscode.CancellationToken,
|
||||
context?: vscode.SignatureHelpContext,
|
||||
): Promise<vscode.SignatureHelp | undefined> {
|
||||
const filepath = this.client.toPath(document.uri);
|
||||
if (!filepath) {
|
||||
return undefined;
|
||||
}
|
||||
const args: Proto.SignatureHelpRequestArgs = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
|
||||
const args: Proto.SignatureHelpRequestArgs = {
|
||||
...typeConverters.Position.toFileLocationRequestArgs(filepath, position),
|
||||
triggerReason: toTsTriggerReason(context!)
|
||||
};
|
||||
|
||||
let info: Proto.SignatureHelpItems | undefined = undefined;
|
||||
let info: Proto.SignatureHelpItems;
|
||||
try {
|
||||
const response = await this.client.execute('signatureHelp', args, token);
|
||||
info = response.body;
|
||||
if (!info) {
|
||||
const { body } = await this.client.execute('signatureHelp', args, token);
|
||||
if (!body) {
|
||||
return undefined;
|
||||
}
|
||||
info = body;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
@@ -71,6 +75,23 @@ class TypeScriptSignatureHelpProvider implements vscode.SignatureHelpProvider {
|
||||
}
|
||||
}
|
||||
|
||||
function toTsTriggerReason(context: vscode.SignatureHelpContext): Proto.SignatureHelpTriggerReason {
|
||||
switch (context.triggerReason) {
|
||||
case vscode.SignatureHelpTriggerReason.Retrigger:
|
||||
return { kind: 'retrigger' };
|
||||
|
||||
case vscode.SignatureHelpTriggerReason.TriggerCharacter:
|
||||
if (context.triggerCharacter) {
|
||||
return { kind: 'characterTyped', triggerCharacter: context.triggerCharacter as any };
|
||||
} else {
|
||||
return { kind: 'invoked' };
|
||||
}
|
||||
|
||||
case vscode.SignatureHelpTriggerReason.Invoke:
|
||||
default:
|
||||
return { kind: 'invoked' };
|
||||
}
|
||||
}
|
||||
export function register(
|
||||
selector: vscode.DocumentSelector,
|
||||
client: ITypeScriptServiceClient,
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as Proto from '../protocol';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import API from '../utils/api';
|
||||
import { ConditionalRegistration, ConfigurationDependentRegistration, VersionDependentRegistration } from '../utils/dependentRegistration';
|
||||
import { Disposable } from '../utils/dispose';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
|
||||
class TagClosing extends Disposable {
|
||||
|
||||
private _disposed = false;
|
||||
private _timeout: NodeJS.Timer | undefined = undefined;
|
||||
private _cancel: vscode.CancellationTokenSource | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
private readonly client: ITypeScriptServiceClient
|
||||
) {
|
||||
super();
|
||||
vscode.workspace.onDidChangeTextDocument(
|
||||
event => this.onDidChangeTextDocument(event.document, event.contentChanges),
|
||||
null,
|
||||
this._disposables);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
super.dispose();
|
||||
this._disposed = true;
|
||||
|
||||
if (this._timeout) {
|
||||
clearTimeout(this._timeout);
|
||||
this._timeout = undefined;
|
||||
}
|
||||
|
||||
if (this._cancel) {
|
||||
this._cancel.cancel();
|
||||
this._cancel.dispose();
|
||||
this._cancel = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private onDidChangeTextDocument(
|
||||
document: vscode.TextDocument,
|
||||
changes: vscode.TextDocumentContentChangeEvent[]
|
||||
) {
|
||||
const activeDocument = vscode.window.activeTextEditor && vscode.window.activeTextEditor.document;
|
||||
if (document !== activeDocument || changes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filepath = this.client.toPath(document.uri);
|
||||
if (!filepath) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof this._timeout !== 'undefined') {
|
||||
clearTimeout(this._timeout);
|
||||
}
|
||||
|
||||
if (this._cancel) {
|
||||
this._cancel.cancel();
|
||||
this._cancel.dispose();
|
||||
this._cancel = undefined;
|
||||
}
|
||||
|
||||
const lastChange = changes[changes.length - 1];
|
||||
const lastCharacter = lastChange.text[lastChange.text.length - 1];
|
||||
if (lastChange.rangeLength > 0 || lastCharacter !== '>' && lastCharacter !== '/') {
|
||||
return;
|
||||
}
|
||||
|
||||
const priorCharacter = lastChange.range.start.character > 0
|
||||
? document.getText(new vscode.Range(lastChange.range.start.translate({ characterDelta: -1 }), lastChange.range.start))
|
||||
: '';
|
||||
if (priorCharacter === '>') {
|
||||
return;
|
||||
}
|
||||
|
||||
const rangeStart = lastChange.range.start;
|
||||
const version = document.version;
|
||||
this._timeout = setTimeout(async () => {
|
||||
this._timeout = undefined;
|
||||
|
||||
if (this._disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
let position = new vscode.Position(rangeStart.line, rangeStart.character + lastChange.text.length);
|
||||
let insertion: Proto.TextInsertion;
|
||||
const args: Proto.JsxClosingTagRequestArgs = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
|
||||
|
||||
this._cancel = new vscode.CancellationTokenSource();
|
||||
try {
|
||||
const { body } = await this.client.execute('jsxClosingTag', args, this._cancel.token);
|
||||
if (!body) {
|
||||
return;
|
||||
}
|
||||
insertion = body;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeEditor = vscode.window.activeTextEditor;
|
||||
if (!activeEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeDocument = activeEditor.document;
|
||||
if (document === activeDocument && activeDocument.version === version) {
|
||||
activeEditor.insertSnippet(
|
||||
this.getTagSnippet(insertion),
|
||||
this.getInsertionPositions(activeEditor, position));
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
private getTagSnippet(closingTag: Proto.TextInsertion): vscode.SnippetString {
|
||||
const snippet = new vscode.SnippetString();
|
||||
snippet.appendPlaceholder('', 0);
|
||||
snippet.appendText(closingTag.newText);
|
||||
return snippet;
|
||||
}
|
||||
|
||||
private getInsertionPositions(editor: vscode.TextEditor, position: vscode.Position) {
|
||||
const activeSelectionPositions = editor.selections.map(s => s.active);
|
||||
return activeSelectionPositions.some(p => p.isEqual(position))
|
||||
? activeSelectionPositions
|
||||
: position;
|
||||
}
|
||||
}
|
||||
|
||||
export class ActiveDocumentDependentRegistration extends Disposable {
|
||||
private readonly _registration: ConditionalRegistration;
|
||||
|
||||
constructor(
|
||||
private readonly selector: vscode.DocumentSelector,
|
||||
register: () => vscode.Disposable,
|
||||
) {
|
||||
super();
|
||||
this._registration = this._register(new ConditionalRegistration(register));
|
||||
vscode.window.onDidChangeActiveTextEditor(this.update, this, this._disposables);
|
||||
this.update();
|
||||
}
|
||||
|
||||
private update() {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
const enabled = !!(editor && vscode.languages.match(this.selector, editor.document));
|
||||
this._registration.update(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
export function register(
|
||||
selector: vscode.DocumentSelector,
|
||||
modeId: string,
|
||||
client: ITypeScriptServiceClient,
|
||||
) {
|
||||
return new VersionDependentRegistration(client, API.v300, () =>
|
||||
new ConfigurationDependentRegistration(modeId, 'autoClosingTags', () =>
|
||||
new ActiveDocumentDependentRegistration(selector, () =>
|
||||
new TagClosing(client))));
|
||||
}
|
||||
@@ -8,14 +8,14 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as Proto from '../protocol';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import TsConfigProvider, { TSConfig } from '../utils/tsconfigProvider';
|
||||
import { isImplicitProjectConfigFile } from '../utils/tsconfig';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
import { Lazy } from '../utils/lazy';
|
||||
import { isImplicitProjectConfigFile } from '../utils/tsconfig';
|
||||
import TsConfigProvider, { TSConfig } from '../utils/tsconfigProvider';
|
||||
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
type AutoDetect = 'on' | 'off' | 'build' | 'watch';
|
||||
@@ -78,7 +78,10 @@ class TscTaskProvider implements vscode.TaskProvider {
|
||||
|
||||
private async getAllTsConfigs(token: vscode.CancellationToken): Promise<TSConfig[]> {
|
||||
const out = new Set<TSConfig>();
|
||||
const configs = (await this.getTsConfigForActiveFile(token)).concat(await this.getTsConfigsInWorkspace());
|
||||
const configs = [
|
||||
...await this.getTsConfigForActiveFile(token),
|
||||
...await this.getTsConfigsInWorkspace()
|
||||
];
|
||||
for (const config of configs) {
|
||||
if (await exists(config.path)) {
|
||||
out.add(config);
|
||||
|
||||
115
extensions/typescript-language-features/src/features/tsconfig.ts
Normal file
115
extensions/typescript-language-features/src/features/tsconfig.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as jsonc from 'jsonc-parser';
|
||||
import { dirname, join, basename } from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { flatten } from '../utils/arrays';
|
||||
|
||||
function mapChildren<R>(node: jsonc.Node | undefined, f: (x: jsonc.Node) => R): R[] {
|
||||
return node && node.type === 'array' && node.children
|
||||
? node.children.map(f)
|
||||
: [];
|
||||
}
|
||||
|
||||
class TsconfigLinkProvider implements vscode.DocumentLinkProvider {
|
||||
|
||||
public provideDocumentLinks(
|
||||
document: vscode.TextDocument,
|
||||
_token: vscode.CancellationToken
|
||||
): vscode.ProviderResult<vscode.DocumentLink[]> {
|
||||
const root = jsonc.parseTree(document.getText());
|
||||
if (!root) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
this.getExendsLink(document, root),
|
||||
...this.getFilesLinks(document, root),
|
||||
...this.getReferencesLinks(document, root)
|
||||
].filter(x => !!x) as vscode.DocumentLink[];
|
||||
}
|
||||
|
||||
private getExendsLink(document: vscode.TextDocument, root: jsonc.Node): vscode.DocumentLink | undefined {
|
||||
const extendsNode = jsonc.findNodeAtLocation(root, ['extends']);
|
||||
if (!this.isPathValue(extendsNode)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return new vscode.DocumentLink(
|
||||
this.getRange(document, extendsNode),
|
||||
basename(extendsNode.value).match('.json$')
|
||||
? this.getFileTarget(document, extendsNode)
|
||||
: vscode.Uri.file(join(dirname(document.uri.fsPath), extendsNode!.value + '.json')));
|
||||
}
|
||||
|
||||
private getFilesLinks(document: vscode.TextDocument, root: jsonc.Node) {
|
||||
return mapChildren(
|
||||
jsonc.findNodeAtLocation(root, ['files']),
|
||||
child => this.pathNodeToLink(document, child));
|
||||
}
|
||||
|
||||
private getReferencesLinks(document: vscode.TextDocument, root: jsonc.Node) {
|
||||
return mapChildren(
|
||||
jsonc.findNodeAtLocation(root, ['references']),
|
||||
child => {
|
||||
const pathNode = jsonc.findNodeAtLocation(child, ['path']);
|
||||
if (!this.isPathValue(pathNode)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return new vscode.DocumentLink(this.getRange(document, pathNode),
|
||||
basename(pathNode.value).match('.json$')
|
||||
? this.getFileTarget(document, pathNode)
|
||||
: this.getFolderTarget(document, pathNode));
|
||||
});
|
||||
}
|
||||
|
||||
private pathNodeToLink(
|
||||
document: vscode.TextDocument,
|
||||
node: jsonc.Node | undefined
|
||||
): vscode.DocumentLink | undefined {
|
||||
return this.isPathValue(node)
|
||||
? new vscode.DocumentLink(this.getRange(document, node), this.getFileTarget(document, node))
|
||||
: undefined;
|
||||
}
|
||||
|
||||
private isPathValue(extendsNode: jsonc.Node | undefined): extendsNode is jsonc.Node {
|
||||
return extendsNode
|
||||
&& extendsNode.type === 'string'
|
||||
&& extendsNode.value
|
||||
&& !(extendsNode.value as string).includes('*'); // don't treat globs as links.
|
||||
}
|
||||
|
||||
private getFileTarget(document: vscode.TextDocument, node: jsonc.Node): vscode.Uri {
|
||||
return vscode.Uri.file(join(dirname(document.uri.fsPath), node!.value));
|
||||
}
|
||||
|
||||
private getFolderTarget(document: vscode.TextDocument, node: jsonc.Node): vscode.Uri {
|
||||
return vscode.Uri.file(join(dirname(document.uri.fsPath), node!.value, 'tsconfig.json'));
|
||||
}
|
||||
|
||||
private getRange(document: vscode.TextDocument, node: jsonc.Node) {
|
||||
const offset = node!.offset;
|
||||
const start = document.positionAt(offset + 1);
|
||||
const end = document.positionAt(offset + (node!.length - 1));
|
||||
return new vscode.Range(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
export function register() {
|
||||
const patterns: vscode.GlobPattern[] = [
|
||||
'**/[jt]sconfig.json',
|
||||
'**/[jt]sconfig.*.json',
|
||||
];
|
||||
|
||||
const languages = ['json', 'jsonc'];
|
||||
|
||||
const selector: vscode.DocumentSelector = flatten(
|
||||
languages.map(language =>
|
||||
patterns.map((pattern): vscode.DocumentFilter => ({ language, pattern }))));
|
||||
|
||||
return vscode.languages.registerDocumentLinkProvider(selector, new TsconfigLinkProvider());
|
||||
}
|
||||
@@ -5,12 +5,12 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import API from '../utils/api';
|
||||
import { VersionDependentRegistration } from '../utils/dependentRegistration';
|
||||
import DefinitionProviderBase from './definitionProviderBase';
|
||||
import API from '../utils/api';
|
||||
|
||||
export default class TypeScriptTypeDefinitionProvider extends DefinitionProviderBase implements vscode.TypeDefinitionProvider {
|
||||
public provideTypeDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken | boolean): Promise<vscode.Definition | undefined> {
|
||||
public provideTypeDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.Definition | undefined> {
|
||||
return this.getSymbolLocations('typeDefinition', document, position, token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,20 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as Proto from '../protocol';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import API from '../utils/api';
|
||||
import * as languageIds from '../utils/languageModeIds';
|
||||
import * as fileSchemes from '../utils/fileSchemes';
|
||||
import { isTypeScriptDocument } from '../utils/languageModeIds';
|
||||
import { escapeRegExp } from '../utils/regexp';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
import FileConfigurationManager from './fileConfigurationManager';
|
||||
import { VersionDependentRegistration } from '../utils/dependentRegistration';
|
||||
import { nulToken } from '../utils/cancellation';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -23,16 +28,16 @@ enum UpdateImportsOnFileMoveSetting {
|
||||
Never = 'never',
|
||||
}
|
||||
|
||||
export class UpdateImportsOnFileRenameHandler {
|
||||
class UpdateImportsOnFileRenameHandler {
|
||||
private readonly _onDidRenameSub: vscode.Disposable;
|
||||
|
||||
public constructor(
|
||||
private readonly client: ITypeScriptServiceClient,
|
||||
private readonly fileConfigurationManager: FileConfigurationManager,
|
||||
private readonly handles: (uri: vscode.Uri) => Promise<boolean>,
|
||||
private readonly _handles: (uri: vscode.Uri) => Promise<boolean>,
|
||||
) {
|
||||
this._onDidRenameSub = vscode.workspace.onDidRenameResource(e => {
|
||||
this.doRename(e.oldResource, e.newResource);
|
||||
this._onDidRenameSub = vscode.workspace.onDidRenameFile(e => {
|
||||
this.doRename(e.oldUri, e.newUri);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -44,11 +49,13 @@ export class UpdateImportsOnFileRenameHandler {
|
||||
oldResource: vscode.Uri,
|
||||
newResource: vscode.Uri,
|
||||
): Promise<void> {
|
||||
if (!this.client.apiVersion.gte(API.v290)) {
|
||||
const targetResource = await this.getTargetResource(newResource);
|
||||
if (!targetResource) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await this.handles(newResource)) {
|
||||
const targetFile = this.client.toPath(targetResource);
|
||||
if (!targetFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -62,7 +69,7 @@ export class UpdateImportsOnFileRenameHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
const document = await vscode.workspace.openTextDocument(newResource);
|
||||
const document = await vscode.workspace.openTextDocument(targetResource);
|
||||
|
||||
const config = this.getConfiguration(document);
|
||||
const setting = config.get<UpdateImportsOnFileMoveSetting>(updateImportsOnFileMoveName);
|
||||
@@ -71,20 +78,37 @@ export class UpdateImportsOnFileRenameHandler {
|
||||
}
|
||||
|
||||
// Make sure TS knows about file
|
||||
this.client.bufferSyncSupport.closeResource(oldResource);
|
||||
this.client.bufferSyncSupport.closeResource(targetResource);
|
||||
this.client.bufferSyncSupport.openTextDocument(document);
|
||||
|
||||
const edits = await this.getEditsForFileRename(document, oldFile, newFile);
|
||||
if (!this.client.apiVersion.gte(API.v300) && !fs.lstatSync(newResource.fsPath).isDirectory()) {
|
||||
// Workaround for https://github.com/Microsoft/vscode/issues/52967
|
||||
// Never attempt to update import paths if the file does not contain something the looks like an export
|
||||
try {
|
||||
const { body } = await this.client.execute('navtree', { file: newFile }, nulToken);
|
||||
const hasExport = (node: Proto.NavigationTree): boolean => {
|
||||
return !!node.kindModifiers.match(/\bexports?\b/g) || !!(node.childItems && node.childItems.some(hasExport));
|
||||
};
|
||||
if (!body || !hasExport(body)) {
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
const edits = await this.getEditsForFileRename(targetFile, document, oldFile, newFile);
|
||||
if (!edits || !edits.size) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (await this.confirmActionWithUser(document)) {
|
||||
if (await this.confirmActionWithUser(newResource, document)) {
|
||||
await vscode.workspace.applyEdit(edits);
|
||||
}
|
||||
}
|
||||
|
||||
private async confirmActionWithUser(
|
||||
newResource: vscode.Uri,
|
||||
newDocument: vscode.TextDocument
|
||||
): Promise<boolean> {
|
||||
const config = this.getConfiguration(newDocument);
|
||||
@@ -96,7 +120,7 @@ export class UpdateImportsOnFileRenameHandler {
|
||||
return false;
|
||||
case UpdateImportsOnFileMoveSetting.Prompt:
|
||||
default:
|
||||
return this.promptUser(newDocument);
|
||||
return this.promptUser(newResource, newDocument);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +129,7 @@ export class UpdateImportsOnFileRenameHandler {
|
||||
}
|
||||
|
||||
private async promptUser(
|
||||
newResource: vscode.Uri,
|
||||
newDocument: vscode.TextDocument
|
||||
): Promise<boolean> {
|
||||
enum Choice {
|
||||
@@ -120,7 +145,7 @@ export class UpdateImportsOnFileRenameHandler {
|
||||
}
|
||||
|
||||
const response = await vscode.window.showInformationMessage<Item>(
|
||||
localize('prompt', "Automatically update imports for moved file: '{0}'?", path.basename(newDocument.fileName)), {
|
||||
localize('prompt', "Automatically update imports for moved file: '{0}'?", path.basename(newResource.fsPath)), {
|
||||
modal: true,
|
||||
},
|
||||
{
|
||||
@@ -177,27 +202,113 @@ export class UpdateImportsOnFileRenameHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
private async getTargetResource(resource: vscode.Uri): Promise<vscode.Uri | undefined> {
|
||||
if (resource.scheme !== fileSchemes.file) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const isDirectory = fs.lstatSync(resource.fsPath).isDirectory();
|
||||
if (isDirectory && this.client.apiVersion.gte(API.v300)) {
|
||||
return resource;
|
||||
}
|
||||
|
||||
if (isDirectory && this.client.apiVersion.gte(API.v292)) {
|
||||
const files = await vscode.workspace.findFiles({
|
||||
base: resource.fsPath,
|
||||
pattern: '**/*.{ts,tsx,js,jsx}',
|
||||
}, '**/node_modules/**', 1);
|
||||
return files[0];
|
||||
}
|
||||
|
||||
return (await this._handles(resource)) ? resource : undefined;
|
||||
}
|
||||
|
||||
private async getEditsForFileRename(
|
||||
targetResource: string,
|
||||
document: vscode.TextDocument,
|
||||
oldFile: string,
|
||||
newFile: string,
|
||||
) {
|
||||
await this.fileConfigurationManager.ensureConfigurationForDocument(document, undefined);
|
||||
const isDirectoryRename = fs.lstatSync(newFile).isDirectory();
|
||||
await this.fileConfigurationManager.setGlobalConfigurationFromDocument(document, nulToken);
|
||||
|
||||
const args: Proto.GetEditsForFileRenameRequestArgs = {
|
||||
file: newFile,
|
||||
const args: Proto.GetEditsForFileRenameRequestArgs & { file: string } = {
|
||||
file: targetResource,
|
||||
oldFilePath: oldFile,
|
||||
newFilePath: newFile,
|
||||
};
|
||||
const response = await this.client.execute('getEditsForFileRename', args);
|
||||
const response = await this.client.execute('getEditsForFileRename', args, nulToken);
|
||||
if (!response || !response.body) {
|
||||
return;
|
||||
}
|
||||
|
||||
return typeConverters.WorkspaceEdit.fromFromFileCodeEdits(this.client, response.body);
|
||||
const edits: Proto.FileCodeEdits[] = [];
|
||||
for (const edit of response.body) {
|
||||
// Workaround for https://github.com/Microsoft/vscode/issues/52675
|
||||
if (!this.client.apiVersion.gte(API.v300)) {
|
||||
if ((edit as Proto.FileCodeEdits).fileName.match(/[\/\\]node_modules[\/\\]/gi)) {
|
||||
continue;
|
||||
}
|
||||
for (const change of (edit as Proto.FileCodeEdits).textChanges) {
|
||||
if (change.newText.match(/\/node_modules\//gi)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
edits.push(await this.fixEdit(edit, isDirectoryRename, oldFile, newFile));
|
||||
}
|
||||
return typeConverters.WorkspaceEdit.fromFileCodeEdits(this.client, edits);
|
||||
}
|
||||
|
||||
private async fixEdit(
|
||||
edit: Proto.FileCodeEdits,
|
||||
isDirectoryRename: boolean,
|
||||
oldFile: string,
|
||||
newFile: string,
|
||||
): Promise<Proto.FileCodeEdits> {
|
||||
if (!isDirectoryRename || this.client.apiVersion.gte(API.v300)) {
|
||||
return edit;
|
||||
}
|
||||
|
||||
const document = await vscode.workspace.openTextDocument(edit.fileName);
|
||||
const oldFileRe = new RegExp('/' + escapeRegExp(path.basename(oldFile)) + '/');
|
||||
|
||||
// Workaround for https://github.com/Microsoft/TypeScript/issues/24968
|
||||
const textChanges = edit.textChanges.map((change): Proto.CodeEdit => {
|
||||
const existingText = document.getText(typeConverters.Range.fromTextSpan(change));
|
||||
const existingMatch = existingText.match(oldFileRe);
|
||||
if (!existingMatch) {
|
||||
return change;
|
||||
}
|
||||
|
||||
const match = new RegExp('/' + escapeRegExp(path.basename(newFile)) + '/(.+)$', 'g').exec(change.newText);
|
||||
if (!match) {
|
||||
return change;
|
||||
}
|
||||
|
||||
return {
|
||||
newText: change.newText.slice(0, -match[1].length),
|
||||
start: change.start,
|
||||
end: {
|
||||
line: change.end.line,
|
||||
offset: change.end.offset - match[1].length
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
fileName: edit.fileName,
|
||||
textChanges
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function isTypeScriptDocument(document: vscode.TextDocument) {
|
||||
return document.languageId === languageIds.typescript || document.languageId === languageIds.typescriptreact;
|
||||
}
|
||||
export function register(
|
||||
client: ITypeScriptServiceClient,
|
||||
fileConfigurationManager: FileConfigurationManager,
|
||||
handles: (uri: vscode.Uri) => Promise<boolean>,
|
||||
) {
|
||||
return new VersionDependentRegistration(client, API.v290, () =>
|
||||
new UpdateImportsOnFileRenameHandler(client, fileConfigurationManager, handles));
|
||||
}
|
||||
@@ -44,13 +44,13 @@ class TypeScriptWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvide
|
||||
file: filepath,
|
||||
searchValue: search
|
||||
};
|
||||
const response = await this.client.execute('navto', args, token);
|
||||
if (!response.body) {
|
||||
const { body } = await this.client.execute('navto', args, token);
|
||||
if (!body) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const result: vscode.SymbolInformation[] = [];
|
||||
for (const item of response.body) {
|
||||
for (const item of body) {
|
||||
if (!item.containerName && item.kind === 'alias') {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -3,34 +3,25 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { basename } from 'path';
|
||||
|
||||
import TypeScriptServiceClient from './typescriptServiceClient';
|
||||
import TypingsStatus from './utils/typingsStatus';
|
||||
import FileConfigurationManager from './features/fileConfigurationManager';
|
||||
import { CommandManager } from './utils/commandManager';
|
||||
import { DiagnosticsManager, DiagnosticKind } from './features/diagnostics';
|
||||
import { LanguageDescription } from './utils/languageDescription';
|
||||
import * as fileSchemes from './utils/fileSchemes';
|
||||
import * as vscode from 'vscode';
|
||||
import { CachedNavTreeResponse } from './features/baseCodeLensProvider';
|
||||
import { DiagnosticKind } from './features/diagnostics';
|
||||
import FileConfigurationManager from './features/fileConfigurationManager';
|
||||
import TypeScriptServiceClient from './typescriptServiceClient';
|
||||
import { CommandManager } from './utils/commandManager';
|
||||
import { Disposable } from './utils/dispose';
|
||||
import * as fileSchemes from './utils/fileSchemes';
|
||||
import { LanguageDescription } from './utils/languageDescription';
|
||||
import { memoize } from './utils/memoize';
|
||||
import { disposeAll } from './utils/dispose';
|
||||
import TelemetryReporter from './utils/telemetry';
|
||||
import { UpdateImportsOnFileRenameHandler } from './features/updatePathsOnRename';
|
||||
import TypingsStatus from './utils/typingsStatus';
|
||||
|
||||
|
||||
const validateSetting = 'validate.enable';
|
||||
const suggestionSetting = 'suggestionActions.enabled';
|
||||
|
||||
export default class LanguageProvider {
|
||||
private readonly diagnosticsManager: DiagnosticsManager;
|
||||
|
||||
private _validate: boolean = true;
|
||||
private _enableSuggestionDiagnostics: boolean = true;
|
||||
|
||||
private readonly disposables: vscode.Disposable[] = [];
|
||||
|
||||
private readonly renameHandler: UpdateImportsOnFileRenameHandler;
|
||||
export default class LanguageProvider extends Disposable {
|
||||
|
||||
constructor(
|
||||
private readonly client: TypeScriptServiceClient,
|
||||
@@ -40,35 +31,15 @@ export default class LanguageProvider {
|
||||
private readonly typingsStatus: TypingsStatus,
|
||||
private readonly fileConfigurationManager: FileConfigurationManager
|
||||
) {
|
||||
this.client.bufferSyncSupport.onDelete(resource => {
|
||||
this.diagnosticsManager.delete(resource);
|
||||
}, null, this.disposables);
|
||||
|
||||
this.diagnosticsManager = new DiagnosticsManager(description.diagnosticOwner);
|
||||
|
||||
vscode.workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables);
|
||||
super();
|
||||
vscode.workspace.onDidChangeConfiguration(this.configurationChanged, this, this._disposables);
|
||||
this.configurationChanged();
|
||||
|
||||
client.onReady(async () => {
|
||||
await this.registerProviders();
|
||||
});
|
||||
|
||||
this.renameHandler = new UpdateImportsOnFileRenameHandler(this.client, this.fileConfigurationManager, async uri => {
|
||||
try {
|
||||
const doc = await vscode.workspace.openTextDocument(uri);
|
||||
return this.handles(uri, doc);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
disposeAll(this.disposables);
|
||||
|
||||
this.diagnosticsManager.dispose();
|
||||
this.renameHandler.dispose();
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get documentSelector(): vscode.DocumentFilter[] {
|
||||
@@ -86,26 +57,27 @@ export default class LanguageProvider {
|
||||
|
||||
const cachedResponse = new CachedNavTreeResponse();
|
||||
|
||||
this.disposables.push((await import('./features/completions')).register(selector, this.client, this.typingsStatus, this.fileConfigurationManager, this.commandManager));
|
||||
this.disposables.push((await import('./features/definitions')).register(selector, this.client));
|
||||
this.disposables.push((await import('./features/directiveCommentCompletions')).register(selector, this.client));
|
||||
this.disposables.push((await import('./features/documentHighlight')).register(selector, this.client));
|
||||
this.disposables.push((await import('./features/documentSymbol')).register(selector, this.client));
|
||||
this.disposables.push((await import('./features/folding')).register(selector, this.client));
|
||||
this.disposables.push((await import('./features/formatting')).register(selector, this.description.id, this.client, this.fileConfigurationManager));
|
||||
this.disposables.push((await import('./features/hover')).register(selector, this.client));
|
||||
this.disposables.push((await import('./features/implementations')).register(selector, this.client));
|
||||
this.disposables.push((await import('./features/implementationsCodeLens')).register(selector, this.description.id, this.client, cachedResponse));
|
||||
this.disposables.push((await import('./features/jsDocCompletions')).register(selector, this.client, this.commandManager));
|
||||
this.disposables.push((await import('./features/organizeImports')).register(selector, this.client, this.commandManager, this.fileConfigurationManager));
|
||||
this.disposables.push((await import('./features/quickFix')).register(selector, this.client, this.fileConfigurationManager, this.commandManager, this.diagnosticsManager, this.telemetryReporter));
|
||||
this.disposables.push((await import('./features/refactor')).register(selector, this.client, this.fileConfigurationManager, this.commandManager));
|
||||
this.disposables.push((await import('./features/references')).register(selector, this.client));
|
||||
this.disposables.push((await import('./features/referencesCodeLens')).register(selector, this.description.id, this.client, cachedResponse));
|
||||
this.disposables.push((await import('./features/rename')).register(selector, this.client));
|
||||
this.disposables.push((await import('./features/signatureHelp')).register(selector, this.client));
|
||||
this.disposables.push((await import('./features/typeDefinitions')).register(selector, this.client));
|
||||
this.disposables.push((await import('./features/workspaceSymbols')).register(this.client, this.description.modeIds));
|
||||
this._register((await import('./features/completions')).register(selector, this.description.id, this.client, this.typingsStatus, this.fileConfigurationManager, this.commandManager));
|
||||
this._register((await import('./features/definitions')).register(selector, this.client));
|
||||
this._register((await import('./features/directiveCommentCompletions')).register(selector, this.client));
|
||||
this._register((await import('./features/documentHighlight')).register(selector, this.client));
|
||||
this._register((await import('./features/documentSymbol')).register(selector, this.client));
|
||||
this._register((await import('./features/folding')).register(selector, this.client));
|
||||
this._register((await import('./features/formatting')).register(selector, this.description.id, this.client, this.fileConfigurationManager));
|
||||
this._register((await import('./features/hover')).register(selector, this.client));
|
||||
this._register((await import('./features/implementations')).register(selector, this.client));
|
||||
this._register((await import('./features/implementationsCodeLens')).register(selector, this.description.id, this.client, cachedResponse));
|
||||
this._register((await import('./features/jsDocCompletions')).register(selector, this.client));
|
||||
this._register((await import('./features/organizeImports')).register(selector, this.client, this.commandManager, this.fileConfigurationManager, this.telemetryReporter));
|
||||
this._register((await import('./features/quickFix')).register(selector, this.client, this.fileConfigurationManager, this.commandManager, this.client.diagnosticsManager, this.telemetryReporter));
|
||||
this._register((await import('./features/refactor')).register(selector, this.client, this.fileConfigurationManager, this.commandManager, this.telemetryReporter));
|
||||
this._register((await import('./features/references')).register(selector, this.client));
|
||||
this._register((await import('./features/referencesCodeLens')).register(selector, this.description.id, this.client, cachedResponse));
|
||||
this._register((await import('./features/rename')).register(selector, this.client));
|
||||
this._register((await import('./features/signatureHelp')).register(selector, this.client));
|
||||
this._register((await import('./features/tagClosing')).register(selector, this.description.id, this.client));
|
||||
this._register((await import('./features/typeDefinitions')).register(selector, this.client));
|
||||
this._register((await import('./features/workspaceSymbols')).register(this.client, this.description.modeIds));
|
||||
}
|
||||
|
||||
private configurationChanged(): void {
|
||||
@@ -119,10 +91,6 @@ export default class LanguageProvider {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.client.bufferSyncSupport.handles(resource)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const base = basename(resource.fsPath);
|
||||
return !!base && base === this.description.configFile;
|
||||
}
|
||||
@@ -136,30 +104,15 @@ export default class LanguageProvider {
|
||||
}
|
||||
|
||||
private updateValidate(value: boolean) {
|
||||
if (this._validate === value) {
|
||||
return;
|
||||
}
|
||||
this._validate = value;
|
||||
this.diagnosticsManager.validate = value;
|
||||
if (value) {
|
||||
this.triggerAllDiagnostics();
|
||||
}
|
||||
this.client.diagnosticsManager.setValidate(this._diagnosticLanguage, value);
|
||||
}
|
||||
|
||||
private updateSuggestionDiagnostics(value: boolean) {
|
||||
if (this._enableSuggestionDiagnostics === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._enableSuggestionDiagnostics = value;
|
||||
this.diagnosticsManager.enableSuggestions = value;
|
||||
if (value) {
|
||||
this.triggerAllDiagnostics();
|
||||
}
|
||||
this.client.diagnosticsManager.setEnableSuggestions(this._diagnosticLanguage, value);
|
||||
}
|
||||
|
||||
public reInitialize(): void {
|
||||
this.diagnosticsManager.reInitialize();
|
||||
this.client.diagnosticsManager.reInitialize();
|
||||
}
|
||||
|
||||
public triggerAllDiagnostics(): void {
|
||||
@@ -169,9 +122,9 @@ export default class LanguageProvider {
|
||||
public diagnosticsReceived(diagnosticsKind: DiagnosticKind, file: vscode.Uri, diagnostics: (vscode.Diagnostic & { reportUnnecessary: any })[]): void {
|
||||
const config = vscode.workspace.getConfiguration(this.id, file);
|
||||
const reportUnnecessary = config.get<boolean>('showUnused', true);
|
||||
this.diagnosticsManager.diagnosticsReceived(diagnosticsKind, file, diagnostics.filter(diag => {
|
||||
this.client.diagnosticsManager.updateDiagnostics(file, this._diagnosticLanguage, diagnosticsKind, diagnostics.filter(diag => {
|
||||
if (!reportUnnecessary) {
|
||||
diag.customTags = undefined;
|
||||
diag.tags = undefined;
|
||||
if (diag.reportUnnecessary && diag.severity === vscode.DiagnosticSeverity.Hint) {
|
||||
return false;
|
||||
}
|
||||
@@ -181,6 +134,10 @@ export default class LanguageProvider {
|
||||
}
|
||||
|
||||
public configFileDiagnosticsReceived(file: vscode.Uri, diagnostics: vscode.Diagnostic[]): void {
|
||||
this.diagnosticsManager.configFileDiagnosticsReceived(file, diagnostics);
|
||||
this.client.diagnosticsManager.configFileDiagnosticsReceived(file, diagnostics);
|
||||
}
|
||||
|
||||
private get _diagnosticLanguage() {
|
||||
return this.description.diagnosticLanguage;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ export class Kind {
|
||||
public static readonly directory = 'directory';
|
||||
public static readonly enum = 'enum';
|
||||
public static readonly externalModuleName = 'external module name';
|
||||
public static readonly file = 'file';
|
||||
public static readonly function = 'function';
|
||||
public static readonly indexSignature = 'index';
|
||||
public static readonly interface = 'interface';
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
import * as Proto from 'typescript/lib/protocol';
|
||||
export = Proto;
|
||||
export = Proto;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import 'mocha';
|
||||
import { tagsMarkdownPreview } from '../utils/previewer';
|
||||
|
||||
suite('typescript.previewer', () => {
|
||||
test('Should ignore hyphens after a param tag', async () => {
|
||||
assert.strictEqual(
|
||||
tagsMarkdownPreview([
|
||||
{
|
||||
name: 'param',
|
||||
text: 'a - b'
|
||||
}
|
||||
]),
|
||||
'*@param* `a` — b');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,25 +8,23 @@
|
||||
* https://github.com/Microsoft/TypeScript-Sublime-Plugin/blob/master/TypeScript%20Indent.tmPreferences
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
import { workspace, Memento, Diagnostic, Range, Disposable, Uri, DiagnosticSeverity, DiagnosticTag, DiagnosticRelatedInformation } from 'vscode';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { DiagnosticKind } from './features/diagnostics';
|
||||
import FileConfigurationManager from './features/fileConfigurationManager';
|
||||
import { register as registerUpdatePathsOnRename } from './features/updatePathsOnRename';
|
||||
import LanguageProvider from './languageProvider';
|
||||
import * as Proto from './protocol';
|
||||
import * as PConst from './protocol.const';
|
||||
|
||||
import TypeScriptServiceClient from './typescriptServiceClient';
|
||||
import LanguageProvider from './languageProvider';
|
||||
|
||||
import TypingsStatus, { AtaProgressReporter } from './utils/typingsStatus';
|
||||
import VersionStatus from './utils/versionStatus';
|
||||
import API from './utils/api';
|
||||
import { CommandManager } from './utils/commandManager';
|
||||
import { Disposable } from './utils/dispose';
|
||||
import { LanguageDescription, DiagnosticLanguage } from './utils/languageDescription';
|
||||
import LogDirectoryProvider from './utils/logDirectoryProvider';
|
||||
import { TypeScriptServerPlugin } from './utils/plugins';
|
||||
import * as typeConverters from './utils/typeConverters';
|
||||
import { CommandManager } from './utils/commandManager';
|
||||
import { LanguageDescription } from './utils/languageDescription';
|
||||
import LogDirectoryProvider from './utils/logDirectoryProvider';
|
||||
import { disposeAll } from './utils/dispose';
|
||||
import { DiagnosticKind } from './features/diagnostics';
|
||||
import API from './utils/api';
|
||||
import FileConfigurationManager from './features/fileConfigurationManager';
|
||||
import TypingsStatus, { AtaProgressReporter } from './utils/typingsStatus';
|
||||
import VersionStatus from './utils/versionStatus';
|
||||
|
||||
// Style check diagnostics that can be reported as warnings
|
||||
const styleCheckDiagnostics = [
|
||||
@@ -38,13 +36,11 @@ const styleCheckDiagnostics = [
|
||||
7030 // not all code paths return a value
|
||||
];
|
||||
|
||||
export default class TypeScriptServiceClientHost {
|
||||
private readonly ataProgressReporter: AtaProgressReporter;
|
||||
export default class TypeScriptServiceClientHost extends Disposable {
|
||||
private readonly typingsStatus: TypingsStatus;
|
||||
private readonly client: TypeScriptServiceClient;
|
||||
private readonly languages: LanguageProvider[] = [];
|
||||
private readonly languagePerId = new Map<string, LanguageProvider>();
|
||||
private readonly disposables: Disposable[] = [];
|
||||
private readonly versionStatus: VersionStatus;
|
||||
private readonly fileConfigurationManager: FileConfigurationManager;
|
||||
|
||||
@@ -52,13 +48,14 @@ export default class TypeScriptServiceClientHost {
|
||||
|
||||
constructor(
|
||||
descriptions: LanguageDescription[],
|
||||
workspaceState: Memento,
|
||||
workspaceState: vscode.Memento,
|
||||
plugins: TypeScriptServerPlugin[],
|
||||
private readonly commandManager: CommandManager,
|
||||
logDirectoryProvider: LogDirectoryProvider
|
||||
) {
|
||||
super();
|
||||
const handleProjectCreateOrDelete = () => {
|
||||
this.client.execute('reloadProjects', null, false);
|
||||
this.client.executeWithoutWaitingForResponse('reloadProjects', null);
|
||||
this.triggerAllDiagnostics();
|
||||
};
|
||||
const handleProjectChange = () => {
|
||||
@@ -66,11 +63,11 @@ export default class TypeScriptServiceClientHost {
|
||||
this.triggerAllDiagnostics();
|
||||
}, 1500);
|
||||
};
|
||||
const configFileWatcher = workspace.createFileSystemWatcher('**/[tj]sconfig.json');
|
||||
this.disposables.push(configFileWatcher);
|
||||
configFileWatcher.onDidCreate(handleProjectCreateOrDelete, this, this.disposables);
|
||||
configFileWatcher.onDidDelete(handleProjectCreateOrDelete, this, this.disposables);
|
||||
configFileWatcher.onDidChange(handleProjectChange, this, this.disposables);
|
||||
const configFileWatcher = vscode.workspace.createFileSystemWatcher('**/[tj]sconfig.json');
|
||||
this._register(configFileWatcher);
|
||||
configFileWatcher.onDidCreate(handleProjectCreateOrDelete, this, this._disposables);
|
||||
configFileWatcher.onDidDelete(handleProjectCreateOrDelete, this, this._disposables);
|
||||
configFileWatcher.onDidChange(handleProjectChange, this, this._disposables);
|
||||
|
||||
const allModeIds = this.getAllModeIds(descriptions);
|
||||
this.client = new TypeScriptServiceClient(
|
||||
@@ -79,30 +76,31 @@ export default class TypeScriptServiceClientHost {
|
||||
plugins,
|
||||
logDirectoryProvider,
|
||||
allModeIds);
|
||||
this.disposables.push(this.client);
|
||||
this._register(this.client);
|
||||
|
||||
this.client.onDiagnosticsReceived(({ kind, resource, diagnostics }) => {
|
||||
this.diagnosticsReceived(kind, resource, diagnostics);
|
||||
}, null, this.disposables);
|
||||
}, null, this._disposables);
|
||||
|
||||
this.client.onConfigDiagnosticsReceived(diag => this.configFileDiagnosticsReceived(diag), null, this.disposables);
|
||||
this.client.onResendModelsRequested(() => this.populateService(), null, this.disposables);
|
||||
this.client.onConfigDiagnosticsReceived(diag => this.configFileDiagnosticsReceived(diag), null, this._disposables);
|
||||
this.client.onResendModelsRequested(() => this.populateService(), null, this._disposables);
|
||||
|
||||
this.versionStatus = new VersionStatus(resource => this.client.toPath(resource));
|
||||
this.disposables.push(this.versionStatus);
|
||||
|
||||
this.typingsStatus = new TypingsStatus(this.client);
|
||||
this.ataProgressReporter = new AtaProgressReporter(this.client);
|
||||
this.fileConfigurationManager = new FileConfigurationManager(this.client);
|
||||
this._register(this.versionStatus);
|
||||
|
||||
this._register(new AtaProgressReporter(this.client));
|
||||
this.typingsStatus = this._register(new TypingsStatus(this.client));
|
||||
this.fileConfigurationManager = this._register(new FileConfigurationManager(this.client));
|
||||
|
||||
for (const description of descriptions) {
|
||||
const manager = new LanguageProvider(this.client, description, this.commandManager, this.client.telemetryReporter, this.typingsStatus, this.fileConfigurationManager);
|
||||
this.languages.push(manager);
|
||||
this.disposables.push(manager);
|
||||
this._register(manager);
|
||||
this.languagePerId.set(description.id, manager);
|
||||
}
|
||||
|
||||
this._register(registerUpdatePathsOnRename(this.client, this.fileConfigurationManager, uri => this.handles(uri)));
|
||||
|
||||
this.client.ensureServiceStarted();
|
||||
this.client.onReady(() => {
|
||||
if (!this.client.apiVersion.gte(API.v230)) {
|
||||
@@ -119,13 +117,14 @@ export default class TypeScriptServiceClientHost {
|
||||
const description: LanguageDescription = {
|
||||
id: 'typescript-plugins',
|
||||
modeIds: Array.from(languages.values()),
|
||||
diagnosticSource: 'ts-plugins',
|
||||
diagnosticSource: 'ts-plugin',
|
||||
diagnosticLanguage: DiagnosticLanguage.TypeScript,
|
||||
diagnosticOwner: 'typescript',
|
||||
isExternal: true
|
||||
};
|
||||
const manager = new LanguageProvider(this.client, description, this.commandManager, this.client.telemetryReporter, this.typingsStatus, this.fileConfigurationManager);
|
||||
this.languages.push(manager);
|
||||
this.disposables.push(manager);
|
||||
this._register(manager);
|
||||
this.languagePerId.set(description.id, manager);
|
||||
}
|
||||
});
|
||||
@@ -134,7 +133,7 @@ export default class TypeScriptServiceClientHost {
|
||||
this.triggerAllDiagnostics();
|
||||
});
|
||||
|
||||
workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables);
|
||||
vscode.workspace.onDidChangeConfiguration(this.configurationChanged, this, this._disposables);
|
||||
this.configurationChanged();
|
||||
}
|
||||
|
||||
@@ -146,35 +145,32 @@ export default class TypeScriptServiceClientHost {
|
||||
return allModeIds;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
disposeAll(this.disposables);
|
||||
this.typingsStatus.dispose();
|
||||
this.ataProgressReporter.dispose();
|
||||
this.fileConfigurationManager.dispose();
|
||||
}
|
||||
|
||||
public get serviceClient(): TypeScriptServiceClient {
|
||||
return this.client;
|
||||
}
|
||||
|
||||
public reloadProjects(): void {
|
||||
this.client.execute('reloadProjects', null, false);
|
||||
this.client.executeWithoutWaitingForResponse('reloadProjects', null);
|
||||
this.triggerAllDiagnostics();
|
||||
}
|
||||
|
||||
public handles(resource: Uri): boolean {
|
||||
return !!this.findLanguage(resource);
|
||||
public async handles(resource: vscode.Uri): Promise<boolean> {
|
||||
const provider = await this.findLanguage(resource);
|
||||
if (provider) {
|
||||
return true;
|
||||
}
|
||||
return this.client.bufferSyncSupport.handles(resource);
|
||||
}
|
||||
|
||||
private configurationChanged(): void {
|
||||
const typescriptConfig = workspace.getConfiguration('typescript');
|
||||
const typescriptConfig = vscode.workspace.getConfiguration('typescript');
|
||||
|
||||
this.reportStyleCheckAsWarnings = typescriptConfig.get('reportStyleChecksAsWarnings', true);
|
||||
}
|
||||
|
||||
private async findLanguage(resource: Uri): Promise<LanguageProvider | undefined> {
|
||||
private async findLanguage(resource: vscode.Uri): Promise<LanguageProvider | undefined> {
|
||||
try {
|
||||
const doc = await workspace.openTextDocument(resource);
|
||||
const doc = await vscode.workspace.openTextDocument(resource);
|
||||
return this.languages.find(language => language.handles(resource, doc));
|
||||
} catch {
|
||||
return undefined;
|
||||
@@ -193,7 +189,7 @@ export default class TypeScriptServiceClientHost {
|
||||
this.client.bufferSyncSupport.requestAllDiagnostics();
|
||||
|
||||
// See https://github.com/Microsoft/TypeScript/issues/5530
|
||||
workspace.saveAll(false).then(() => {
|
||||
vscode.workspace.saveAll(false).then(() => {
|
||||
for (const language of this.languagePerId.values()) {
|
||||
language.reInitialize();
|
||||
}
|
||||
@@ -202,7 +198,7 @@ export default class TypeScriptServiceClientHost {
|
||||
|
||||
private async diagnosticsReceived(
|
||||
kind: DiagnosticKind,
|
||||
resource: Uri,
|
||||
resource: vscode.Uri,
|
||||
diagnostics: Proto.Diagnostic[]
|
||||
): Promise<void> {
|
||||
const language = await this.findLanguage(resource);
|
||||
@@ -228,10 +224,10 @@ export default class TypeScriptServiceClientHost {
|
||||
if (body.diagnostics.length === 0) {
|
||||
language.configFileDiagnosticsReceived(this.client.toResource(body.configFile), []);
|
||||
} else if (body.diagnostics.length >= 1) {
|
||||
workspace.openTextDocument(Uri.file(body.configFile)).then((document) => {
|
||||
vscode.workspace.openTextDocument(vscode.Uri.file(body.configFile)).then((document) => {
|
||||
let curly: [number, number, number] | undefined = undefined;
|
||||
let nonCurly: [number, number, number] | undefined = undefined;
|
||||
let diagnostic: Diagnostic;
|
||||
let diagnostic: vscode.Diagnostic;
|
||||
for (let index = 0; index < document.lineCount; index++) {
|
||||
const line = document.lineAt(index);
|
||||
const text = line.text;
|
||||
@@ -250,16 +246,16 @@ export default class TypeScriptServiceClientHost {
|
||||
}
|
||||
const match = curly || nonCurly;
|
||||
if (match) {
|
||||
diagnostic = new Diagnostic(new Range(match[0], match[1], match[0], match[2]), body.diagnostics[0].text);
|
||||
diagnostic = new vscode.Diagnostic(new vscode.Range(match[0], match[1], match[0], match[2]), body.diagnostics[0].text);
|
||||
} else {
|
||||
diagnostic = new Diagnostic(new Range(0, 0, 0, 0), body.diagnostics[0].text);
|
||||
diagnostic = new vscode.Diagnostic(new vscode.Range(0, 0, 0, 0), body.diagnostics[0].text);
|
||||
}
|
||||
if (diagnostic) {
|
||||
diagnostic.source = language.diagnosticSource;
|
||||
language.configFileDiagnosticsReceived(this.client.toResource(body.configFile), [diagnostic]);
|
||||
}
|
||||
}, _error => {
|
||||
language.configFileDiagnosticsReceived(this.client.toResource(body.configFile), [new Diagnostic(new Range(0, 0, 0, 0), body.diagnostics[0].text)]);
|
||||
language.configFileDiagnosticsReceived(this.client.toResource(body.configFile), [new vscode.Diagnostic(new vscode.Range(0, 0, 0, 0), body.diagnostics[0].text)]);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -268,57 +264,56 @@ export default class TypeScriptServiceClientHost {
|
||||
private createMarkerDatas(
|
||||
diagnostics: Proto.Diagnostic[],
|
||||
source: string
|
||||
): (Diagnostic & { reportUnnecessary: any })[] {
|
||||
): (vscode.Diagnostic & { reportUnnecessary: any })[] {
|
||||
return diagnostics.map(tsDiag => this.tsDiagnosticToVsDiagnostic(tsDiag, source));
|
||||
}
|
||||
|
||||
private tsDiagnosticToVsDiagnostic(diagnostic: Proto.Diagnostic, source: string): Diagnostic & { reportUnnecessary: any } {
|
||||
private tsDiagnosticToVsDiagnostic(diagnostic: Proto.Diagnostic, source: string): vscode.Diagnostic & { reportUnnecessary: any } {
|
||||
const { start, end, text } = diagnostic;
|
||||
const range = new Range(typeConverters.Position.fromLocation(start), typeConverters.Position.fromLocation(end));
|
||||
const converted = new Diagnostic(range, text);
|
||||
const range = new vscode.Range(typeConverters.Position.fromLocation(start), typeConverters.Position.fromLocation(end));
|
||||
const converted = new vscode.Diagnostic(range, text);
|
||||
converted.severity = this.getDiagnosticSeverity(diagnostic);
|
||||
converted.source = diagnostic.source || source;
|
||||
if (diagnostic.code) {
|
||||
converted.code = diagnostic.code;
|
||||
}
|
||||
// TODO: requires TS 3.0
|
||||
const relatedInformation = (diagnostic as any).relatedInformation;
|
||||
const relatedInformation = diagnostic.relatedInformation;
|
||||
if (relatedInformation) {
|
||||
converted.relatedInformation = relatedInformation.map((info: any) => {
|
||||
let span = info.span;
|
||||
if (!span) {
|
||||
return undefined;
|
||||
}
|
||||
return new DiagnosticRelatedInformation(typeConverters.Location.fromTextSpan(this.client.toResource(span.file), span), info.message);
|
||||
}).filter((x: any) => !!x) as DiagnosticRelatedInformation[];
|
||||
return new vscode.DiagnosticRelatedInformation(typeConverters.Location.fromTextSpan(this.client.toResource(span.file), span), info.message);
|
||||
}).filter((x: any) => !!x) as vscode.DiagnosticRelatedInformation[];
|
||||
}
|
||||
if (diagnostic.reportsUnnecessary) {
|
||||
converted.customTags = [DiagnosticTag.Unnecessary];
|
||||
converted.tags = [vscode.DiagnosticTag.Unnecessary];
|
||||
}
|
||||
(converted as Diagnostic & { reportUnnecessary: any }).reportUnnecessary = diagnostic.reportsUnnecessary;
|
||||
return converted as Diagnostic & { reportUnnecessary: any };
|
||||
(converted as vscode.Diagnostic & { reportUnnecessary: any }).reportUnnecessary = diagnostic.reportsUnnecessary;
|
||||
return converted as vscode.Diagnostic & { reportUnnecessary: any };
|
||||
}
|
||||
|
||||
private getDiagnosticSeverity(diagnostic: Proto.Diagnostic): DiagnosticSeverity {
|
||||
private getDiagnosticSeverity(diagnostic: Proto.Diagnostic): vscode.DiagnosticSeverity {
|
||||
if (this.reportStyleCheckAsWarnings
|
||||
&& this.isStyleCheckDiagnostic(diagnostic.code)
|
||||
&& diagnostic.category === PConst.DiagnosticCategory.error
|
||||
) {
|
||||
return DiagnosticSeverity.Warning;
|
||||
return vscode.DiagnosticSeverity.Warning;
|
||||
}
|
||||
|
||||
switch (diagnostic.category) {
|
||||
case PConst.DiagnosticCategory.error:
|
||||
return DiagnosticSeverity.Error;
|
||||
return vscode.DiagnosticSeverity.Error;
|
||||
|
||||
case PConst.DiagnosticCategory.warning:
|
||||
return DiagnosticSeverity.Warning;
|
||||
return vscode.DiagnosticSeverity.Warning;
|
||||
|
||||
case PConst.DiagnosticCategory.suggestion:
|
||||
return DiagnosticSeverity.Hint;
|
||||
return vscode.DiagnosticSeverity.Hint;
|
||||
|
||||
default:
|
||||
return DiagnosticSeverity.Error;
|
||||
return vscode.DiagnosticSeverity.Error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,46 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken, Uri, Event } from 'vscode';
|
||||
import * as vscode from 'vscode';
|
||||
import BufferSyncSupport from './features/bufferSyncSupport';
|
||||
import * as Proto from './protocol';
|
||||
import API from './utils/api';
|
||||
import { TypeScriptServerPlugin } from './utils/plugins';
|
||||
import { TypeScriptServiceConfiguration } from './utils/configuration';
|
||||
import Logger from './utils/logger';
|
||||
import BufferSyncSupport from './features/bufferSyncSupport';
|
||||
import { TypeScriptServerPlugin } from './utils/plugins';
|
||||
|
||||
interface TypeScriptRequestTypes {
|
||||
'applyCodeActionCommand': [Proto.ApplyCodeActionCommandRequestArgs, Proto.ApplyCodeActionCommandResponse];
|
||||
'completionEntryDetails': [Proto.CompletionDetailsRequestArgs, Proto.CompletionDetailsResponse];
|
||||
'completionInfo': [Proto.CompletionsRequestArgs, Proto.CompletionInfoResponse];
|
||||
'completions': [Proto.CompletionsRequestArgs, Proto.CompletionsResponse];
|
||||
'configure': [Proto.ConfigureRequestArguments, Proto.ConfigureResponse];
|
||||
'definition': [Proto.FileLocationRequestArgs, Proto.DefinitionResponse];
|
||||
'definitionAndBoundSpan': [Proto.FileLocationRequestArgs, Proto.DefinitionInfoAndBoundSpanReponse];
|
||||
'docCommentTemplate': [Proto.FileLocationRequestArgs, Proto.DocCommandTemplateResponse];
|
||||
'format': [Proto.FormatRequestArgs, Proto.FormatResponse];
|
||||
'formatonkey': [Proto.FormatOnKeyRequestArgs, Proto.FormatResponse];
|
||||
'getApplicableRefactors': [Proto.GetApplicableRefactorsRequestArgs, Proto.GetApplicableRefactorsResponse];
|
||||
'getCodeFixes': [Proto.CodeFixRequestArgs, Proto.GetCodeFixesResponse];
|
||||
'getCombinedCodeFix': [Proto.GetCombinedCodeFixRequestArgs, Proto.GetCombinedCodeFixResponse];
|
||||
'getEditsForFileRename': [Proto.GetEditsForFileRenameRequestArgs, Proto.GetEditsForFileRenameResponse];
|
||||
'getEditsForRefactor': [Proto.GetEditsForRefactorRequestArgs, Proto.GetEditsForRefactorResponse];
|
||||
'getOutliningSpans': [Proto.FileRequestArgs, Proto.OutliningSpansResponse];
|
||||
'getSupportedCodeFixes': [null, Proto.GetSupportedCodeFixesResponse];
|
||||
'implementation': [Proto.FileLocationRequestArgs, Proto.ImplementationResponse];
|
||||
'jsxClosingTag': [Proto.JsxClosingTagRequestArgs, Proto.JsxClosingTagResponse];
|
||||
'navto': [Proto.NavtoRequestArgs, Proto.NavtoResponse];
|
||||
'navtree': [Proto.FileRequestArgs, Proto.NavTreeResponse];
|
||||
'occurrences': [Proto.FileLocationRequestArgs, Proto.OccurrencesResponse];
|
||||
'organizeImports': [Proto.OrganizeImportsRequestArgs, Proto.OrganizeImportsResponse];
|
||||
'projectInfo': [Proto.ProjectInfoRequestArgs, Proto.ProjectInfoResponse];
|
||||
'quickinfo': [Proto.FileLocationRequestArgs, Proto.QuickInfoResponse];
|
||||
'references': [Proto.FileLocationRequestArgs, Proto.ReferencesResponse];
|
||||
'rename': [Proto.RenameRequestArgs, Proto.RenameResponse];
|
||||
'signatureHelp': [Proto.SignatureHelpRequestArgs, Proto.SignatureHelpResponse];
|
||||
'typeDefinition': [Proto.FileLocationRequestArgs, Proto.TypeDefinitionResponse];
|
||||
}
|
||||
|
||||
|
||||
export interface ITypeScriptServiceClient {
|
||||
/**
|
||||
@@ -17,27 +50,27 @@ export interface ITypeScriptServiceClient {
|
||||
*
|
||||
* Does not try handling case insensitivity.
|
||||
*/
|
||||
normalizedPath(resource: Uri): string | null;
|
||||
normalizedPath(resource: vscode.Uri): string | null;
|
||||
|
||||
/**
|
||||
* Map a resource to a normalized path
|
||||
*
|
||||
* This will attempt to handle case insensitivity.
|
||||
*/
|
||||
toPath(resource: Uri): string | null;
|
||||
toPath(resource: vscode.Uri): string | null;
|
||||
|
||||
/**
|
||||
* Convert a path to a resource.
|
||||
*/
|
||||
toResource(filepath: string): Uri;
|
||||
toResource(filepath: string): vscode.Uri;
|
||||
|
||||
getWorkspaceRootForResource(resource: Uri): string | undefined;
|
||||
getWorkspaceRootForResource(resource: vscode.Uri): string | undefined;
|
||||
|
||||
readonly onTsServerStarted: Event<API>;
|
||||
readonly onProjectLanguageServiceStateChanged: Event<Proto.ProjectLanguageServiceStateEventBody>;
|
||||
readonly onDidBeginInstallTypings: Event<Proto.BeginInstallTypesEventBody>;
|
||||
readonly onDidEndInstallTypings: Event<Proto.EndInstallTypesEventBody>;
|
||||
readonly onTypesInstallerInitializationFailed: Event<Proto.TypesInstallerInitializationFailedEventBody>;
|
||||
readonly onTsServerStarted: vscode.Event<API>;
|
||||
readonly onProjectLanguageServiceStateChanged: vscode.Event<Proto.ProjectLanguageServiceStateEventBody>;
|
||||
readonly onDidBeginInstallTypings: vscode.Event<Proto.BeginInstallTypesEventBody>;
|
||||
readonly onDidEndInstallTypings: vscode.Event<Proto.EndInstallTypesEventBody>;
|
||||
readonly onTypesInstallerInitializationFailed: vscode.Event<Proto.TypesInstallerInitializationFailedEventBody>;
|
||||
|
||||
readonly apiVersion: API;
|
||||
readonly plugins: TypeScriptServerPlugin[];
|
||||
@@ -45,40 +78,22 @@ export interface ITypeScriptServiceClient {
|
||||
readonly logger: Logger;
|
||||
readonly bufferSyncSupport: BufferSyncSupport;
|
||||
|
||||
execute(command: 'configure', args: Proto.ConfigureRequestArguments, token?: CancellationToken): Promise<Proto.ConfigureResponse>;
|
||||
execute(command: 'open', args: Proto.OpenRequestArgs, expectedResult: boolean, token?: CancellationToken): Promise<any>;
|
||||
execute(command: 'close', args: Proto.FileRequestArgs, expectedResult: boolean, token?: CancellationToken): Promise<any>;
|
||||
execute(command: 'change', args: Proto.ChangeRequestArgs, expectedResult: boolean, token?: CancellationToken): Promise<any>;
|
||||
execute(command: 'quickinfo', args: Proto.FileLocationRequestArgs, token?: CancellationToken): Promise<Proto.QuickInfoResponse>;
|
||||
execute(command: 'completions', args: Proto.CompletionsRequestArgs, token?: CancellationToken): Promise<Proto.CompletionsResponse>;
|
||||
execute(command: 'completionEntryDetails', args: Proto.CompletionDetailsRequestArgs, token?: CancellationToken): Promise<Proto.CompletionDetailsResponse>;
|
||||
execute(command: 'signatureHelp', args: Proto.SignatureHelpRequestArgs, token?: CancellationToken): Promise<Proto.SignatureHelpResponse>;
|
||||
execute(command: 'definition', args: Proto.FileLocationRequestArgs, token?: CancellationToken): Promise<Proto.DefinitionResponse>;
|
||||
execute(command: 'implementation', args: Proto.FileLocationRequestArgs, token?: CancellationToken): Promise<Proto.ImplementationResponse>;
|
||||
execute(command: 'typeDefinition', args: Proto.FileLocationRequestArgs, token?: CancellationToken): Promise<Proto.TypeDefinitionResponse>;
|
||||
execute(command: 'references', args: Proto.FileLocationRequestArgs, token?: CancellationToken): Promise<Proto.ReferencesResponse>;
|
||||
execute(command: 'navto', args: Proto.NavtoRequestArgs, token?: CancellationToken): Promise<Proto.NavtoResponse>;
|
||||
execute(command: 'navbar', args: Proto.FileRequestArgs, token?: CancellationToken): Promise<Proto.NavBarResponse>;
|
||||
execute(command: 'format', args: Proto.FormatRequestArgs, token?: CancellationToken): Promise<Proto.FormatResponse>;
|
||||
execute(command: 'formatonkey', args: Proto.FormatOnKeyRequestArgs, token?: CancellationToken): Promise<Proto.FormatResponse>;
|
||||
execute(command: 'rename', args: Proto.RenameRequestArgs, token?: CancellationToken): Promise<Proto.RenameResponse>;
|
||||
execute(command: 'occurrences', args: Proto.FileLocationRequestArgs, token?: CancellationToken): Promise<Proto.OccurrencesResponse>;
|
||||
execute(command: 'projectInfo', args: Proto.ProjectInfoRequestArgs, token?: CancellationToken): Promise<Proto.ProjectInfoResponse>;
|
||||
execute(command: 'reloadProjects', args: any, expectedResult: boolean, token?: CancellationToken): Promise<any>;
|
||||
execute(command: 'reload', args: Proto.ReloadRequestArgs, expectedResult: boolean, token?: CancellationToken): Promise<any>;
|
||||
execute(command: 'compilerOptionsForInferredProjects', args: Proto.SetCompilerOptionsForInferredProjectsArgs, token?: CancellationToken): Promise<any>;
|
||||
execute(command: 'navtree', args: Proto.FileRequestArgs, token?: CancellationToken): Promise<Proto.NavTreeResponse>;
|
||||
execute(command: 'getCodeFixes', args: Proto.CodeFixRequestArgs, token?: CancellationToken): Promise<Proto.GetCodeFixesResponse>;
|
||||
execute(command: 'getSupportedCodeFixes', args: null, token?: CancellationToken): Promise<Proto.GetSupportedCodeFixesResponse>;
|
||||
execute(command: 'getCombinedCodeFix', args: Proto.GetCombinedCodeFixRequestArgs, token?: CancellationToken): Promise<Proto.GetCombinedCodeFixResponse>;
|
||||
execute(command: 'docCommentTemplate', args: Proto.FileLocationRequestArgs, token?: CancellationToken): Promise<Proto.DocCommandTemplateResponse>;
|
||||
execute(command: 'getApplicableRefactors', args: Proto.GetApplicableRefactorsRequestArgs, token?: CancellationToken): Promise<Proto.GetApplicableRefactorsResponse>;
|
||||
execute(command: 'getEditsForRefactor', args: Proto.GetEditsForRefactorRequestArgs, token?: CancellationToken): Promise<Proto.GetEditsForRefactorResponse>;
|
||||
execute(command: 'applyCodeActionCommand', args: Proto.ApplyCodeActionCommandRequestArgs, token?: CancellationToken): Promise<Proto.ApplyCodeActionCommandResponse>;
|
||||
execute(command: 'organizeImports', args: Proto.OrganizeImportsRequestArgs, token?: CancellationToken): Promise<Proto.OrganizeImportsResponse>;
|
||||
execute(command: 'getOutliningSpans', args: Proto.FileRequestArgs, token: CancellationToken): Promise<Proto.OutliningSpansResponse>;
|
||||
execute(command: 'getEditsForFileRename', args: Proto.GetEditsForFileRenameRequestArgs): Promise<Proto.GetEditsForFileRenameResponse>;
|
||||
execute(command: string, args: any, expectedResult: boolean | CancellationToken, token?: CancellationToken): Promise<any>;
|
||||
execute<K extends keyof TypeScriptRequestTypes>(
|
||||
command: K,
|
||||
args: TypeScriptRequestTypes[K][0],
|
||||
token: vscode.CancellationToken
|
||||
): Promise<TypeScriptRequestTypes[K][1]>;
|
||||
|
||||
executeAsync(command: 'geterr', args: Proto.GeterrRequestArgs, token: CancellationToken): Promise<any>;
|
||||
executeWithoutWaitingForResponse(command: 'open', args: Proto.OpenRequestArgs): void;
|
||||
executeWithoutWaitingForResponse(command: 'close', args: Proto.FileRequestArgs): void;
|
||||
executeWithoutWaitingForResponse(command: 'change', args: Proto.ChangeRequestArgs): void;
|
||||
executeWithoutWaitingForResponse(command: 'compilerOptionsForInferredProjects', args: Proto.SetCompilerOptionsForInferredProjectsArgs): void;
|
||||
executeWithoutWaitingForResponse(command: 'reloadProjects', args: null): void;
|
||||
|
||||
executeAsync(command: 'geterr', args: Proto.GeterrRequestArgs, token: vscode.CancellationToken): Promise<any>;
|
||||
|
||||
/**
|
||||
* Cancel on going geterr requests and re-queue them after `f` has been evaluated.
|
||||
*/
|
||||
interuptGetErr<R>(f: () => R): R;
|
||||
}
|
||||
@@ -4,34 +4,31 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as cp from 'child_process';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
import * as electron from './utils/electron';
|
||||
import { Reader, ICallback } from './utils/wireProtocol';
|
||||
|
||||
import { workspace, window, Uri, CancellationToken, Disposable, Memento, MessageItem, EventEmitter, commands, env } from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import BufferSyncSupport from './features/bufferSyncSupport';
|
||||
import { DiagnosticKind, DiagnosticsManager } from './features/diagnostics';
|
||||
import * as Proto from './protocol';
|
||||
import { ITypeScriptServiceClient } from './typescriptService';
|
||||
import { TypeScriptServerPlugin } from './utils/plugins';
|
||||
import Logger from './utils/logger';
|
||||
|
||||
import API from './utils/api';
|
||||
import { TsServerLogLevel, TypeScriptServiceConfiguration } from './utils/configuration';
|
||||
import { Disposable } from './utils/dispose';
|
||||
import * as electron from './utils/electron';
|
||||
import * as fileSchemes from './utils/fileSchemes';
|
||||
import * as is from './utils/is';
|
||||
import LogDirectoryProvider from './utils/logDirectoryProvider';
|
||||
import Logger from './utils/logger';
|
||||
import { TypeScriptPluginPathsProvider } from './utils/pluginPathsProvider';
|
||||
import { TypeScriptServerPlugin } from './utils/plugins';
|
||||
import TelemetryReporter from './utils/telemetry';
|
||||
import Tracer from './utils/tracer';
|
||||
import API from './utils/api';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
import { TypeScriptServiceConfiguration, TsServerLogLevel } from './utils/configuration';
|
||||
import { TypeScriptVersionProvider, TypeScriptVersion } from './utils/versionProvider';
|
||||
import { TypeScriptVersionPicker } from './utils/versionPicker';
|
||||
import * as fileSchemes from './utils/fileSchemes';
|
||||
import { inferredProjectConfig } from './utils/tsconfig';
|
||||
import LogDirectoryProvider from './utils/logDirectoryProvider';
|
||||
import { disposeAll } from './utils/dispose';
|
||||
import { DiagnosticKind } from './features/diagnostics';
|
||||
import { TypeScriptPluginPathsProvider } from './utils/pluginPathsProvider';
|
||||
import BufferSyncSupport from './features/bufferSyncSupport';
|
||||
import { TypeScriptVersionPicker } from './utils/versionPicker';
|
||||
import { TypeScriptVersion, TypeScriptVersionProvider } from './utils/versionProvider';
|
||||
import { Reader } from './utils/wireProtocol';
|
||||
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -123,10 +120,15 @@ class RequestQueue {
|
||||
}
|
||||
}
|
||||
|
||||
class ForkedTsServerProcess {
|
||||
class ForkedTsServerProcess extends Disposable {
|
||||
private readonly _reader: Reader<Proto.Response>;
|
||||
|
||||
constructor(
|
||||
private childProcess: cp.ChildProcess
|
||||
) { }
|
||||
) {
|
||||
super();
|
||||
this._reader = new Reader<Proto.Response>(this.childProcess.stdout);
|
||||
}
|
||||
|
||||
public onError(cb: (err: Error) => void): void {
|
||||
this.childProcess.on('error', cb);
|
||||
@@ -140,13 +142,9 @@ class ForkedTsServerProcess {
|
||||
this.childProcess.stdin.write(JSON.stringify(serverRequest) + '\r\n', 'utf8');
|
||||
}
|
||||
|
||||
public createReader(
|
||||
callback: ICallback<Proto.Response>,
|
||||
onError: (error: any) => void
|
||||
) {
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
new Reader<Proto.Response>(this.childProcess.stdout, callback, onError);
|
||||
}
|
||||
public get onReaderError() { return this._reader.onError; }
|
||||
|
||||
public get onData() { return this._reader.onData; }
|
||||
|
||||
public kill() {
|
||||
this.childProcess.kill();
|
||||
@@ -155,11 +153,11 @@ class ForkedTsServerProcess {
|
||||
|
||||
export interface TsDiagnostics {
|
||||
readonly kind: DiagnosticKind;
|
||||
readonly resource: Uri;
|
||||
readonly resource: vscode.Uri;
|
||||
readonly diagnostics: Proto.Diagnostic[];
|
||||
}
|
||||
|
||||
export default class TypeScriptServiceClient implements ITypeScriptServiceClient {
|
||||
export default class TypeScriptServiceClient extends Disposable implements ITypeScriptServiceClient {
|
||||
private static readonly WALK_THROUGH_SNIPPET_SCHEME_COLON = `${fileSchemes.walkThroughSnippet}:`;
|
||||
|
||||
private pathSeparator: string;
|
||||
@@ -173,7 +171,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
private tracer: Tracer;
|
||||
public readonly logger: Logger = new Logger();
|
||||
private tsServerLogFile: string | null = null;
|
||||
private servicePromise: Thenable<ForkedTsServerProcess> | null;
|
||||
private servicePromise: Thenable<ForkedTsServerProcess | null> | null;
|
||||
private lastError: Error | null;
|
||||
private lastStart: number;
|
||||
private numberRestarts: number;
|
||||
@@ -194,17 +192,17 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
*/
|
||||
private _tsserverVersion: string | undefined;
|
||||
|
||||
private readonly disposables: Disposable[] = [];
|
||||
|
||||
public readonly bufferSyncSupport: BufferSyncSupport;
|
||||
public readonly diagnosticsManager: DiagnosticsManager;
|
||||
|
||||
constructor(
|
||||
private readonly workspaceState: Memento,
|
||||
private readonly workspaceState: vscode.Memento,
|
||||
private readonly onDidChangeTypeScriptVersion: (version: TypeScriptVersion) => void,
|
||||
public readonly plugins: TypeScriptServerPlugin[],
|
||||
private readonly logDirectoryProvider: LogDirectoryProvider,
|
||||
allModeIds: string[]
|
||||
) {
|
||||
super();
|
||||
this.pathSeparator = path.sep;
|
||||
this.lastStart = Date.now();
|
||||
|
||||
@@ -231,7 +229,12 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
this.bufferSyncSupport = new BufferSyncSupport(this, allModeIds);
|
||||
this.onReady(() => { this.bufferSyncSupport.listen(); });
|
||||
|
||||
workspace.onDidChangeConfiguration(() => {
|
||||
this.diagnosticsManager = new DiagnosticsManager('typescript');
|
||||
this.bufferSyncSupport.onDelete(resource => {
|
||||
this.diagnosticsManager.delete(resource);
|
||||
}, null, this._disposables);
|
||||
|
||||
vscode.workspace.onDidChangeConfiguration(() => {
|
||||
const oldConfiguration = this._configuration;
|
||||
this._configuration = TypeScriptServiceConfiguration.loadFromWorkspace();
|
||||
|
||||
@@ -250,9 +253,9 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
this.restartTsServer();
|
||||
}
|
||||
}
|
||||
}, this, this.disposables);
|
||||
}, this, this._disposables);
|
||||
this.telemetryReporter = new TelemetryReporter(() => this._tsserverVersion || this._apiVersion.versionString);
|
||||
this.disposables.push(this.telemetryReporter);
|
||||
this._register(this.telemetryReporter);
|
||||
}
|
||||
|
||||
public get configuration() {
|
||||
@@ -260,22 +263,17 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
super.dispose();
|
||||
|
||||
this.bufferSyncSupport.dispose();
|
||||
this._onTsServerStarted.dispose();
|
||||
this._onDidBeginInstallTypings.dispose();
|
||||
this._onDidEndInstallTypings.dispose();
|
||||
this._onTypesInstallerInitializationFailed.dispose();
|
||||
|
||||
if (this.servicePromise) {
|
||||
this.servicePromise.then(childProcess => {
|
||||
childProcess.kill();
|
||||
if (childProcess) {
|
||||
childProcess.kill();
|
||||
}
|
||||
}).then(undefined, () => void 0);
|
||||
}
|
||||
|
||||
disposeAll(this.disposables);
|
||||
this._onDiagnosticsReceived.dispose();
|
||||
this._onConfigDiagnosticsReceived.dispose();
|
||||
this._onResendModelsRequested.dispose();
|
||||
}
|
||||
|
||||
public restartTsServer(): void {
|
||||
@@ -288,7 +286,9 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
this.servicePromise = this.servicePromise.then(childProcess => {
|
||||
this.info('Killing TS Server');
|
||||
this.isRestarting = true;
|
||||
childProcess.kill();
|
||||
if (childProcess) {
|
||||
childProcess.kill();
|
||||
}
|
||||
this.resetClientVersion();
|
||||
}).then(start);
|
||||
} else {
|
||||
@@ -296,28 +296,28 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
}
|
||||
}
|
||||
|
||||
private readonly _onTsServerStarted = new EventEmitter<API>();
|
||||
private readonly _onTsServerStarted = this._register(new vscode.EventEmitter<API>());
|
||||
public readonly onTsServerStarted = this._onTsServerStarted.event;
|
||||
|
||||
private readonly _onDiagnosticsReceived = new EventEmitter<TsDiagnostics>();
|
||||
private readonly _onDiagnosticsReceived = this._register(new vscode.EventEmitter<TsDiagnostics>());
|
||||
public readonly onDiagnosticsReceived = this._onDiagnosticsReceived.event;
|
||||
|
||||
private readonly _onConfigDiagnosticsReceived = new EventEmitter<Proto.ConfigFileDiagnosticEvent>();
|
||||
private readonly _onConfigDiagnosticsReceived = this._register(new vscode.EventEmitter<Proto.ConfigFileDiagnosticEvent>());
|
||||
public readonly onConfigDiagnosticsReceived = this._onConfigDiagnosticsReceived.event;
|
||||
|
||||
private readonly _onResendModelsRequested = new EventEmitter<void>();
|
||||
private readonly _onResendModelsRequested = this._register(new vscode.EventEmitter<void>());
|
||||
public readonly onResendModelsRequested = this._onResendModelsRequested.event;
|
||||
|
||||
private readonly _onProjectLanguageServiceStateChanged = new EventEmitter<Proto.ProjectLanguageServiceStateEventBody>();
|
||||
private readonly _onProjectLanguageServiceStateChanged = this._register(new vscode.EventEmitter<Proto.ProjectLanguageServiceStateEventBody>());
|
||||
public readonly onProjectLanguageServiceStateChanged = this._onProjectLanguageServiceStateChanged.event;
|
||||
|
||||
private readonly _onDidBeginInstallTypings = new EventEmitter<Proto.BeginInstallTypesEventBody>();
|
||||
private readonly _onDidBeginInstallTypings = this._register(new vscode.EventEmitter<Proto.BeginInstallTypesEventBody>());
|
||||
public readonly onDidBeginInstallTypings = this._onDidBeginInstallTypings.event;
|
||||
|
||||
private readonly _onDidEndInstallTypings = new EventEmitter<Proto.EndInstallTypesEventBody>();
|
||||
private readonly _onDidEndInstallTypings = this._register(new vscode.EventEmitter<Proto.EndInstallTypesEventBody>());
|
||||
public readonly onDidEndInstallTypings = this._onDidEndInstallTypings.event;
|
||||
|
||||
private readonly _onTypesInstallerInitializationFailed = new EventEmitter<Proto.TypesInstallerInitializationFailedEventBody>();
|
||||
private readonly _onTypesInstallerInitializationFailed = this._register(new vscode.EventEmitter<Proto.TypesInstallerInitializationFailedEventBody>());
|
||||
public readonly onTypesInstallerInitializationFailed = this._onTypesInstallerInitializationFailed.event;
|
||||
|
||||
public get apiVersion(): API {
|
||||
@@ -340,7 +340,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
this.telemetryReporter.logTelemetry(eventName, properties);
|
||||
}
|
||||
|
||||
private service(): Thenable<ForkedTsServerProcess> {
|
||||
private service(): Thenable<ForkedTsServerProcess | null> {
|
||||
if (this.servicePromise) {
|
||||
return this.servicePromise;
|
||||
}
|
||||
@@ -360,12 +360,17 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
}
|
||||
}
|
||||
|
||||
private startService(resendModels: boolean = false): Promise<ForkedTsServerProcess> {
|
||||
private token: number = 0;
|
||||
private startService(resendModels: boolean = false): Promise<ForkedTsServerProcess> | null {
|
||||
if (this.isDisposed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let currentVersion = this.versionPicker.currentVersion;
|
||||
|
||||
this.info(`Using tsserver from: ${currentVersion.path}`);
|
||||
if (!fs.existsSync(currentVersion.tsServerPath)) {
|
||||
window.showWarningMessage(localize('noServerFound', 'The path {0} doesn\'t point to a valid tsserver install. Falling back to bundled TypeScript version.', currentVersion.path));
|
||||
vscode.window.showWarningMessage(localize('noServerFound', 'The path {0} doesn\'t point to a valid tsserver install. Falling back to bundled TypeScript version.', currentVersion.path));
|
||||
|
||||
this.versionPicker.useBundledVersion();
|
||||
currentVersion = this.versionPicker.currentVersion;
|
||||
@@ -377,6 +382,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
this.requestQueue = new RequestQueue();
|
||||
this.callbacks = new CallbackMap();
|
||||
this.lastError = null;
|
||||
let mytoken = ++this.token;
|
||||
|
||||
return this.servicePromise = new Promise<ForkedTsServerProcess>(async (resolve, reject) => {
|
||||
try {
|
||||
@@ -385,76 +391,82 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
const tsServerForkOptions: electron.IForkOptions = {
|
||||
execArgv: debugPort ? [`--inspect=${debugPort}`] : [] // [`--debug-brk=5859`]
|
||||
};
|
||||
electron.fork(currentVersion.tsServerPath, tsServerForkArgs, tsServerForkOptions, this.logger, (err: any, childProcess: cp.ChildProcess | null) => {
|
||||
if (err || !childProcess) {
|
||||
this.lastError = err;
|
||||
this.error('Starting TSServer failed with error.', err);
|
||||
window.showErrorMessage(localize('serverCouldNotBeStarted', 'TypeScript language server couldn\'t be started. Error message is: {0}', err.message || err));
|
||||
/* __GDPR__
|
||||
"error" : {
|
||||
"${include}": [
|
||||
"${TypeScriptCommonProperties}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.logTelemetry('error');
|
||||
this.resetClientVersion();
|
||||
const childProcess = electron.fork(currentVersion.tsServerPath, tsServerForkArgs, tsServerForkOptions, this.logger);
|
||||
childProcess.once('error', (err: Error) => {
|
||||
this.lastError = err;
|
||||
this.error('Starting TSServer failed with error.', err);
|
||||
vscode.window.showErrorMessage(localize('serverCouldNotBeStarted', 'TypeScript language server couldn\'t be started. Error message is: {0}', err.message || err.name));
|
||||
/* __GDPR__
|
||||
"error" : {
|
||||
"${include}": [
|
||||
"${TypeScriptCommonProperties}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.logTelemetry('error');
|
||||
this.resetClientVersion();
|
||||
return;
|
||||
});
|
||||
|
||||
this.info('Started TSServer');
|
||||
const handle = new ForkedTsServerProcess(childProcess);
|
||||
this.lastStart = Date.now();
|
||||
|
||||
handle.onError((err: Error) => {
|
||||
if (this.token !== mytoken) {
|
||||
// this is coming from an old process
|
||||
return;
|
||||
}
|
||||
|
||||
this.info('Started TSServer');
|
||||
const handle = new ForkedTsServerProcess(childProcess);
|
||||
this.lastStart = Date.now();
|
||||
|
||||
handle.onError((err: Error) => {
|
||||
this.lastError = err;
|
||||
this.error('TSServer errored with error.', err);
|
||||
if (this.tsServerLogFile) {
|
||||
this.error(`TSServer log file: ${this.tsServerLogFile}`);
|
||||
this.lastError = err;
|
||||
this.error('TSServer errored with error.', err);
|
||||
if (this.tsServerLogFile) {
|
||||
this.error(`TSServer log file: ${this.tsServerLogFile}`);
|
||||
}
|
||||
/* __GDPR__
|
||||
"tsserver.error" : {
|
||||
"${include}": [
|
||||
"${TypeScriptCommonProperties}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.logTelemetry('tsserver.error');
|
||||
this.serviceExited(false);
|
||||
});
|
||||
handle.onExit((code: any) => {
|
||||
if (this.token !== mytoken) {
|
||||
// this is coming from an old process
|
||||
return;
|
||||
}
|
||||
if (code === null || typeof code === 'undefined') {
|
||||
this.info('TSServer exited');
|
||||
} else {
|
||||
this.error(`TSServer exited with code: ${code}`);
|
||||
/* __GDPR__
|
||||
"tsserver.error" : {
|
||||
"tsserver.exitWithCode" : {
|
||||
"code" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
|
||||
"${include}": [
|
||||
"${TypeScriptCommonProperties}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.logTelemetry('tsserver.error');
|
||||
this.serviceExited(false);
|
||||
});
|
||||
handle.onExit((code: any) => {
|
||||
if (code === null || typeof code === 'undefined') {
|
||||
this.info('TSServer exited');
|
||||
} else {
|
||||
this.error(`TSServer exited with code: ${code}`);
|
||||
/* __GDPR__
|
||||
"tsserver.exitWithCode" : {
|
||||
"code" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
|
||||
"${include}": [
|
||||
"${TypeScriptCommonProperties}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.logTelemetry('tsserver.exitWithCode', { code: code });
|
||||
}
|
||||
this.logTelemetry('tsserver.exitWithCode', { code: code });
|
||||
}
|
||||
|
||||
if (this.tsServerLogFile) {
|
||||
this.info(`TSServer log file: ${this.tsServerLogFile}`);
|
||||
}
|
||||
this.serviceExited(!this.isRestarting);
|
||||
this.isRestarting = false;
|
||||
});
|
||||
|
||||
handle.createReader(
|
||||
msg => { this.dispatchMessage(msg); },
|
||||
error => { this.error('ReaderError', error); });
|
||||
|
||||
this._onReady!.resolve();
|
||||
resolve(handle);
|
||||
this._onTsServerStarted.fire(currentVersion.version);
|
||||
|
||||
this.serviceStarted(resendModels);
|
||||
if (this.tsServerLogFile) {
|
||||
this.info(`TSServer log file: ${this.tsServerLogFile}`);
|
||||
}
|
||||
this.serviceExited(!this.isRestarting);
|
||||
this.isRestarting = false;
|
||||
});
|
||||
|
||||
handle.onData(msg => this.dispatchMessage(msg));
|
||||
handle.onReaderError(error => this.error('ReaderError', error));
|
||||
|
||||
this._onReady!.resolve();
|
||||
resolve(handle);
|
||||
this._onTsServerStarted.fire(currentVersion.version);
|
||||
|
||||
this.serviceStarted(resendModels);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
@@ -476,7 +488,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
|
||||
public async openTsServerLogFile(): Promise<boolean> {
|
||||
if (!this.apiVersion.gte(API.v222)) {
|
||||
window.showErrorMessage(
|
||||
vscode.window.showErrorMessage(
|
||||
localize(
|
||||
'typescript.openTsServerLog.notSupported',
|
||||
'TS Server logging requires TS 2.2.2+'));
|
||||
@@ -484,7 +496,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
}
|
||||
|
||||
if (this._configuration.tsServerLogLevel === TsServerLogLevel.Off) {
|
||||
window.showErrorMessage<MessageItem>(
|
||||
vscode.window.showErrorMessage<vscode.MessageItem>(
|
||||
localize(
|
||||
'typescript.openTsServerLog.loggingNotEnabled',
|
||||
'TS Server logging is off. Please set `typescript.tsserver.log` and restart the TS server to enable logging'),
|
||||
@@ -495,7 +507,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
})
|
||||
.then(selection => {
|
||||
if (selection) {
|
||||
return workspace.getConfiguration().update('typescript.tsserver.log', 'verbose', true).then(() => {
|
||||
return vscode.workspace.getConfiguration().update('typescript.tsserver.log', 'verbose', true).then(() => {
|
||||
this.restartTsServer();
|
||||
});
|
||||
}
|
||||
@@ -505,17 +517,17 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
}
|
||||
|
||||
if (!this.tsServerLogFile) {
|
||||
window.showWarningMessage(localize(
|
||||
vscode.window.showWarningMessage(localize(
|
||||
'typescript.openTsServerLog.noLogFile',
|
||||
'TS Server has not started logging.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await commands.executeCommand('revealFileInOS', Uri.parse(this.tsServerLogFile));
|
||||
await vscode.commands.executeCommand('revealFileInOS', vscode.Uri.parse(this.tsServerLogFile));
|
||||
return true;
|
||||
} catch {
|
||||
window.showWarningMessage(localize(
|
||||
vscode.window.showWarningMessage(localize(
|
||||
'openTsServerLog.openFileFailedFailed',
|
||||
'Could not open TS Server log file'));
|
||||
return false;
|
||||
@@ -526,7 +538,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
const configureOptions: Proto.ConfigureRequestArguments = {
|
||||
hostInfo: 'vscode'
|
||||
};
|
||||
this.execute('configure', configureOptions);
|
||||
this.executeWithoutWaitingForResponse('configure', configureOptions);
|
||||
this.setCompilerOptionsForInferredProjects(this._configuration);
|
||||
if (resendModels) {
|
||||
this._onResendModelsRequested.fire();
|
||||
@@ -541,7 +553,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
const args: Proto.SetCompilerOptionsForInferredProjectsArgs = {
|
||||
options: this.getCompilerOptionsForInferredProjects(configuration)
|
||||
};
|
||||
this.execute('compilerOptionsForInferredProjects', args, true);
|
||||
this.executeWithoutWaitingForResponse('compilerOptionsForInferredProjects', args);
|
||||
}
|
||||
|
||||
private getCompilerOptionsForInferredProjects(configuration: TypeScriptServiceConfiguration): Proto.ExternalProjectCompilerOptions {
|
||||
@@ -558,7 +570,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
reportIssue
|
||||
}
|
||||
|
||||
interface MyMessageItem extends MessageItem {
|
||||
interface MyMessageItem extends vscode.MessageItem {
|
||||
id: MessageAction;
|
||||
}
|
||||
|
||||
@@ -579,7 +591,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
if (diff < 10 * 1000 /* 10 seconds */) {
|
||||
this.lastStart = Date.now();
|
||||
startService = false;
|
||||
prompt = window.showErrorMessage<MyMessageItem>(
|
||||
prompt = vscode.window.showErrorMessage<MyMessageItem>(
|
||||
localize('serverDiedAfterStart', 'The TypeScript language service died 5 times right after it got started. The service will not be restarted.'),
|
||||
{
|
||||
title: localize('serverDiedReportIssue', 'Report Issue'),
|
||||
@@ -596,7 +608,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
this.resetClientVersion();
|
||||
} else if (diff < 60 * 1000 /* 1 Minutes */) {
|
||||
this.lastStart = Date.now();
|
||||
prompt = window.showWarningMessage<MyMessageItem>(
|
||||
prompt = vscode.window.showWarningMessage<MyMessageItem>(
|
||||
localize('serverDied', 'The TypeScript language service died unexpectedly 5 times in the last 5 Minutes.'),
|
||||
{
|
||||
title: localize('serverDiedReportIssue', 'Report Issue'),
|
||||
@@ -606,7 +618,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
if (prompt) {
|
||||
prompt.then(item => {
|
||||
if (item && item.id === MessageAction.reportIssue) {
|
||||
return commands.executeCommand('workbench.action.reportIssues');
|
||||
return vscode.commands.executeCommand('workbench.action.reportIssues');
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
@@ -618,7 +630,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
}
|
||||
}
|
||||
|
||||
public normalizedPath(resource: Uri): string | null {
|
||||
public normalizedPath(resource: vscode.Uri): string | null {
|
||||
if (this._apiVersion.gte(API.v213)) {
|
||||
if (resource.scheme === fileSchemes.walkThroughSnippet || resource.scheme === fileSchemes.untitled) {
|
||||
const dirName = path.dirname(resource.path);
|
||||
@@ -640,7 +652,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
return result.replace(new RegExp('\\' + this.pathSeparator, 'g'), '/');
|
||||
}
|
||||
|
||||
public toPath(resource: Uri): string | null {
|
||||
public toPath(resource: vscode.Uri): string | null {
|
||||
return this.normalizedPath(resource);
|
||||
}
|
||||
|
||||
@@ -648,11 +660,11 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
return this._apiVersion.gte(API.v270) ? '^' : '';
|
||||
}
|
||||
|
||||
public toResource(filepath: string): Uri {
|
||||
public toResource(filepath: string): vscode.Uri {
|
||||
if (this._apiVersion.gte(API.v213)) {
|
||||
if (filepath.startsWith(TypeScriptServiceClient.WALK_THROUGH_SNIPPET_SCHEME_COLON) || (filepath.startsWith(fileSchemes.untitled + ':'))
|
||||
) {
|
||||
let resource = Uri.parse(filepath);
|
||||
let resource = vscode.Uri.parse(filepath);
|
||||
if (this.inMemoryResourcePrefix) {
|
||||
const dirName = path.dirname(resource.path);
|
||||
const fileName = path.basename(resource.path);
|
||||
@@ -666,8 +678,8 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
return this.bufferSyncSupport.toResource(filepath);
|
||||
}
|
||||
|
||||
public getWorkspaceRootForResource(resource: Uri): string | undefined {
|
||||
const roots = workspace.workspaceFolders;
|
||||
public getWorkspaceRootForResource(resource: vscode.Uri): string | undefined {
|
||||
const roots = vscode.workspace.workspaceFolders;
|
||||
if (!roots || !roots.length) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -684,22 +696,31 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public executeAsync(command: string, args: Proto.GeterrRequestArgs, token: CancellationToken): Promise<any> {
|
||||
return this.executeImpl(command, args, { isAsync: true, token, expectsResult: true });
|
||||
public execute(command: string, args: any, token: vscode.CancellationToken): Promise<any> {
|
||||
return this.executeImpl(command, args, {
|
||||
isAsync: false,
|
||||
token,
|
||||
expectsResult: true
|
||||
});
|
||||
}
|
||||
|
||||
public execute(command: string, args: any, expectsResultOrToken?: boolean | CancellationToken): Promise<any> {
|
||||
let token: CancellationToken | undefined = undefined;
|
||||
let expectsResult = true;
|
||||
if (typeof expectsResultOrToken === 'boolean') {
|
||||
expectsResult = expectsResultOrToken;
|
||||
} else {
|
||||
token = expectsResultOrToken;
|
||||
}
|
||||
return this.executeImpl(command, args, { isAsync: false, token, expectsResult });
|
||||
public executeWithoutWaitingForResponse(command: string, args: any): void {
|
||||
this.executeImpl(command, args, {
|
||||
isAsync: false,
|
||||
token: undefined,
|
||||
expectsResult: false
|
||||
});
|
||||
}
|
||||
|
||||
private executeImpl(command: string, args: any, executeInfo: { isAsync: boolean, token?: CancellationToken, expectsResult: boolean }): Promise<any> {
|
||||
public executeAsync(command: string, args: Proto.GeterrRequestArgs, token: vscode.CancellationToken): Promise<any> {
|
||||
return this.executeImpl(command, args, {
|
||||
isAsync: true,
|
||||
token,
|
||||
expectsResult: true
|
||||
});
|
||||
}
|
||||
|
||||
private executeImpl(command: string, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean }): Promise<any> {
|
||||
const request = this.requestQueue.createRequest(command, args);
|
||||
const requestInfo: RequestItem = {
|
||||
request: request,
|
||||
@@ -721,6 +742,17 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
if (!wasCancelled) {
|
||||
this.error(`'${command}' request failed with error.`, err);
|
||||
const properties = this.parseErrorText(err && err.message, command);
|
||||
/* __GDPR__
|
||||
"languageServiceErrorResponse" : {
|
||||
"command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"message" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
|
||||
"stack" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
|
||||
"errortext" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
|
||||
"${include}": [
|
||||
"${TypeScriptCommonProperties}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.logTelemetry('languageServiceErrorResponse', properties);
|
||||
}
|
||||
throw err;
|
||||
@@ -734,6 +766,10 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
return result;
|
||||
}
|
||||
|
||||
public interuptGetErr<R>(f: () => R): R {
|
||||
return this.bufferSyncSupport.interuptGetErr(f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a `errorText` from a tsserver request indicating failure in handling a request,
|
||||
* prepares a payload for telemetry-logging.
|
||||
@@ -774,8 +810,10 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
this.callbacks.add(serverRequest.seq, requestItem.callbacks, requestItem.isAsync);
|
||||
}
|
||||
this.service()
|
||||
.then((childProcess) => {
|
||||
childProcess.write(serverRequest);
|
||||
.then(childProcess => {
|
||||
if (childProcess) {
|
||||
childProcess.write(serverRequest);
|
||||
}
|
||||
})
|
||||
.then(undefined, err => {
|
||||
const callback = this.callbacks.fetch(serverRequest.seq);
|
||||
@@ -879,7 +917,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
case 'projectsUpdatedInBackground':
|
||||
if (event.body) {
|
||||
const body = (event as Proto.ProjectsUpdatedInBackgroundEvent).body;
|
||||
const resources = body.openFiles.map(Uri.file);
|
||||
const resources = body.openFiles.map(vscode.Uri.file);
|
||||
this.bufferSyncSupport.getErr(resources);
|
||||
}
|
||||
break;
|
||||
@@ -974,7 +1012,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
}
|
||||
|
||||
if (this.apiVersion.gte(API.v222)) {
|
||||
this.cancellationPipeName = electron.getTempSock('tscancellation');
|
||||
this.cancellationPipeName = electron.getTempFile('tscancellation');
|
||||
args.push('--cancellationPipeName', this.cancellationPipeName + '*');
|
||||
}
|
||||
|
||||
@@ -1053,7 +1091,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
||||
const getTsLocale = (configuration: TypeScriptServiceConfiguration): string | undefined =>
|
||||
(configuration.locale
|
||||
? configuration.locale
|
||||
: env.language);
|
||||
: vscode.env.language);
|
||||
|
||||
function getDignosticsKind(event: Proto.Event) {
|
||||
switch (event.event) {
|
||||
|
||||
@@ -24,11 +24,12 @@ export default class API {
|
||||
public static readonly v240 = API.fromSimpleString('2.4.0');
|
||||
public static readonly v250 = API.fromSimpleString('2.5.0');
|
||||
public static readonly v260 = API.fromSimpleString('2.6.0');
|
||||
public static readonly v262 = API.fromSimpleString('2.6.2');
|
||||
public static readonly v270 = API.fromSimpleString('2.7.0');
|
||||
public static readonly v280 = API.fromSimpleString('2.8.0');
|
||||
public static readonly v290 = API.fromSimpleString('2.9.0');
|
||||
public static readonly v291 = API.fromSimpleString('2.9.1');
|
||||
public static readonly v292 = API.fromSimpleString('2.9.2');
|
||||
public static readonly v300 = API.fromSimpleString('3.0.0');
|
||||
|
||||
|
||||
public static fromVersionString(versionString: string): API {
|
||||
|
||||
@@ -14,4 +14,8 @@ export function equals<T>(one: T[], other: T[], itemEquals: (a: T, b: T) => bool
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function flatten<T>(arr: T[][]): T[] {
|
||||
return [].concat.apply([], arr);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
const nulTokenSource = new vscode.CancellationTokenSource();
|
||||
|
||||
export const nulToken = nulTokenSource.token;
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { WorkspaceEdit, workspace } from 'vscode';
|
||||
import * as vscode from 'vscode';
|
||||
import * as Proto from '../protocol';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import * as typeConverters from './typeConverters';
|
||||
@@ -11,35 +11,34 @@ import * as typeConverters from './typeConverters';
|
||||
export function getEditForCodeAction(
|
||||
client: ITypeScriptServiceClient,
|
||||
action: Proto.CodeAction
|
||||
): WorkspaceEdit | undefined {
|
||||
): vscode.WorkspaceEdit | undefined {
|
||||
return action.changes && action.changes.length
|
||||
? typeConverters.WorkspaceEdit.fromFromFileCodeEdits(client, action.changes)
|
||||
? typeConverters.WorkspaceEdit.fromFileCodeEdits(client, action.changes)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export async function applyCodeAction(
|
||||
client: ITypeScriptServiceClient,
|
||||
action: Proto.CodeAction
|
||||
action: Proto.CodeAction,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<boolean> {
|
||||
const workspaceEdit = getEditForCodeAction(client, action);
|
||||
if (workspaceEdit) {
|
||||
if (!(await workspace.applyEdit(workspaceEdit))) {
|
||||
if (!(await vscode.workspace.applyEdit(workspaceEdit))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return applyCodeActionCommands(client, action);
|
||||
return applyCodeActionCommands(client, action.commands, token);
|
||||
}
|
||||
|
||||
export async function applyCodeActionCommands(
|
||||
client: ITypeScriptServiceClient,
|
||||
action: Proto.CodeAction
|
||||
commands: ReadonlyArray<{}> | undefined,
|
||||
token: vscode.CancellationToken,
|
||||
): Promise<boolean> {
|
||||
if (action.commands && action.commands.length) {
|
||||
for (const command of action.commands) {
|
||||
const response = await client.execute('applyCodeActionCommand', { command });
|
||||
if (!response || !response.body) {
|
||||
return false;
|
||||
}
|
||||
if (commands && commands.length) {
|
||||
for (const command of commands) {
|
||||
await client.execute('applyCodeActionCommand', { command }, token);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { WorkspaceConfiguration, workspace } from 'vscode';
|
||||
import * as vscode from 'vscode';
|
||||
import * as arrays from './arrays';
|
||||
|
||||
export enum TsServerLogLevel {
|
||||
@@ -58,7 +58,7 @@ export class TypeScriptServiceConfiguration {
|
||||
}
|
||||
|
||||
private constructor() {
|
||||
const configuration = workspace.getConfiguration();
|
||||
const configuration = vscode.workspace.getConfiguration();
|
||||
|
||||
this.locale = TypeScriptServiceConfiguration.extractLocale(configuration);
|
||||
this.globalTsdk = TypeScriptServiceConfiguration.extractGlobalTsdk(configuration);
|
||||
@@ -83,7 +83,7 @@ export class TypeScriptServiceConfiguration {
|
||||
&& arrays.equals(this.tsServerPluginPaths, other.tsServerPluginPaths);
|
||||
}
|
||||
|
||||
private static extractGlobalTsdk(configuration: WorkspaceConfiguration): string | null {
|
||||
private static extractGlobalTsdk(configuration: vscode.WorkspaceConfiguration): string | null {
|
||||
const inspect = configuration.inspect('typescript.tsdk');
|
||||
if (inspect && inspect.globalValue && 'string' === typeof inspect.globalValue) {
|
||||
return inspect.globalValue;
|
||||
@@ -91,7 +91,7 @@ export class TypeScriptServiceConfiguration {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static extractLocalTsdk(configuration: WorkspaceConfiguration): string | null {
|
||||
private static extractLocalTsdk(configuration: vscode.WorkspaceConfiguration): string | null {
|
||||
const inspect = configuration.inspect('typescript.tsdk');
|
||||
if (inspect && inspect.workspaceValue && 'string' === typeof inspect.workspaceValue) {
|
||||
return inspect.workspaceValue;
|
||||
@@ -99,32 +99,32 @@ export class TypeScriptServiceConfiguration {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static readTsServerLogLevel(configuration: WorkspaceConfiguration): TsServerLogLevel {
|
||||
private static readTsServerLogLevel(configuration: vscode.WorkspaceConfiguration): TsServerLogLevel {
|
||||
const setting = configuration.get<string>('typescript.tsserver.log', 'off');
|
||||
return TsServerLogLevel.fromString(setting);
|
||||
}
|
||||
|
||||
private static readTsServerPluginPaths(configuration: WorkspaceConfiguration): string[] {
|
||||
private static readTsServerPluginPaths(configuration: vscode.WorkspaceConfiguration): string[] {
|
||||
return configuration.get<string[]>('typescript.tsserver.pluginPaths', []);
|
||||
}
|
||||
|
||||
private static readCheckJs(configuration: WorkspaceConfiguration): boolean {
|
||||
private static readCheckJs(configuration: vscode.WorkspaceConfiguration): boolean {
|
||||
return configuration.get<boolean>('javascript.implicitProjectConfig.checkJs', false);
|
||||
}
|
||||
|
||||
private static readExperimentalDecorators(configuration: WorkspaceConfiguration): boolean {
|
||||
private static readExperimentalDecorators(configuration: vscode.WorkspaceConfiguration): boolean {
|
||||
return configuration.get<boolean>('javascript.implicitProjectConfig.experimentalDecorators', false);
|
||||
}
|
||||
|
||||
private static readNpmLocation(configuration: WorkspaceConfiguration): string | null {
|
||||
private static readNpmLocation(configuration: vscode.WorkspaceConfiguration): string | null {
|
||||
return configuration.get<string | null>('typescript.npm', null);
|
||||
}
|
||||
|
||||
private static readDisableAutomaticTypeAcquisition(configuration: WorkspaceConfiguration): boolean {
|
||||
private static readDisableAutomaticTypeAcquisition(configuration: vscode.WorkspaceConfiguration): boolean {
|
||||
return configuration.get<boolean>('typescript.disableAutomaticTypeAcquisition', false);
|
||||
}
|
||||
|
||||
private static extractLocale(configuration: WorkspaceConfiguration): string | null {
|
||||
private static extractLocale(configuration: vscode.WorkspaceConfiguration): string | null {
|
||||
return configuration.get<string | null>('typescript.locale', null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import API from '../utils/api';
|
||||
import { disposeAll } from '../utils/dispose';
|
||||
import API from './api';
|
||||
import { Disposable } from './dispose';
|
||||
|
||||
class ConditionalRegistration {
|
||||
export class ConditionalRegistration {
|
||||
private registration: vscode.Disposable | undefined = undefined;
|
||||
|
||||
public constructor(
|
||||
@@ -36,15 +36,15 @@ class ConditionalRegistration {
|
||||
}
|
||||
}
|
||||
|
||||
export class VersionDependentRegistration {
|
||||
export class VersionDependentRegistration extends Disposable {
|
||||
private readonly _registration: ConditionalRegistration;
|
||||
private readonly _disposables: vscode.Disposable[] = [];
|
||||
|
||||
constructor(
|
||||
private readonly client: ITypeScriptServiceClient,
|
||||
private readonly minVersion: API,
|
||||
register: () => vscode.Disposable,
|
||||
) {
|
||||
super();
|
||||
this._registration = new ConditionalRegistration(register);
|
||||
|
||||
this.update(client.apiVersion);
|
||||
@@ -55,7 +55,7 @@ export class VersionDependentRegistration {
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
disposeAll(this._disposables);
|
||||
super.dispose();
|
||||
this._registration.dispose();
|
||||
}
|
||||
|
||||
@@ -65,31 +65,27 @@ export class VersionDependentRegistration {
|
||||
}
|
||||
|
||||
|
||||
export class ConfigurationDependentRegistration {
|
||||
export class ConfigurationDependentRegistration extends Disposable {
|
||||
private readonly _registration: ConditionalRegistration;
|
||||
private readonly _disposables: vscode.Disposable[] = [];
|
||||
|
||||
constructor(
|
||||
private readonly language: string,
|
||||
private readonly configValue: string,
|
||||
register: () => vscode.Disposable,
|
||||
) {
|
||||
super();
|
||||
this._registration = new ConditionalRegistration(register);
|
||||
|
||||
this.update();
|
||||
|
||||
vscode.workspace.onDidChangeConfiguration(() => {
|
||||
this.update();
|
||||
}, null, this._disposables);
|
||||
vscode.workspace.onDidChangeConfiguration(this.update, this, this._disposables);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
disposeAll(this._disposables);
|
||||
super.dispose();
|
||||
this._registration.dispose();
|
||||
}
|
||||
|
||||
private update() {
|
||||
const config = vscode.workspace.getConfiguration(this.language);
|
||||
const config = vscode.workspace.getConfiguration(this.language, null);
|
||||
this._registration.update(!!config.get<boolean>(this.configValue));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function disposeAll(disposables: vscode.Disposable[]) {
|
||||
function disposeAll(disposables: vscode.Disposable[]) {
|
||||
while (disposables.length) {
|
||||
const item = disposables.pop();
|
||||
if (item) {
|
||||
@@ -13,3 +13,30 @@ export function disposeAll(disposables: vscode.Disposable[]) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class Disposable {
|
||||
private _isDisposed = false;
|
||||
|
||||
protected _disposables: vscode.Disposable[] = [];
|
||||
|
||||
public dispose(): any {
|
||||
if (this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
this._isDisposed = true;
|
||||
disposeAll(this._disposables);
|
||||
}
|
||||
|
||||
protected _register<T extends vscode.Disposable>(value: T): T {
|
||||
if (this._isDisposed) {
|
||||
value.dispose();
|
||||
} else {
|
||||
this._disposables.push(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
protected get isDisposed() {
|
||||
return this._isDisposed;
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import Logger from './logger';
|
||||
import { getTempFile, makeRandomHexString } from './temp';
|
||||
import * as temp from './temp';
|
||||
import path = require('path');
|
||||
import os = require('os');
|
||||
import net = require('net');
|
||||
import fs = require('fs');
|
||||
import cp = require('child_process');
|
||||
|
||||
export interface IForkOptions {
|
||||
@@ -15,37 +14,27 @@ export interface IForkOptions {
|
||||
execArgv?: string[];
|
||||
}
|
||||
|
||||
export function getTempSock(prefix: string): string {
|
||||
const fullName = `vscode-${prefix}-${makeRandomHexString(20)}`;
|
||||
return getTempFile(fullName + '.sock');
|
||||
const getRootTempDir = (() => {
|
||||
let dir: string | undefined;
|
||||
return () => {
|
||||
if (!dir) {
|
||||
dir = temp.getTempFile(`vscode-typescript`);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir);
|
||||
}
|
||||
}
|
||||
return dir;
|
||||
};
|
||||
})();
|
||||
|
||||
export function getTempFile(prefix: string): string {
|
||||
return path.join(getRootTempDir(), `${prefix}-${temp.makeRandomHexString(20)}.tmp`);
|
||||
}
|
||||
|
||||
function generatePipeName(): string {
|
||||
return getPipeName(makeRandomHexString(40));
|
||||
}
|
||||
|
||||
function getPipeName(name: string): string {
|
||||
const fullName = 'vscode-' + name;
|
||||
if (process.platform === 'win32') {
|
||||
return '\\\\.\\pipe\\' + fullName + '-sock';
|
||||
}
|
||||
|
||||
// Mac/Unix: use socket file
|
||||
return path.join(os.tmpdir(), fullName + '.sock');
|
||||
}
|
||||
|
||||
function generatePatchedEnv(
|
||||
env: any,
|
||||
stdInPipeName: string,
|
||||
stdOutPipeName: string,
|
||||
stdErrPipeName: string
|
||||
): any {
|
||||
function generatePatchedEnv(env: any): any {
|
||||
const newEnv = Object.assign({}, env);
|
||||
|
||||
// Set the two unique pipe names and the electron flag as process env
|
||||
newEnv['STDIN_PIPE_NAME'] = stdInPipeName;
|
||||
newEnv['STDOUT_PIPE_NAME'] = stdOutPipeName;
|
||||
newEnv['STDERR_PIPE_NAME'] = stdErrPipeName;
|
||||
newEnv['ELECTRON_RUN_AS_NODE'] = '1';
|
||||
|
||||
// Ensure we always have a PATH set
|
||||
@@ -57,87 +46,18 @@ export function fork(
|
||||
modulePath: string,
|
||||
args: string[],
|
||||
options: IForkOptions,
|
||||
logger: Logger,
|
||||
callback: (error: any, cp: cp.ChildProcess | null) => void,
|
||||
): void {
|
||||
|
||||
let callbackCalled = false;
|
||||
const resolve = (result: cp.ChildProcess) => {
|
||||
if (callbackCalled) {
|
||||
return;
|
||||
}
|
||||
callbackCalled = true;
|
||||
callback(null, result);
|
||||
};
|
||||
const reject = (err: any) => {
|
||||
if (callbackCalled) {
|
||||
return;
|
||||
}
|
||||
callbackCalled = true;
|
||||
callback(err, null);
|
||||
};
|
||||
|
||||
// Generate three unique pipe names
|
||||
const stdInPipeName = generatePipeName();
|
||||
const stdOutPipeName = generatePipeName();
|
||||
const stdErrPipeName = generatePipeName();
|
||||
|
||||
|
||||
const newEnv = generatePatchedEnv(process.env, stdInPipeName, stdOutPipeName, stdErrPipeName);
|
||||
logger: Logger
|
||||
): cp.ChildProcess {
|
||||
const newEnv = generatePatchedEnv(process.env);
|
||||
newEnv['NODE_PATH'] = path.join(modulePath, '..', '..', '..');
|
||||
let childProcess: cp.ChildProcess;
|
||||
|
||||
// Begin listening to stderr pipe
|
||||
let stdErrServer = net.createServer((stdErrStream) => {
|
||||
// From now on the childProcess.stderr is available for reading
|
||||
childProcess.stderr = stdErrStream;
|
||||
});
|
||||
stdErrServer.listen(stdErrPipeName);
|
||||
|
||||
// Begin listening to stdout pipe
|
||||
let stdOutServer = net.createServer((stdOutStream) => {
|
||||
// The child process will write exactly one chunk with content `ready` when it has installed a listener to the stdin pipe
|
||||
|
||||
stdOutStream.once('data', (_chunk: Buffer) => {
|
||||
// The child process is sending me the `ready` chunk, time to connect to the stdin pipe
|
||||
childProcess.stdin = <any>net.connect(stdInPipeName);
|
||||
|
||||
// From now on the childProcess.stdout is available for reading
|
||||
childProcess.stdout = stdOutStream;
|
||||
|
||||
resolve(childProcess);
|
||||
});
|
||||
});
|
||||
stdOutServer.listen(stdOutPipeName);
|
||||
|
||||
let serverClosed = false;
|
||||
const closeServer = () => {
|
||||
if (serverClosed) {
|
||||
return;
|
||||
}
|
||||
serverClosed = true;
|
||||
stdOutServer.close();
|
||||
stdErrServer.close();
|
||||
};
|
||||
|
||||
// Create the process
|
||||
logger.info('Forking TSServer', `PATH: ${newEnv['PATH']} `);
|
||||
|
||||
const bootstrapperPath = require.resolve('./electronForkStart');
|
||||
childProcess = cp.fork(bootstrapperPath, [modulePath].concat(args), {
|
||||
return cp.fork(modulePath, args, {
|
||||
silent: true,
|
||||
cwd: options.cwd,
|
||||
env: newEnv,
|
||||
execArgv: options.execArgv
|
||||
});
|
||||
|
||||
childProcess.once('error', (err: Error) => {
|
||||
closeServer();
|
||||
reject(err);
|
||||
});
|
||||
|
||||
childProcess.once('exit', (err: Error) => {
|
||||
closeServer();
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
var net = require('net'),
|
||||
fs = require('fs');
|
||||
|
||||
var ENABLE_LOGGING = false;
|
||||
|
||||
var log = (function () {
|
||||
if (!ENABLE_LOGGING) {
|
||||
return function () { };
|
||||
}
|
||||
var isFirst = true;
|
||||
var LOG_LOCATION = 'C:\\stdFork.log';
|
||||
return function log(str: any) {
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
fs.writeFileSync(LOG_LOCATION, str + '\n');
|
||||
return;
|
||||
}
|
||||
fs.appendFileSync(LOG_LOCATION, str + '\n');
|
||||
};
|
||||
})();
|
||||
|
||||
var stdInPipeName = process.env['STDIN_PIPE_NAME'];
|
||||
var stdOutPipeName = process.env['STDOUT_PIPE_NAME'];
|
||||
var stdErrPipeName = process.env['STDERR_PIPE_NAME'];
|
||||
|
||||
log('STDIN_PIPE_NAME: ' + stdInPipeName);
|
||||
log('STDOUT_PIPE_NAME: ' + stdOutPipeName);
|
||||
log('STDERR_PIPE_NAME: ' + stdErrPipeName);
|
||||
log('ELECTRON_RUN_AS_NODE: ' + process.env['ELECTRON_RUN_AS_NODE']);
|
||||
|
||||
// stdout redirection to named pipe
|
||||
(function () {
|
||||
log('Beginning stdout redirection...');
|
||||
|
||||
// Create a writing stream to the stdout pipe
|
||||
var stdOutStream = net.connect(stdOutPipeName);
|
||||
|
||||
// unref stdOutStream to behave like a normal standard out
|
||||
stdOutStream.unref();
|
||||
|
||||
// handle process.stdout
|
||||
(<any>process).__defineGetter__('stdout', function () { return stdOutStream; });
|
||||
|
||||
// Create a writing stream to the stderr pipe
|
||||
var stdErrStream = net.connect(stdErrPipeName);
|
||||
|
||||
// unref stdErrStream to behave like a normal standard out
|
||||
stdErrStream.unref();
|
||||
|
||||
// handle process.stderr
|
||||
(<any>process).__defineGetter__('stderr', function () { return stdErrStream; });
|
||||
|
||||
var fsWriteSyncString = function (fd: number, str: string, _position: number, encoding?: string) {
|
||||
// fs.writeSync(fd, string[, position[, encoding]]);
|
||||
var buf = Buffer.from(str, encoding || 'utf8');
|
||||
return fsWriteSyncBuffer(fd, buf, 0, buf.length);
|
||||
};
|
||||
|
||||
var fsWriteSyncBuffer = function (fd: number, buffer: Buffer, off: number, len: number) {
|
||||
off = Math.abs(off | 0);
|
||||
len = Math.abs(len | 0);
|
||||
|
||||
// fs.writeSync(fd, buffer, offset, length[, position]);
|
||||
var buffer_length = buffer.length;
|
||||
|
||||
if (off > buffer_length) {
|
||||
throw new Error('offset out of bounds');
|
||||
}
|
||||
if (len > buffer_length) {
|
||||
throw new Error('length out of bounds');
|
||||
}
|
||||
if (((off + len) | 0) < off) {
|
||||
throw new Error('off + len overflow');
|
||||
}
|
||||
if (buffer_length - off < len) {
|
||||
// Asking for more than is left over in the buffer
|
||||
throw new Error('off + len > buffer.length');
|
||||
}
|
||||
|
||||
var slicedBuffer = buffer;
|
||||
if (off !== 0 || len !== buffer_length) {
|
||||
slicedBuffer = buffer.slice(off, off + len);
|
||||
}
|
||||
|
||||
if (fd === 1) {
|
||||
stdOutStream.write(slicedBuffer);
|
||||
} else {
|
||||
stdErrStream.write(slicedBuffer);
|
||||
}
|
||||
return slicedBuffer.length;
|
||||
};
|
||||
|
||||
// handle fs.writeSync(1, ...)
|
||||
var originalWriteSync = fs.writeSync;
|
||||
fs.writeSync = function (fd: number, data: any, _position: number, _encoding?: string) {
|
||||
if (fd !== 1 && fd !== 2) {
|
||||
return originalWriteSync.apply(fs, arguments);
|
||||
}
|
||||
// usage:
|
||||
// fs.writeSync(fd, buffer, offset, length[, position]);
|
||||
// OR
|
||||
// fs.writeSync(fd, string[, position[, encoding]]);
|
||||
|
||||
if (data instanceof Buffer) {
|
||||
return fsWriteSyncBuffer.apply(null, arguments);
|
||||
}
|
||||
|
||||
// For compatibility reasons with fs.writeSync, writing null will write "null", etc
|
||||
if (typeof data !== 'string') {
|
||||
data += '';
|
||||
}
|
||||
|
||||
return fsWriteSyncString.apply(null, arguments);
|
||||
};
|
||||
|
||||
log('Finished defining process.stdout, process.stderr and fs.writeSync');
|
||||
})();
|
||||
|
||||
// stdin redirection to named pipe
|
||||
(function () {
|
||||
|
||||
// Begin listening to stdin pipe
|
||||
var server = net.createServer(function (stream: any) {
|
||||
// Stop accepting new connections, keep the existing one alive
|
||||
server.close();
|
||||
|
||||
log('Parent process has connected to my stdin. All should be good now.');
|
||||
|
||||
// handle process.stdin
|
||||
(<any>process).__defineGetter__('stdin', function () {
|
||||
return stream;
|
||||
});
|
||||
|
||||
// Remove myself from process.argv
|
||||
process.argv.splice(1, 1);
|
||||
|
||||
// Load the actual program
|
||||
var program = process.argv[1];
|
||||
log('Loading program: ' + program);
|
||||
|
||||
// Unset the custom environmental variables that should not get inherited
|
||||
delete process.env['STDIN_PIPE_NAME'];
|
||||
delete process.env['STDOUT_PIPE_NAME'];
|
||||
delete process.env['STDERR_PIPE_NAME'];
|
||||
delete process.env['ELECTRON_RUN_AS_NODE'];
|
||||
|
||||
require(program);
|
||||
|
||||
log('Finished loading program.');
|
||||
|
||||
var stdinIsReferenced = true;
|
||||
var timer = setInterval(function () {
|
||||
var listenerCount = (
|
||||
stream.listeners('data').length +
|
||||
stream.listeners('end').length +
|
||||
stream.listeners('close').length +
|
||||
stream.listeners('error').length
|
||||
);
|
||||
// log('listenerCount: ' + listenerCount);
|
||||
if (listenerCount <= 1) {
|
||||
// No more "actual" listeners, only internal node
|
||||
if (stdinIsReferenced) {
|
||||
stdinIsReferenced = false;
|
||||
// log('unreferencing stream!!!');
|
||||
stream.unref();
|
||||
}
|
||||
} else {
|
||||
// There are "actual" listeners
|
||||
if (!stdinIsReferenced) {
|
||||
stdinIsReferenced = true;
|
||||
stream.ref();
|
||||
}
|
||||
}
|
||||
// log(
|
||||
// '' + stream.listeners('data').length +
|
||||
// ' ' + stream.listeners('end').length +
|
||||
// ' ' + stream.listeners('close').length +
|
||||
// ' ' + stream.listeners('error').length
|
||||
// );
|
||||
}, 1000);
|
||||
|
||||
if ((<any>timer).unref) {
|
||||
(<any>timer).unref();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
server.listen(stdInPipeName, function () {
|
||||
// signal via stdout that the parent process can now begin writing to stdin pipe
|
||||
process.stdout.write('ready');
|
||||
});
|
||||
|
||||
})();
|
||||
@@ -4,26 +4,36 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as languageModeIds from './languageModeIds';
|
||||
|
||||
export const enum DiagnosticLanguage {
|
||||
JavaScript,
|
||||
TypeScript
|
||||
}
|
||||
|
||||
export const allDiagnosticLangauges = [DiagnosticLanguage.JavaScript, DiagnosticLanguage.TypeScript];
|
||||
|
||||
export interface LanguageDescription {
|
||||
readonly id: string;
|
||||
readonly diagnosticOwner: string;
|
||||
readonly diagnosticSource: string;
|
||||
readonly diagnosticLanguage: DiagnosticLanguage;
|
||||
readonly modeIds: string[];
|
||||
readonly configFile?: string;
|
||||
readonly isExternal?: boolean;
|
||||
readonly diagnosticOwner: string;
|
||||
}
|
||||
|
||||
export const standardLanguageDescriptions: LanguageDescription[] = [
|
||||
{
|
||||
id: 'typescript',
|
||||
diagnosticSource: 'ts',
|
||||
diagnosticOwner: 'typescript',
|
||||
diagnosticSource: 'ts',
|
||||
diagnosticLanguage: DiagnosticLanguage.TypeScript,
|
||||
modeIds: [languageModeIds.typescript, languageModeIds.typescriptreact],
|
||||
configFile: 'tsconfig.json'
|
||||
}, {
|
||||
id: 'javascript',
|
||||
diagnosticSource: 'ts',
|
||||
diagnosticOwner: 'typescript',
|
||||
diagnosticSource: 'ts',
|
||||
diagnosticLanguage: DiagnosticLanguage.JavaScript,
|
||||
modeIds: [languageModeIds.javascript, languageModeIds.javascriptreact],
|
||||
configFile: 'jsconfig.json'
|
||||
}
|
||||
|
||||
@@ -14,4 +14,8 @@ export const jsxTags = 'jsx-tags';
|
||||
|
||||
export function isSupportedLanguageMode(doc: vscode.TextDocument) {
|
||||
return vscode.languages.match([typescript, typescriptreact, javascript, javascriptreact], doc) > 0;
|
||||
}
|
||||
|
||||
export function isTypeScriptDocument(doc: vscode.TextDocument) {
|
||||
return vscode.languages.match([typescript, typescriptreact], doc) > 0;
|
||||
}
|
||||
@@ -28,13 +28,13 @@ export default class LogDirectoryProvider {
|
||||
@memoize
|
||||
private logDirectory(): string | undefined {
|
||||
try {
|
||||
const path = this.context.logDirectory;
|
||||
const path = this.context.logPath;
|
||||
if (!fs.existsSync(path)) {
|
||||
fs.mkdirSync(path);
|
||||
}
|
||||
return this.context.logDirectory;
|
||||
return this.context.logPath;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,18 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { OutputChannel, window } from 'vscode';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as is from './is';
|
||||
import { memoize } from './memoize';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export default class Logger {
|
||||
|
||||
@memoize
|
||||
private get output(): OutputChannel {
|
||||
return window.createOutputChannel(localize('channelName', 'TypeScript'));
|
||||
private get output(): vscode.OutputChannel {
|
||||
return vscode.window.createOutputChannel(localize('channelName', 'TypeScript'));
|
||||
}
|
||||
|
||||
private data2String(data: any): string {
|
||||
|
||||
@@ -9,9 +9,8 @@ import { isSupportedLanguageMode } from './languageModeIds';
|
||||
/**
|
||||
* When clause context set when the current file is managed by vscode's built-in typescript extension.
|
||||
*/
|
||||
const isManagedFile_contextName = 'typescript.isManagedFile';
|
||||
|
||||
export default class ManagedFileContextManager {
|
||||
private static readonly contextName = 'typescript.isManagedFile';
|
||||
|
||||
private isInManagedFileContext: boolean = false;
|
||||
|
||||
@@ -41,8 +40,7 @@ export default class ManagedFileContextManager {
|
||||
return;
|
||||
}
|
||||
|
||||
vscode.commands.executeCommand('setContext', isManagedFile_contextName, newValue);
|
||||
vscode.commands.executeCommand('setContext', ManagedFileContextManager.contextName, newValue);
|
||||
this.isInManagedFileContext = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as path from 'path';
|
||||
import { workspace } from 'vscode';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { TypeScriptServiceConfiguration } from './configuration';
|
||||
import { RelativeWorkspacePathResolver } from './relativePathResolver';
|
||||
|
||||
|
||||
export class TypeScriptPluginPathsProvider {
|
||||
public readonly relativePathResolver: RelativeWorkspacePathResolver = new RelativeWorkspacePathResolver();
|
||||
|
||||
@@ -37,7 +37,7 @@ export class TypeScriptPluginPathsProvider {
|
||||
return [workspacePath];
|
||||
}
|
||||
|
||||
return (workspace.workspaceFolders || [])
|
||||
return (vscode.workspace.workspaceFolders || [])
|
||||
.map(workspaceFolder => path.join(workspaceFolder.uri.fsPath, pluginPath));
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { extensions } from 'vscode';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export interface TypeScriptServerPlugin {
|
||||
readonly path: string;
|
||||
@@ -13,7 +13,7 @@ export interface TypeScriptServerPlugin {
|
||||
|
||||
export function getContributedTypeScriptServerPlugins(): TypeScriptServerPlugin[] {
|
||||
const plugins: TypeScriptServerPlugin[] = [];
|
||||
for (const extension of extensions.all) {
|
||||
for (const extension of vscode.extensions.all) {
|
||||
const pack = extension.packageJSON;
|
||||
if (pack.contributes && pack.contributes.typescriptServerPlugins && Array.isArray(pack.contributes.typescriptServerPlugins)) {
|
||||
for (const plugin of pack.contributes.typescriptServerPlugins) {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as Proto from '../protocol';
|
||||
import { MarkdownString } from 'vscode';
|
||||
|
||||
function getTagBodyText(tag: Proto.JSDocTagInfo): string | undefined {
|
||||
if (!tag.text) {
|
||||
@@ -27,7 +27,7 @@ function getTagBodyText(tag: Proto.JSDocTagInfo): string | undefined {
|
||||
function getTagDocumentation(tag: Proto.JSDocTagInfo): string | undefined {
|
||||
switch (tag.name) {
|
||||
case 'param':
|
||||
const body = (tag.text || '').split(/^([\w\.]+)\s*/);
|
||||
const body = (tag.text || '').split(/^([\w\.]+)\s*-?\s*/);
|
||||
if (body && body.length === 3) {
|
||||
const param = body[1];
|
||||
const doc = body[2];
|
||||
@@ -64,21 +64,26 @@ export function tagsMarkdownPreview(tags: Proto.JSDocTagInfo[]): string {
|
||||
export function markdownDocumentation(
|
||||
documentation: Proto.SymbolDisplayPart[],
|
||||
tags: Proto.JSDocTagInfo[]
|
||||
): MarkdownString {
|
||||
const out = new MarkdownString();
|
||||
): vscode.MarkdownString {
|
||||
const out = new vscode.MarkdownString();
|
||||
addMarkdownDocumentation(out, documentation, tags);
|
||||
return out;
|
||||
}
|
||||
|
||||
export function addMarkdownDocumentation(
|
||||
out: MarkdownString,
|
||||
documentation: Proto.SymbolDisplayPart[],
|
||||
tags: Proto.JSDocTagInfo[]
|
||||
): MarkdownString {
|
||||
out.appendMarkdown(plain(documentation));
|
||||
const tagsPreview = tagsMarkdownPreview(tags);
|
||||
if (tagsPreview) {
|
||||
out.appendMarkdown('\n\n' + tagsPreview);
|
||||
out: vscode.MarkdownString,
|
||||
documentation: Proto.SymbolDisplayPart[] | undefined,
|
||||
tags: Proto.JSDocTagInfo[] | undefined
|
||||
): vscode.MarkdownString {
|
||||
if (documentation) {
|
||||
out.appendMarkdown(plain(documentation));
|
||||
}
|
||||
|
||||
if (tags) {
|
||||
const tagsPreview = tagsMarkdownPreview(tags);
|
||||
if (tagsPreview) {
|
||||
out.appendMarkdown('\n\n' + tagsPreview);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import { loadMessageBundle } from 'vscode-nls';
|
||||
import { openOrCreateConfigFile, isImplicitProjectConfigFile } from './tsconfig';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import TelemetryReporter from './telemetry';
|
||||
import { isImplicitProjectConfigFile, openOrCreateConfigFile } from './tsconfig';
|
||||
|
||||
const localize = loadMessageBundle();
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as path from 'path';
|
||||
import { workspace } from 'vscode';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export class RelativeWorkspacePathResolver {
|
||||
public asAbsoluteWorkspacePath(relativePath: string): string | undefined {
|
||||
for (const root of workspace.workspaceFolders || []) {
|
||||
for (const root of vscode.workspace.workspaceFolders || []) {
|
||||
const rootPrefixes = [`./${root.name}/`, `${root.name}/`, `.\\${root.name}\\`, `${root.name}\\`];
|
||||
for (const rootPrefix of rootPrefixes) {
|
||||
if (relativePath.startsWith(rootPrefix)) {
|
||||
|
||||
105
extensions/typescript-language-features/src/utils/resourceMap.ts
Normal file
105
extensions/typescript-language-features/src/utils/resourceMap.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as vscode from 'vscode';
|
||||
import { memoize } from './memoize';
|
||||
import { getTempFile } from './temp';
|
||||
|
||||
/**
|
||||
* Maps of file resources
|
||||
*
|
||||
* Attempts to handle correct mapping on both case sensitive and case in-sensitive
|
||||
* file systems.
|
||||
*/
|
||||
export class ResourceMap<T> {
|
||||
private readonly _map = new Map<string, { resource: vscode.Uri, value: T }>();
|
||||
|
||||
constructor(
|
||||
private readonly _normalizePath: (resource: vscode.Uri) => string | null = (resource) => resource.fsPath
|
||||
) { }
|
||||
|
||||
public get size() {
|
||||
return this._map.size;
|
||||
}
|
||||
|
||||
public has(resource: vscode.Uri): boolean {
|
||||
const file = this.toKey(resource);
|
||||
return !!file && this._map.has(file);
|
||||
}
|
||||
|
||||
public get(resource: vscode.Uri): T | undefined {
|
||||
const file = this.toKey(resource);
|
||||
if (!file) {
|
||||
return undefined;
|
||||
}
|
||||
const entry = this._map.get(file);
|
||||
return entry ? entry.value : undefined;
|
||||
}
|
||||
|
||||
public set(resource: vscode.Uri, value: T) {
|
||||
const file = this.toKey(resource);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
const entry = this._map.get(file);
|
||||
if (entry) {
|
||||
entry.value = value;
|
||||
} else {
|
||||
this._map.set(file, { resource, value });
|
||||
}
|
||||
}
|
||||
|
||||
public delete(resource: vscode.Uri): void {
|
||||
const file = this.toKey(resource);
|
||||
if (file) {
|
||||
this._map.delete(file);
|
||||
}
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._map.clear();
|
||||
}
|
||||
|
||||
public get values(): Iterable<T> {
|
||||
return Array.from(this._map.values()).map(x => x.value);
|
||||
}
|
||||
|
||||
public get entries(): Iterable<{ resource: vscode.Uri, value: T }> {
|
||||
return this._map.values();
|
||||
}
|
||||
|
||||
private toKey(resource: vscode.Uri): string | null {
|
||||
const key = this._normalizePath(resource);
|
||||
if (!key) {
|
||||
return key;
|
||||
}
|
||||
return this.isCaseInsensitivePath(key) ? key.toLowerCase() : key;
|
||||
}
|
||||
|
||||
private isCaseInsensitivePath(path: string) {
|
||||
if (isWindowsPath(path)) {
|
||||
return true;
|
||||
}
|
||||
return path[0] === '/' && this.onIsCaseInsenitiveFileSystem;
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get onIsCaseInsenitiveFileSystem() {
|
||||
if (process.platform === 'win32') {
|
||||
return true;
|
||||
}
|
||||
if (process.platform !== 'darwin') {
|
||||
return false;
|
||||
}
|
||||
const temp = getTempFile('typescript-case-check');
|
||||
fs.writeFileSync(temp, '');
|
||||
return fs.existsSync(temp.toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
export function isWindowsPath(path: string): boolean {
|
||||
return /^[a-zA-Z]:\\/.test(path);
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import VsCodeTelemetryReporter from 'vscode-extension-telemetry';
|
||||
import { memoize } from './memoize';
|
||||
|
||||
@@ -59,15 +59,14 @@ export default class TelemetryReporter {
|
||||
|
||||
@memoize
|
||||
private get packageInfo(): IPackageInfo | null {
|
||||
const packagePath = path.join(__dirname, '..', '..', 'package.json');
|
||||
const extensionPackage = require(packagePath);
|
||||
if (extensionPackage) {
|
||||
const { packageJSON } = vscode.extensions.getExtension('vscode.typescript-language-features')!;
|
||||
if (packageJSON) {
|
||||
return {
|
||||
name: extensionPackage.name,
|
||||
version: extensionPackage.version,
|
||||
aiKey: extensionPackage.aiKey
|
||||
name: packageJSON.name,
|
||||
version: packageJSON.version,
|
||||
aiKey: packageJSON.aiKey
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { workspace } from 'vscode';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as Proto from '../protocol';
|
||||
import Logger from './logger';
|
||||
|
||||
|
||||
enum Trace {
|
||||
Off,
|
||||
Messages,
|
||||
@@ -45,7 +43,7 @@ export default class Tracer {
|
||||
}
|
||||
|
||||
private static readTrace(): Trace {
|
||||
let result: Trace = Trace.fromString(workspace.getConfiguration().get<string>('typescript.tsserver.trace', 'off'));
|
||||
let result: Trace = Trace.fromString(vscode.workspace.getConfiguration().get<string>('typescript.tsserver.trace', 'off'));
|
||||
if (result === Trace.Off && !!process.env.TSS_TRACE) {
|
||||
result = Trace.Messages;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import * as Proto from '../protocol';
|
||||
|
||||
import { TypeScriptServiceConfiguration } from './configuration';
|
||||
|
||||
|
||||
export function isImplicitProjectConfigFile(configFileName: string) {
|
||||
return configFileName.indexOf('/dev/null/') === 0;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,14 @@ export namespace Range {
|
||||
endLine: range.end.line + 1,
|
||||
endOffset: range.end.character + 1
|
||||
});
|
||||
|
||||
export const toFormattingRequestArgs = (file: string, range: vscode.Range): Proto.FormatRequestArgs => ({
|
||||
file,
|
||||
line: range.start.line + 1,
|
||||
offset: range.start.character + 1,
|
||||
endLine: range.end.line + 1,
|
||||
endOffset: range.end.character + 1
|
||||
});
|
||||
}
|
||||
|
||||
export namespace Position {
|
||||
@@ -50,11 +58,18 @@ export namespace TextEdit {
|
||||
}
|
||||
|
||||
export namespace WorkspaceEdit {
|
||||
export function fromFromFileCodeEdits(
|
||||
export function fromFileCodeEdits(
|
||||
client: ITypeScriptServiceClient,
|
||||
edits: Iterable<Proto.FileCodeEdits>
|
||||
): vscode.WorkspaceEdit {
|
||||
return withFileCodeEdits(new vscode.WorkspaceEdit(), client, edits);
|
||||
|
||||
}
|
||||
export function withFileCodeEdits(
|
||||
workspaceEdit: vscode.WorkspaceEdit,
|
||||
client: ITypeScriptServiceClient,
|
||||
edits: Iterable<Proto.FileCodeEdits>
|
||||
): vscode.WorkspaceEdit {
|
||||
const workspaceEdit = new vscode.WorkspaceEdit();
|
||||
for (const edit of edits) {
|
||||
for (const textChange of edit.textChanges) {
|
||||
workspaceEdit.replace(client.toResource(edit.fileName),
|
||||
|
||||
@@ -3,18 +3,18 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MessageItem, workspace, Disposable, ProgressLocation, window } from 'vscode';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import * as vscode from 'vscode';
|
||||
import { loadMessageBundle } from 'vscode-nls';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
|
||||
const localize = loadMessageBundle();
|
||||
|
||||
const typingsInstallTimeout = 30 * 1000;
|
||||
|
||||
export default class TypingsStatus extends Disposable {
|
||||
export default class TypingsStatus extends vscode.Disposable {
|
||||
private _acquiringTypings: { [eventId: string]: NodeJS.Timer } = Object.create({});
|
||||
private _client: ITypeScriptServiceClient;
|
||||
private _subscriptions: Disposable[] = [];
|
||||
private _subscriptions: vscode.Disposable[] = [];
|
||||
|
||||
constructor(client: ITypeScriptServiceClient) {
|
||||
super(() => this.dispose());
|
||||
@@ -60,10 +60,10 @@ export default class TypingsStatus extends Disposable {
|
||||
export class AtaProgressReporter {
|
||||
|
||||
private _promises = new Map<number, Function>();
|
||||
private _disposable: Disposable;
|
||||
private _disposable: vscode.Disposable;
|
||||
|
||||
constructor(client: ITypeScriptServiceClient) {
|
||||
this._disposable = Disposable.from(
|
||||
this._disposable = vscode.Disposable.from(
|
||||
client.onDidBeginInstallTypings(e => this._onBegin(e.eventId)),
|
||||
client.onDidEndInstallTypings(e => this._onEndOrTimeout(e.eventId)),
|
||||
client.onTypesInstallerInitializationFailed(_ => this.onTypesInstallerInitializationFailed()));
|
||||
@@ -83,8 +83,8 @@ export class AtaProgressReporter {
|
||||
});
|
||||
});
|
||||
|
||||
window.withProgress({
|
||||
location: ProgressLocation.Window,
|
||||
vscode.window.withProgress({
|
||||
location: vscode.ProgressLocation.Window,
|
||||
title: localize('installingPackages', "Fetching data for better TypeScript IntelliSense")
|
||||
}, () => promise);
|
||||
}
|
||||
@@ -98,12 +98,12 @@ export class AtaProgressReporter {
|
||||
}
|
||||
|
||||
private onTypesInstallerInitializationFailed() {
|
||||
interface MyMessageItem extends MessageItem {
|
||||
interface MyMessageItem extends vscode.MessageItem {
|
||||
id: number;
|
||||
}
|
||||
|
||||
if (workspace.getConfiguration('typescript').get<boolean>('check.npmIsInstalled', true)) {
|
||||
window.showWarningMessage<MyMessageItem>(
|
||||
if (vscode.workspace.getConfiguration('typescript').get<boolean>('check.npmIsInstalled', true)) {
|
||||
vscode.window.showWarningMessage<MyMessageItem>(
|
||||
localize(
|
||||
'typesInstallerInitializationFailed.title',
|
||||
"Could not install typings files for JavaScript language features. Please ensure that NPM is installed or configure 'typescript.npm' in your user settings. Click [here]({0}) to learn more.",
|
||||
@@ -118,7 +118,7 @@ export class AtaProgressReporter {
|
||||
}
|
||||
switch (selected.id) {
|
||||
case 1:
|
||||
const tsConfig = workspace.getConfiguration('typescript');
|
||||
const tsConfig = vscode.workspace.getConfiguration('typescript');
|
||||
tsConfig.update('check.npmIsInstalled', false, true);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { TypeScriptVersionProvider, TypeScriptVersion } from './versionProvider';
|
||||
import { Memento, commands, Uri, window, QuickPickItem, workspace } from 'vscode';
|
||||
import { TypeScriptVersion, TypeScriptVersionProvider } from './versionProvider';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
const useWorkspaceTsdkStorageKey = 'typescript.useWorkspaceTsdk';
|
||||
|
||||
interface MyQuickPickItem extends QuickPickItem {
|
||||
interface MyQuickPickItem extends vscode.QuickPickItem {
|
||||
id: MessageAction;
|
||||
version?: TypeScriptVersion;
|
||||
}
|
||||
@@ -27,7 +27,7 @@ export class TypeScriptVersionPicker {
|
||||
|
||||
public constructor(
|
||||
private readonly versionProvider: TypeScriptVersionProvider,
|
||||
private readonly workspaceState: Memento
|
||||
private readonly workspaceState: vscode.Memento
|
||||
) {
|
||||
this._currentVersion = this.versionProvider.defaultVersion;
|
||||
|
||||
@@ -82,7 +82,7 @@ export class TypeScriptVersionPicker {
|
||||
id: MessageAction.learnMore
|
||||
});
|
||||
|
||||
const selected = await window.showQuickPick<MyQuickPickItem>(pickOptions, {
|
||||
const selected = await vscode.window.showQuickPick<MyQuickPickItem>(pickOptions, {
|
||||
placeHolder: localize(
|
||||
'selectTsVersion',
|
||||
'Select the TypeScript version used for JavaScript and TypeScript language features'),
|
||||
@@ -97,7 +97,7 @@ export class TypeScriptVersionPicker {
|
||||
case MessageAction.useLocal:
|
||||
await this.workspaceState.update(useWorkspaceTsdkStorageKey, true);
|
||||
if (selected.version) {
|
||||
const tsConfig = workspace.getConfiguration('typescript');
|
||||
const tsConfig = vscode.workspace.getConfiguration('typescript');
|
||||
await tsConfig.update('tsdk', selected.version.pathLabel, false);
|
||||
|
||||
const previousVersion = this.currentVersion;
|
||||
@@ -114,7 +114,7 @@ export class TypeScriptVersionPicker {
|
||||
|
||||
|
||||
case MessageAction.learnMore:
|
||||
commands.executeCommand('vscode.open', Uri.parse('https://go.microsoft.com/fwlink/?linkid=839919'));
|
||||
vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://go.microsoft.com/fwlink/?linkid=839919'));
|
||||
return { oldVersion: this.currentVersion };
|
||||
|
||||
default:
|
||||
|
||||
@@ -2,17 +2,14 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
import { workspace, window } from 'vscode';
|
||||
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import API from './api';
|
||||
import { TypeScriptServiceConfiguration } from './configuration';
|
||||
import { RelativeWorkspacePathResolver } from './relativePathResolver';
|
||||
import API from './api';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
||||
export class TypeScriptVersion {
|
||||
@@ -40,7 +37,7 @@ export class TypeScriptVersion {
|
||||
}
|
||||
|
||||
// Allow TS developers to provide custom version
|
||||
const tsdkVersion = workspace.getConfiguration().get<string | undefined>('typescript.tsdk_version', undefined);
|
||||
const tsdkVersion = vscode.workspace.getConfiguration().get<string | undefined>('typescript.tsdk_version', undefined);
|
||||
if (tsdkVersion) {
|
||||
return API.fromVersionString(tsdkVersion);
|
||||
}
|
||||
@@ -143,16 +140,16 @@ export class TypeScriptVersionProvider {
|
||||
|
||||
public get bundledVersion(): TypeScriptVersion {
|
||||
try {
|
||||
const bundledVersion = new TypeScriptVersion(
|
||||
path.dirname(require.resolve('typescript/lib/tsserver.js')),
|
||||
'');
|
||||
const { extensionPath } = vscode.extensions.getExtension('vscode.typescript-language-features')!;
|
||||
const typescriptPath = path.join(extensionPath, '../node_modules/typescript/lib');
|
||||
const bundledVersion = new TypeScriptVersion(typescriptPath, '');
|
||||
if (bundledVersion.isValid) {
|
||||
return bundledVersion;
|
||||
}
|
||||
} catch (e) {
|
||||
// noop
|
||||
}
|
||||
window.showErrorMessage(localize(
|
||||
vscode.window.showErrorMessage(localize(
|
||||
'noBundledServerFound',
|
||||
'VS Code\'s tsserver was deleted by another application such as a misbehaving virus detection tool. Please reinstall VS Code.'));
|
||||
throw new Error('Could not find bundled tsserver.js');
|
||||
@@ -182,14 +179,14 @@ export class TypeScriptVersionProvider {
|
||||
}
|
||||
|
||||
private loadTypeScriptVersionsFromPath(relativePath: string): TypeScriptVersion[] {
|
||||
if (!workspace.workspaceFolders) {
|
||||
if (!vscode.workspace.workspaceFolders) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const versions: TypeScriptVersion[] = [];
|
||||
for (const root of workspace.workspaceFolders) {
|
||||
for (const root of vscode.workspace.workspaceFolders) {
|
||||
let label: string = relativePath;
|
||||
if (workspace.workspaceFolders && workspace.workspaceFolders.length > 1) {
|
||||
if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 1) {
|
||||
label = path.join(root.name, relativePath);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { TypeScriptVersion } from './versionProvider';
|
||||
import * as languageModeIds from './languageModeIds';
|
||||
import { TypeScriptVersion } from './versionProvider';
|
||||
|
||||
export default class VersionStatus {
|
||||
private readonly _onChangeEditorSub: vscode.Disposable;
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as stream from 'stream';
|
||||
import * as vscode from 'vscode';
|
||||
import { Disposable } from './dispose';
|
||||
|
||||
const DefaultSize: number = 8192;
|
||||
const ContentLength: string = 'Content-Length: ';
|
||||
@@ -78,26 +80,27 @@ class ProtocolBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
export interface ICallback<T> {
|
||||
(data: T): void;
|
||||
}
|
||||
|
||||
export class Reader<T> {
|
||||
export class Reader<T> extends Disposable {
|
||||
|
||||
private readonly buffer: ProtocolBuffer = new ProtocolBuffer();
|
||||
private nextMessageLength: number = -1;
|
||||
|
||||
public constructor(
|
||||
private readonly readable: stream.Readable,
|
||||
private readonly callback: ICallback<T>,
|
||||
private readonly onError: (error: any) => void
|
||||
) {
|
||||
this.readable.on('data', (data: Buffer) => {
|
||||
this.onLengthData(data);
|
||||
});
|
||||
public constructor(readable: stream.Readable) {
|
||||
super();
|
||||
readable.on('data', data => this.onLengthData(data));
|
||||
}
|
||||
|
||||
private onLengthData(data: Buffer): void {
|
||||
private readonly _onError = this._register(new vscode.EventEmitter<Error>());
|
||||
public readonly onError = this._onError.event;
|
||||
|
||||
private readonly _onData = this._register(new vscode.EventEmitter<T>());
|
||||
public readonly onData = this._onData.event;
|
||||
|
||||
private onLengthData(data: Buffer | string): void {
|
||||
if (this.isDisposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.buffer.append(data);
|
||||
while (true) {
|
||||
@@ -113,10 +116,10 @@ export class Reader<T> {
|
||||
}
|
||||
this.nextMessageLength = -1;
|
||||
const json = JSON.parse(msg);
|
||||
this.callback(json);
|
||||
this._onData.fire(json);
|
||||
}
|
||||
} catch (e) {
|
||||
this.onError(e);
|
||||
this._onError.fire(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user