Merge remote-tracking branch 'origin/master' into pr/limerickgds/51557

This commit is contained in:
Alex Dima
2018-09-11 15:30:40 +02:00
10817 changed files with 109048 additions and 208700 deletions

View File

@@ -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
}

View File

@@ -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();

View File

@@ -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));
}

View File

@@ -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);
}

View File

@@ -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));
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}
}

View File

@@ -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(

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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[];
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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(

View File

@@ -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),
'*');
});
}

View File

@@ -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));
}
}

View File

@@ -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);

View File

@@ -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));
}

View File

@@ -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);
});
}

View File

@@ -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;
}

View File

@@ -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: ''

View File

@@ -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(

View File

@@ -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,

View File

@@ -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))));
}

View File

@@ -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);

View 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());
}

View File

@@ -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);
}
}

View File

@@ -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));
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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';

View File

@@ -1,2 +1,2 @@
import * as Proto from 'typescript/lib/protocol';
export = Proto;
export = Proto;

View File

@@ -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');
});
});

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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);
});
}

View File

@@ -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');
});
})();

View File

@@ -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'
}

View File

@@ -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;
}

View File

@@ -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;
}
}
}
}

View File

@@ -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 {

View File

@@ -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;
}
}

View File

@@ -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));
}
}

View File

@@ -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) {

View File

@@ -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;
}
}

View File

@@ -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();

View File

@@ -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)) {

View 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);
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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),

View File

@@ -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;
}

View File

@@ -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:

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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);
}
}
}