Rename typescript to typescript-language-features

This commit is contained in:
Matt Bierner
2018-03-23 12:52:26 -07:00
parent 6c04bb4b64
commit 0b655c0603
79 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,176 @@
/*---------------------------------------------------------------------------------------------
* 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 TypeScriptServiceClientHost from './typeScriptServiceClientHost';
import { Command } from './utils/commandManager';
import { Lazy } from './utils/lazy';
import { openOrCreateConfigFile, isImplicitProjectConfigFile } from './utils/tsconfig';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export class ReloadTypeScriptProjectsCommand implements Command {
public readonly id = 'typescript.reloadProjects';
public constructor(
private readonly lazyClientHost: Lazy<TypeScriptServiceClientHost>
) { }
public execute() {
this.lazyClientHost.value.reloadProjects();
}
}
export class ReloadJavaScriptProjectsCommand implements Command {
public readonly id = 'javascript.reloadProjects';
public constructor(
private readonly lazyClientHost: Lazy<TypeScriptServiceClientHost>
) { }
public execute() {
this.lazyClientHost.value.reloadProjects();
}
}
export class SelectTypeScriptVersionCommand implements Command {
public readonly id = 'typescript.selectTypeScriptVersion';
public constructor(
private readonly lazyClientHost: Lazy<TypeScriptServiceClientHost>
) { }
public execute() {
this.lazyClientHost.value.serviceClient.onVersionStatusClicked();
}
}
export class OpenTsServerLogCommand implements Command {
public readonly id = 'typescript.openTsServerLog';
public constructor(
private readonly lazyClientHost: Lazy<TypeScriptServiceClientHost>
) { }
public execute() {
this.lazyClientHost.value.serviceClient.openTsServerLogFile();
}
}
export class RestartTsServerCommand implements Command {
public readonly id = 'typescript.restartTsServer';
public constructor(
private readonly lazyClientHost: Lazy<TypeScriptServiceClientHost>
) { }
public execute() {
this.lazyClientHost.value.serviceClient.restartTsServer();
}
}
export class TypeScriptGoToProjectConfigCommand implements Command {
public readonly id = 'typescript.goToProjectConfig';
public constructor(
private readonly lazyClientHost: Lazy<TypeScriptServiceClientHost>,
) { }
public execute() {
const editor = vscode.window.activeTextEditor;
if (editor) {
goToProjectConfig(this.lazyClientHost.value, true, editor.document.uri);
}
}
}
export class JavaScriptGoToProjectConfigCommand implements Command {
public readonly id = 'javascript.goToProjectConfig';
public constructor(
private readonly lazyClientHost: Lazy<TypeScriptServiceClientHost>,
) { }
public execute() {
const editor = vscode.window.activeTextEditor;
if (editor) {
goToProjectConfig(this.lazyClientHost.value, false, editor.document.uri);
}
}
}
async function goToProjectConfig(
clientHost: TypeScriptServiceClientHost,
isTypeScriptProject: boolean,
resource: vscode.Uri
): Promise<void> {
const client = clientHost.serviceClient;
const rootPath = client.getWorkspaceRootForResource(resource);
if (!rootPath) {
vscode.window.showInformationMessage(
localize(
'typescript.projectConfigNoWorkspace',
'Please open a folder in VS Code to use a TypeScript or JavaScript project'));
return;
}
const file = client.normalizePath(resource);
// TSServer errors when 'projectInfo' is invoked on a non js/ts file
if (!file || !clientHost.handles(resource)) {
vscode.window.showWarningMessage(
localize(
'typescript.projectConfigUnsupportedFile',
'Could not determine TypeScript or JavaScript project. Unsupported file type'));
return;
}
let res: protocol.ProjectInfoResponse | undefined = undefined;
try {
res = await client.execute('projectInfo', { file, needFileNameList: false } as protocol.ProjectInfoRequestArgs);
} catch {
// noop
}
if (!res || !res.body) {
vscode.window.showWarningMessage(localize('typescript.projectConfigCouldNotGetInfo', 'Could not determine TypeScript or JavaScript project'));
return;
}
const { configFileName } = res.body;
if (configFileName && !isImplicitProjectConfigFile(configFileName)) {
const doc = await vscode.workspace.openTextDocument(configFileName);
vscode.window.showTextDocument(doc, vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined);
return;
}
enum ProjectConfigAction {
None,
CreateConfig,
LearnMore
}
interface ProjectConfigMessageItem extends vscode.MessageItem {
id: ProjectConfigAction;
}
const selected = await vscode.window.showInformationMessage<ProjectConfigMessageItem>(
(isTypeScriptProject
? localize('typescript.noTypeScriptProjectConfig', 'File is not part of a TypeScript project. Click [here]({0}) to learn more.', 'https://go.microsoft.com/fwlink/?linkid=841896')
: localize('typescript.noJavaScriptProjectConfig', 'File is not part of a JavaScript project Click [here]({0}) to learn more.', 'https://go.microsoft.com/fwlink/?linkid=759670')
), {
title: isTypeScriptProject
? localize('typescript.configureTsconfigQuickPick', 'Configure tsconfig.json')
: localize('typescript.configureJsconfigQuickPick', 'Configure jsconfig.json'),
id: ProjectConfigAction.CreateConfig
});
switch (selected && selected.id) {
case ProjectConfigAction.CreateConfig:
openOrCreateConfigFile(isTypeScriptProject, rootPath, client.configuration);
return;
}
}

View File

@@ -0,0 +1,117 @@
/*---------------------------------------------------------------------------------------------
* 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 { CommandManager } from './utils/commandManager';
import TypeScriptServiceClientHost from './typeScriptServiceClientHost';
import * as commands from './commands';
import TypeScriptTaskProviderManager from './features/taskProvider';
import { getContributedTypeScriptServerPlugins, TypeScriptServerPlugin } from './utils/plugins';
import * as ProjectStatus from './utils/projectStatus';
import * as languageModeIds from './utils/languageModeIds';
import * as languageConfigurations from './utils/languageConfigurations';
import { standardLanguageDescriptions } from './utils/languageDescription';
import ManagedFileContextManager from './utils/managedFileContext';
import { lazy, Lazy } from './utils/lazy';
import * as fileSchemes from './utils/fileSchemes';
import LogDirectoryProvider from './utils/logDirectoryProvider';
import { OrganizeImportsCommand, OrganizeImportsContextManager } from './features/organizeImports';
export function activate(
context: vscode.ExtensionContext
): void {
const plugins = getContributedTypeScriptServerPlugins();
const commandManager = new CommandManager();
context.subscriptions.push(commandManager);
const lazyClientHost = createLazyClientHost(context, plugins, commandManager);
registerCommands(commandManager, lazyClientHost);
context.subscriptions.push(new TypeScriptTaskProviderManager(lazyClientHost.map(x => x.serviceClient)));
context.subscriptions.push(vscode.languages.setLanguageConfiguration(languageModeIds.jsxTags, languageConfigurations.jsxTags));
const supportedLanguage = [].concat.apply([], standardLanguageDescriptions.map(x => x.modeIds).concat(plugins.map(x => x.languages)));
function didOpenTextDocument(textDocument: vscode.TextDocument): boolean {
if (isSupportedDocument(supportedLanguage, textDocument)) {
openListener.dispose();
// Force activation
// tslint:disable-next-line:no-unused-expression
void lazyClientHost.value;
context.subscriptions.push(new ManagedFileContextManager(resource => {
return lazyClientHost.value.serviceClient.normalizePath(resource);
}));
return true;
}
return false;
}
const openListener = vscode.workspace.onDidOpenTextDocument(didOpenTextDocument, undefined, context.subscriptions);
for (const textDocument of vscode.workspace.textDocuments) {
if (didOpenTextDocument(textDocument)) {
break;
}
}
}
function createLazyClientHost(
context: vscode.ExtensionContext,
plugins: TypeScriptServerPlugin[],
commandManager: CommandManager
): Lazy<TypeScriptServiceClientHost> {
return lazy(() => {
const logDirectoryProvider = new LogDirectoryProvider(context);
const clientHost = new TypeScriptServiceClientHost(
standardLanguageDescriptions,
context.workspaceState,
plugins,
commandManager,
logDirectoryProvider);
context.subscriptions.push(clientHost);
const organizeImportsContext = new OrganizeImportsContextManager();
clientHost.serviceClient.onTsServerStarted(api => {
organizeImportsContext.onDidChangeApiVersion(api);
}, null, context.subscriptions);
clientHost.serviceClient.onReady(() => {
context.subscriptions.push(
ProjectStatus.create(
clientHost.serviceClient,
clientHost.serviceClient.telemetryReporter,
path => new Promise<boolean>(resolve => setTimeout(() => resolve(clientHost.handles(path)), 750)),
context.workspaceState));
});
return clientHost;
});
}
function registerCommands(
commandManager: CommandManager,
lazyClientHost: Lazy<TypeScriptServiceClientHost>
) {
commandManager.register(new commands.ReloadTypeScriptProjectsCommand(lazyClientHost));
commandManager.register(new commands.ReloadJavaScriptProjectsCommand(lazyClientHost));
commandManager.register(new commands.SelectTypeScriptVersionCommand(lazyClientHost));
commandManager.register(new commands.OpenTsServerLogCommand(lazyClientHost));
commandManager.register(new commands.RestartTsServerCommand(lazyClientHost));
commandManager.register(new commands.TypeScriptGoToProjectConfigCommand(lazyClientHost));
commandManager.register(new commands.JavaScriptGoToProjectConfigCommand(lazyClientHost));
commandManager.register(new OrganizeImportsCommand(lazyClientHost));
}
function isSupportedDocument(
supportedLanguage: string[],
document: vscode.TextDocument
): boolean {
if (supportedLanguage.indexOf(document.languageId) < 0) {
return false;
}
return fileSchemes.isSupportedScheme(document.uri.scheme);
}

View File

@@ -0,0 +1,150 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* 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 Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import * as typeConverters from '../utils/typeConverters';
import { escapeRegExp } from '../utils/regexp';
export class ReferencesCodeLens extends CodeLens {
constructor(
public document: Uri,
public file: string,
range: Range
) {
super(range);
}
}
export class CachedNavTreeResponse {
private response?: Promise<Proto.NavTreeResponse>;
private version: number = -1;
private document: string = '';
public execute(
document: TextDocument,
f: () => Promise<Proto.NavTreeResponse>
) {
if (this.matches(document)) {
return this.response;
}
return this.update(document, f());
}
private matches(document: TextDocument): boolean {
return this.version === document.version && this.document === document.uri.toString();
}
private update(
document: TextDocument,
response: Promise<Proto.NavTreeResponse>
): Promise<Proto.NavTreeResponse> {
this.response = response;
this.version = document.version;
this.document = document.uri.toString();
return response;
}
}
export abstract class TypeScriptBaseCodeLensProvider implements CodeLensProvider {
private enabled: boolean = true;
private onDidChangeCodeLensesEmitter = new EventEmitter<void>();
public constructor(
protected client: ITypeScriptServiceClient,
private cachedResponse: CachedNavTreeResponse
) { }
public get onDidChangeCodeLenses(): Event<void> {
return this.onDidChangeCodeLensesEmitter.event;
}
protected setEnabled(enabled: false): void {
if (this.enabled !== enabled) {
this.enabled = enabled;
this.onDidChangeCodeLensesEmitter.fire();
}
}
async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
if (!this.enabled) {
return [];
}
const filepath = this.client.normalizePath(document.uri);
if (!filepath) {
return [];
}
try {
const response = await this.cachedResponse.execute(document, () => this.client.execute('navtree', { file: filepath }, token));
if (!response) {
return [];
}
const tree = response.body;
const referenceableSpans: Range[] = [];
if (tree && tree.childItems) {
tree.childItems.forEach(item => this.walkNavTree(document, item, null, referenceableSpans));
}
return referenceableSpans.map(span => new ReferencesCodeLens(document.uri, filepath, span));
} catch {
return [];
}
}
protected abstract extractSymbol(
document: TextDocument,
item: Proto.NavigationTree,
parent: Proto.NavigationTree | null
): Range | null;
private walkNavTree(
document: TextDocument,
item: Proto.NavigationTree,
parent: Proto.NavigationTree | null,
results: Range[]
): void {
if (!item) {
return;
}
const range = this.extractSymbol(document, item, parent);
if (range) {
results.push(range);
}
(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 {
if (!item) {
return null;
}
const span = item.spans && item.spans[0];
if (!span) {
return null;
}
const range = typeConverters.Range.fromTextSpan(span);
const text = document.getText(range);
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(
document.positionAt(startOffset),
document.positionAt(startOffset + item.text.length));
}
}

View File

@@ -0,0 +1,294 @@
/*---------------------------------------------------------------------------------------------
* 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 { workspace, TextDocument, TextDocumentChangeEvent, TextDocumentContentChangeEvent, Disposable, Uri } from 'vscode';
import * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import { Delayer } from '../utils/async';
import * as languageModeIds from '../utils/languageModeIds';
import { disposeAll } from '../utils/dipose';
interface IDiagnosticRequestor {
requestDiagnostic(resource: Uri): void;
}
function mode2ScriptKind(mode: string): 'TS' | 'TSX' | 'JS' | 'JSX' | undefined {
switch (mode) {
case languageModeIds.typescript: return 'TS';
case languageModeIds.typescriptreact: return 'TSX';
case languageModeIds.javascript: return 'JS';
case languageModeIds.javascriptreact: return 'JSX';
}
return undefined;
}
class SyncedBuffer {
constructor(
private readonly document: TextDocument,
private readonly filepath: string,
private readonly diagnosticRequestor: IDiagnosticRequestor,
private readonly client: ITypeScriptServiceClient
) { }
public open(): void {
const args: Proto.OpenRequestArgs = {
file: this.filepath,
fileContent: this.document.getText(),
};
if (this.client.apiVersion.has203Features()) {
const scriptKind = mode2ScriptKind(this.document.languageId);
if (scriptKind) {
args.scriptKindName = scriptKind;
}
}
if (this.client.apiVersion.has230Features()) {
args.projectRootPath = this.client.getWorkspaceRootForResource(this.document.uri);
}
if (this.client.apiVersion.has240Features()) {
const tsPluginsForDocument = this.client.plugins
.filter(x => x.languages.indexOf(this.document.languageId) >= 0);
if (tsPluginsForDocument.length) {
(args as any).plugins = tsPluginsForDocument.map(plugin => plugin.name);
}
}
this.client.execute('open', args, false);
}
public get lineCount(): number {
return this.document.lineCount;
}
public close(): void {
const args: Proto.FileRequestArgs = {
file: this.filepath
};
this.client.execute('close', args, false);
}
public onContentChanged(events: TextDocumentContentChangeEvent[]): void {
const filePath = this.client.normalizePath(this.document.uri);
if (!filePath) {
return;
}
for (const event of events) {
const range = event.range;
const text = event.text;
const args: Proto.ChangeRequestArgs = {
file: filePath,
line: range.start.line + 1,
offset: range.start.character + 1,
endLine: range.end.line + 1,
endOffset: range.end.character + 1,
insertString: text
};
this.client.execute('change', args, false);
}
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._normalizePath(resource);
return !!file && this._map.has(file);
}
public get(resource: Uri): SyncedBuffer | undefined {
const file = this._normalizePath(resource);
return file ? this._map.get(file) : undefined;
}
public set(resource: Uri, buffer: SyncedBuffer) {
const file = this._normalizePath(resource);
if (file) {
this._map.set(file, buffer);
}
}
public delete(resource: Uri): void {
const file = this._normalizePath(resource);
if (file) {
this._map.delete(file);
}
}
public get allBuffers(): Iterable<SyncedBuffer> {
return this._map.values();
}
public get allResources(): Iterable<string> {
return this._map.keys();
}
}
export interface Diagnostics {
delete(resource: Uri): void;
}
export default class BufferSyncSupport {
private readonly client: ITypeScriptServiceClient;
private _validate: boolean;
private readonly modeIds: Set<string>;
private readonly diagnostics: Diagnostics;
private readonly disposables: Disposable[] = [];
private readonly syncedBuffers: SyncedBufferMap;
private readonly pendingDiagnostics = new Map<string, number>();
private readonly diagnosticDelayer: Delayer<any>;
constructor(
client: ITypeScriptServiceClient,
modeIds: string[],
diagnostics: Diagnostics,
validate: boolean
) {
this.client = client;
this.modeIds = new Set<string>(modeIds);
this.diagnostics = diagnostics;
this._validate = validate;
this.diagnosticDelayer = new Delayer<any>(300);
this.syncedBuffers = new SyncedBufferMap(path => this.client.normalizePath(path));
}
public listen(): void {
workspace.onDidOpenTextDocument(this.onDidOpenTextDocument, this, this.disposables);
workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, this.disposables);
workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, this.disposables);
workspace.textDocuments.forEach(this.onDidOpenTextDocument, this);
}
public set validate(value: boolean) {
this._validate = value;
}
public handles(resource: Uri): boolean {
return this.syncedBuffers.has(resource);
}
public reOpenDocuments(): void {
for (const buffer of this.syncedBuffers.allBuffers) {
buffer.open();
}
}
public dispose(): void {
disposeAll(this.disposables);
}
private onDidOpenTextDocument(document: TextDocument): void {
if (!this.modeIds.has(document.languageId)) {
return;
}
const resource = document.uri;
const filepath = this.client.normalizePath(resource);
if (!filepath) {
return;
}
const syncedBuffer = new SyncedBuffer(document, filepath, this, this.client);
this.syncedBuffers.set(resource, syncedBuffer);
syncedBuffer.open();
this.requestDiagnostic(resource);
}
private onDidCloseTextDocument(document: TextDocument): void {
const resource = document.uri;
const syncedBuffer = this.syncedBuffers.get(resource);
if (!syncedBuffer) {
return;
}
this.diagnostics.delete(resource);
this.syncedBuffers.delete(resource);
syncedBuffer.close();
if (!fs.existsSync(resource.fsPath)) {
this.requestAllDiagnostics();
}
}
private onDidChangeTextDocument(e: TextDocumentChangeEvent): void {
const syncedBuffer = this.syncedBuffers.get(e.document.uri);
if (syncedBuffer) {
syncedBuffer.onContentChanged(e.contentChanges);
}
}
public requestAllDiagnostics() {
if (!this._validate) {
return;
}
for (const filePath of this.syncedBuffers.allResources) {
this.pendingDiagnostics.set(filePath, Date.now());
}
this.diagnosticDelayer.trigger(() => {
this.sendPendingDiagnostics();
}, 200);
}
public requestDiagnostic(resource: Uri): void {
if (!this._validate) {
return;
}
const file = this.client.normalizePath(resource);
if (!file) {
return;
}
this.pendingDiagnostics.set(file, Date.now());
const buffer = this.syncedBuffers.get(resource);
let delay = 300;
if (buffer) {
const lineCount = buffer.lineCount;
delay = Math.min(Math.max(Math.ceil(lineCount / 20), 300), 800);
}
this.diagnosticDelayer.trigger(() => {
this.sendPendingDiagnostics();
}, delay);
}
private sendPendingDiagnostics(): void {
if (!this._validate) {
return;
}
const files = Array.from(this.pendingDiagnostics.entries())
.sort((a, b) => a[1] - b[1])
.map(entry => entry[0]);
// 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.push(file);
}
}
if (files.length) {
const args: Proto.GeterrRequestArgs = {
delay: 0,
files: files
};
this.client.execute('geterr', args, false);
}
this.pendingDiagnostics.clear();
}
}

View File

@@ -0,0 +1,561 @@
/*---------------------------------------------------------------------------------------------
* 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 { ITypeScriptServiceClient } from '../typescriptService';
import TypingsStatus from '../utils/typingsStatus';
import * as Proto from '../protocol';
import * as PConst from '../protocol.const';
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';
const localize = nls.loadMessageBundle();
class MyCompletionItem extends vscode.CompletionItem {
public readonly useCodeSnippet: boolean;
constructor(
public readonly position: vscode.Position,
public readonly document: vscode.TextDocument,
line: string,
public readonly tsEntry: Proto.CompletionEntry,
enableDotCompletions: boolean,
useCodeSnippetsOnMethodSuggest: boolean
) {
super(tsEntry.name);
if (tsEntry.isRecommended) {
// Make sure isRecommended property always comes first
// https://github.com/Microsoft/vscode/issues/40325
this.sortText = '\0' + tsEntry.sortText;
} else if (tsEntry.source) {
// De-prioritze auto-imports
// https://github.com/Microsoft/vscode/issues/40311
this.sortText = '\uffff' + tsEntry.sortText;
} else {
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);
}
if (tsEntry.insertText) {
this.insertText = tsEntry.insertText;
if (tsEntry.replacementSpan) {
this.range = typeConverters.Range.fromTextSpan(tsEntry.replacementSpan);
if (this.insertText[0] === '[') { // o.x -> o['x']
this.filterText = '.' + this.label;
}
// Make sure we only replace a single line at most
if (!this.range.isSingleLine) {
this.range = new vscode.Range(this.range.start.line, this.range.start.character, this.range.start.line, line.length);
}
}
}
if (tsEntry.kindModifiers && tsEntry.kindModifiers.match(/\boptional\b/)) {
if (!this.insertText) {
this.insertText = this.label;
}
if (!this.filterText) {
this.filterText = this.label;
}
this.label += '?';
}
}
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 static convertKind(kind: string): vscode.CompletionItemKind {
switch (kind) {
case PConst.Kind.primitiveType:
case PConst.Kind.keyword:
return vscode.CompletionItemKind.Keyword;
case PConst.Kind.const:
return vscode.CompletionItemKind.Constant;
case PConst.Kind.let:
case PConst.Kind.variable:
case PConst.Kind.localVariable:
case PConst.Kind.alias:
return vscode.CompletionItemKind.Variable;
case PConst.Kind.memberVariable:
case PConst.Kind.memberGetAccessor:
case PConst.Kind.memberSetAccessor:
return vscode.CompletionItemKind.Field;
case PConst.Kind.function:
return vscode.CompletionItemKind.Function;
case PConst.Kind.memberFunction:
case PConst.Kind.constructSignature:
case PConst.Kind.callSignature:
case PConst.Kind.indexSignature:
return vscode.CompletionItemKind.Method;
case PConst.Kind.enum:
return vscode.CompletionItemKind.Enum;
case PConst.Kind.module:
case PConst.Kind.externalModuleName:
return vscode.CompletionItemKind.Module;
case PConst.Kind.class:
case PConst.Kind.type:
return vscode.CompletionItemKind.Class;
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:
return vscode.CompletionItemKind.Folder;
}
return vscode.CompletionItemKind.Property;
}
private static getCommitCharacters(
enableDotCompletions: boolean,
enableCallCompletions: boolean,
kind: string
): string[] | undefined {
switch (kind) {
case PConst.Kind.memberGetAccessor:
case PConst.Kind.memberSetAccessor:
case PConst.Kind.constructSignature:
case PConst.Kind.callSignature:
case PConst.Kind.indexSignature:
case PConst.Kind.enum:
case PConst.Kind.interface:
return enableDotCompletions ? ['.'] : undefined;
case PConst.Kind.module:
case PConst.Kind.alias:
case PConst.Kind.const:
case PConst.Kind.let:
case PConst.Kind.variable:
case PConst.Kind.localVariable:
case PConst.Kind.memberVariable:
case PConst.Kind.class:
case PConst.Kind.function:
case PConst.Kind.memberFunction:
return enableDotCompletions ? (enableCallCompletions ? ['.', '('] : ['.']) : undefined;
}
return undefined;
}
}
class ApplyCompletionCodeActionCommand implements Command {
public static readonly ID = '_typescript.applyCompletionCodeAction';
public readonly id = ApplyCompletionCodeActionCommand.ID;
public constructor(
private readonly client: ITypeScriptServiceClient
) { }
public async execute(_file: string, codeActions: Proto.CodeAction[]): Promise<boolean> {
if (codeActions.length === 0) {
return true;
}
if (codeActions.length === 1) {
return applyCodeAction(this.client, codeActions[0]);
}
interface MyQuickPickItem extends vscode.QuickPickItem {
index: number;
}
const selection = await vscode.window.showQuickPick<MyQuickPickItem>(
codeActions.map((action, i): MyQuickPickItem => ({
label: action.description,
description: '',
index: i
})), {
placeHolder: localize('selectCodeAction', 'Select code action to apply')
}
);
if (!selection) {
return false;
}
const action = codeActions[selection.index];
if (!action) {
return false;
}
return applyCodeAction(this.client, action);
}
}
interface CompletionConfiguration {
readonly useCodeSnippetsOnMethodSuggest: boolean;
readonly nameSuggestions: boolean;
readonly quickSuggestionsForPaths: boolean;
readonly autoImportSuggestions: boolean;
}
namespace CompletionConfiguration {
export const useCodeSnippetsOnMethodSuggest = 'useCodeSnippetsOnMethodSuggest';
export const nameSuggestions = 'nameSuggestions';
export const quickSuggestionsForPaths = 'quickSuggestionsForPaths';
export const autoImportSuggestions = 'autoImportSuggestions.enabled';
export function getConfigurationForResource(
resource: vscode.Uri
): CompletionConfiguration {
// TS settings are 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)
};
}
}
export default class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider {
constructor(
private readonly client: ITypeScriptServiceClient,
private readonly typingsStatus: TypingsStatus,
commandManager: CommandManager
) {
commandManager.register(new ApplyCompletionCodeActionCommand(this.client));
}
public async provideCompletionItems(
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken,
context: vscode.CompletionContext
): Promise<vscode.CompletionItem[]> {
if (this.typingsStatus.isAcquiringTypings) {
return Promise.reject<vscode.CompletionItem[]>({
label: localize(
{ key: 'acquiringTypingsLabel', comment: ['Typings refers to the *.d.ts typings files that power our IntelliSense. It should not be localized'] },
'Acquiring typings...'),
detail: localize(
{ key: 'acquiringTypingsDetail', comment: ['Typings refers to the *.d.ts typings files that power our IntelliSense. It should not be localized'] },
'Acquiring typings definitions for IntelliSense.')
});
}
const file = this.client.normalizePath(document.uri);
if (!file) {
return [];
}
const line = document.lineAt(position.line);
const completionConfiguration = CompletionConfiguration.getConfigurationForResource(document.uri);
if (!this.shouldTrigger(context, completionConfiguration, line, position)) {
return [];
}
const args: Proto.CompletionsRequestArgs = {
...typeConverters.Position.toFileLocationRequestArgs(file, position),
includeExternalModuleExports: completionConfiguration.autoImportSuggestions,
includeInsertTextCompletions: true
};
let msg: Proto.CompletionEntry[] | undefined = undefined;
try {
const response = await this.client.execute('completions', args, token);
msg = response.body;
if (!msg) {
return [];
}
} catch {
return [];
}
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;
}
public async resolveCompletionItem(
item: vscode.CompletionItem,
token: vscode.CancellationToken
): Promise<vscode.CompletionItem | undefined> {
if (!(item instanceof MyCompletionItem)) {
return undefined;
}
const filepath = this.client.normalizePath(item.document.uri);
if (!filepath) {
return undefined;
}
item.resolve();
const args: Proto.CompletionDetailsRequestArgs = {
...typeConverters.Position.toFileLocationRequestArgs(filepath, item.position),
entryNames: [
item.tsEntry.source ? { name: item.tsEntry.name, source: item.tsEntry.source } : item.tsEntry.name
]
};
let response: Proto.CompletionDetailsResponse;
try {
response = await this.client.execute('completionEntryDetails', args, token);
} catch {
return item;
}
const details = response.body;
if (!details || !details.length || !details[0]) {
return item;
}
const detail = details[0];
item.detail = detail.displayParts.length ? Previewer.plain(detail.displayParts) : undefined;
item.documentation = this.getDocumentation(detail, item);
const { command, additionalTextEdits } = this.getCodeActions(detail, filepath);
item.command = command;
item.additionalTextEdits = additionalTextEdits;
if (detail && item.useCodeSnippet) {
const shouldCompleteFunction = await this.isValidFunctionCompletionContext(filepath, item.position);
if (shouldCompleteFunction) {
item.insertText = this.snippetForFunctionCall(item, detail);
}
}
return item;
}
private getCodeActions(
detail: Proto.CompletionEntryDetails,
filepath: string
): { command?: vscode.Command, additionalTextEdits?: vscode.TextEdit[] } {
if (!detail.codeActions || !detail.codeActions.length) {
return {};
}
// Try to extract out the additionalTextEdits for the current file.
// Also check if we still have to apply other workspace edits and commands
// using a vscode command
const additionalTextEdits: vscode.TextEdit[] = [];
let hasReaminingCommandsOrEdits = false;
for (const tsAction of detail.codeActions) {
if (tsAction.commands) {
hasReaminingCommandsOrEdits = true;
}
// Apply all edits in the current file using `additionalTextEdits`
if (tsAction.changes) {
for (const change of tsAction.changes) {
if (change.fileName === filepath) {
additionalTextEdits.push(...change.textChanges.map(typeConverters.TextEdit.fromCodeEdit));
} else {
hasReaminingCommandsOrEdits = true;
}
}
}
}
let command: vscode.Command | undefined = undefined;
if (hasReaminingCommandsOrEdits) {
// Create command that applies all edits not in the current file.
command = {
title: '',
command: ApplyCompletionCodeActionCommand.ID,
arguments: [filepath, detail.codeActions.map((x): Proto.CodeAction => ({
commands: x.commands,
description: x.description,
changes: x.changes.filter(x => x.fileName !== filepath)
}))]
};
}
return {
command,
additionalTextEdits: additionalTextEdits.length ? additionalTextEdits : undefined
};
}
private shouldEnableDotCompletions(
document: vscode.TextDocument,
position: vscode.Position
): boolean {
// TODO: Workaround for https://github.com/Microsoft/TypeScript/issues/13456
// Only enable dot completions when previous character is an identifier.
// Prevents incorrectly completing while typing spread operators.
if (position.character > 1) {
const preText = document.getText(new vscode.Range(
position.line, 0,
position.line, position.character - 1));
return preText.match(/[a-z_$\)\]\}]\s*$/ig) !== null;
}
return true;
}
private shouldTrigger(
context: vscode.CompletionContext,
config: CompletionConfiguration,
line: vscode.TextLine,
position: vscode.Position
): boolean {
if (context.triggerCharacter === '"' || context.triggerCharacter === '\'') {
if (!config.quickSuggestionsForPaths) {
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;
}
// 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;
}
}
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;
}
}
return true;
}
private getDocumentation(
detail: Proto.CompletionEntryDetails,
item: MyCompletionItem
): vscode.MarkdownString | undefined {
const documentation = new vscode.MarkdownString();
if (detail.source) {
const importPath = `'${Previewer.plain(detail.source)}'`;
const autoImportLabel = localize('autoImportLabel', 'Auto import from {0}', importPath);
item.detail = `${autoImportLabel}\n${item.detail}`;
}
Previewer.addMarkdownDocumentation(documentation, detail.documentation, detail.tags);
return documentation.value.length ? documentation : undefined;
}
private async isValidFunctionCompletionContext(
filepath: string,
position: vscode.Position
): 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) {
case 'var':
case 'let':
case 'const':
case 'alias':
return false;
default:
return true;
}
} catch (e) {
return true;
}
}
private snippetForFunctionCall(
item: vscode.CompletionItem,
detail: Proto.CompletionEntryDetails
): vscode.SnippetString {
let hasOptionalParameters = false;
let hasAddedParameters = false;
const snippet = new vscode.SnippetString();
const methodName = detail.displayParts.find(part => part.kind === 'methodName');
snippet.appendText((methodName && methodName.text) || item.label || item.insertText as string);
snippet.appendText('(');
let parenCount = 0;
let i = 0;
for (; i < detail.displayParts.length; ++i) {
const part = detail.displayParts[i];
// Only take top level paren names
if (part.kind === 'parameterName' && parenCount === 1) {
const next = detail.displayParts[i + 1];
// Skip optional parameters
const nameIsFollowedByOptionalIndicator = next && next.text === '?';
if (!nameIsFollowedByOptionalIndicator) {
if (hasAddedParameters) {
snippet.appendText(', ');
}
hasAddedParameters = true;
snippet.appendPlaceholder(part.text);
}
hasOptionalParameters = hasOptionalParameters || nameIsFollowedByOptionalIndicator;
} else if (part.kind === 'punctuation') {
if (part.text === '(') {
++parenCount;
} else if (part.text === ')') {
--parenCount;
} else if (part.text === '...' && parenCount === 1) {
// Found rest parmeter. Do not fill in any further arguments
hasOptionalParameters = true;
break;
}
}
}
if (hasOptionalParameters) {
snippet.appendTabstop();
}
snippet.appendText(')');
snippet.appendTabstop(0);
return snippet;
}
}

View File

@@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DefinitionProvider, TextDocument, Position, CancellationToken, Definition } from 'vscode';
import DefinitionProviderBase from './definitionProviderBase';
export default class TypeScriptDefinitionProvider extends DefinitionProviderBase implements DefinitionProvider {
public provideDefinition(document: TextDocument, position: Position, token: CancellationToken | boolean): Promise<Definition | undefined> {
return this.getSymbolLocations('definition', document, position, token);
}
}

View File

@@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextDocument, Position, CancellationToken, Location } 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 async getSymbolLocations(
definitionType: 'definition' | 'implementation' | 'typeDefinition',
document: TextDocument,
position: Position,
token: CancellationToken | boolean
): Promise<Location[] | undefined> {
const filepath = this.client.normalizePath(document.uri);
if (!filepath) {
return undefined;
}
const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
try {
const response = await this.client.execute(definitionType, args, token);
const locations: Proto.FileSpan[] = (response && response.body) || [];
return locations.map(location =>
typeConverters.Location.fromTextSpan(this.client.asUrl(location.file), location));
} catch {
return [];
}
}
}

View File

@@ -0,0 +1,114 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Diagnostic, DiagnosticCollection, languages, Uri } from 'vscode';
class DiagnosticSet {
private _map: ObjectMap<Diagnostic[]> = Object.create(null);
public set(
file: Uri,
diagnostics: Diagnostic[]
) {
this._map[this.key(file)] = diagnostics;
}
public get(
file: Uri
): Diagnostic[] {
return this._map[this.key(file)] || [];
}
public clear(): void {
this._map = Object.create(null);
}
private key(file: Uri): string {
return file.toString(true);
}
}
export enum DiagnosticKind {
Syntax,
Semantic,
Suggestion
}
const allDiagnosticKinds = [DiagnosticKind.Syntax, DiagnosticKind.Semantic, DiagnosticKind.Suggestion];
export class DiagnosticsManager {
private readonly diagnostics = new Map<DiagnosticKind, DiagnosticSet>();
private readonly currentDiagnostics: DiagnosticCollection;
private _validate: boolean = true;
constructor(
language: string
) {
for (const kind of allDiagnosticKinds) {
this.diagnostics.set(kind, new DiagnosticSet());
}
this.currentDiagnostics = languages.createDiagnosticCollection(language);
}
public dispose() {
this.currentDiagnostics.dispose();
}
public reInitialize(): void {
this.currentDiagnostics.clear();
for (const diagnosticSet of this.diagnostics.values()) {
diagnosticSet.clear();
}
}
public set validate(value: boolean) {
if (this._validate === value) {
return;
}
this._validate = value;
if (!value) {
this.currentDiagnostics.clear();
}
}
public diagnosticsReceived(
kind: DiagnosticKind,
file: Uri,
syntaxDiagnostics: Diagnostic[]
): void {
const diagnostics = this.diagnostics.get(kind);
if (diagnostics) {
diagnostics.set(file, syntaxDiagnostics);
this.updateCurrentDiagnostics(file);
}
}
public configFileDiagnosticsReceived(file: Uri, diagnostics: Diagnostic[]): void {
this.currentDiagnostics.set(file, diagnostics);
}
public delete(resource: Uri): void {
this.currentDiagnostics.delete(resource);
}
private updateCurrentDiagnostics(file: Uri) {
if (!this._validate) {
return;
}
const allDiagnostics = allDiagnosticKinds.reduce((sum, kind) => {
sum.push(...this.diagnostics.get(kind)!.get(file));
return sum;
}, [] as Diagnostic[]);
this.currentDiagnostics.set(file, allDiagnostics);
}
public getDiagnostics(file: Uri): Diagnostic[] {
return this.currentDiagnostics.get(file) || [];
}
}

View File

@@ -0,0 +1,76 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Position, CompletionItemProvider, CompletionItemKind, TextDocument, CancellationToken, CompletionItem, Range } from 'vscode';
import { ITypeScriptServiceClient } from '../typescriptService';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
interface Directive {
value: string;
description: string;
}
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.')
}, {
value: '@ts-nocheck',
description: localize(
'ts-nocheck',
'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.')
}
];
export default class DirectiveCommentCompletionProvider implements CompletionItemProvider {
constructor(
private readonly client: ITypeScriptServiceClient,
) { }
public provideCompletionItems(
document: TextDocument,
position: Position,
_token: CancellationToken
): CompletionItem[] {
if (!this.client.apiVersion.has230Features()) {
return [];
}
const file = this.client.normalizePath(document.uri);
if (!file) {
return [];
}
const line = document.lineAt(position.line).text;
const prefix = line.slice(0, position.character);
const match = prefix.match(/^\s*\/\/+\s?(@[a-zA-Z\-]*)?$/);
if (match) {
return directives.map(directive => {
const item = new CompletionItem(directive.value, CompletionItemKind.Snippet);
item.detail = directive.description;
item.range = new Range(position.line, Math.max(0, position.character - (match[1] ? match[1].length : 0)), position.line, position.character);
return item;
});
}
return [];
}
public resolveCompletionItem(
item: CompletionItem,
_token: CancellationToken
) {
return item;
}
}

View File

@@ -0,0 +1,48 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DocumentHighlightProvider, DocumentHighlight, DocumentHighlightKind, TextDocument, Position, CancellationToken } from 'vscode';
import * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import * as typeConverters from '../utils/typeConverters';
export default class TypeScriptDocumentHighlightProvider implements DocumentHighlightProvider {
public constructor(
private readonly client: ITypeScriptServiceClient
) { }
public async provideDocumentHighlights(
resource: TextDocument,
position: Position,
token: CancellationToken
): Promise<DocumentHighlight[]> {
const file = this.client.normalizePath(resource.uri);
if (!file) {
return [];
}
const args = typeConverters.Position.toFileLocationRequestArgs(file, position);
try {
const response = await this.client.execute('occurrences', args, token);
if (response && response.body) {
return response.body
.filter(x => !x.isInString)
.map(documentHighlightFromOccurance);
}
} catch {
// noop
}
return [];
}
}
function documentHighlightFromOccurance(occurrence: Proto.OccurrencesResponseItem): DocumentHighlight {
return new DocumentHighlight(
typeConverters.Range.fromTextSpan(occurrence),
occurrence.isWriteAccess ? DocumentHighlightKind.Write : DocumentHighlightKind.Read);
}

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 { DocumentSymbolProvider, SymbolInformation, SymbolKind, TextDocument, CancellationToken, Uri } from 'vscode';
import * as Proto from '../protocol';
import * as PConst from '../protocol.const';
import { ITypeScriptServiceClient } from '../typescriptService';
import * as typeConverters from '../utils/typeConverters';
const outlineTypeTable: { [kind: string]: SymbolKind } = Object.create(null);
outlineTypeTable[PConst.Kind.module] = SymbolKind.Module;
outlineTypeTable[PConst.Kind.class] = SymbolKind.Class;
outlineTypeTable[PConst.Kind.enum] = SymbolKind.Enum;
outlineTypeTable[PConst.Kind.interface] = SymbolKind.Interface;
outlineTypeTable[PConst.Kind.memberFunction] = SymbolKind.Method;
outlineTypeTable[PConst.Kind.memberVariable] = SymbolKind.Property;
outlineTypeTable[PConst.Kind.memberGetAccessor] = SymbolKind.Property;
outlineTypeTable[PConst.Kind.memberSetAccessor] = SymbolKind.Property;
outlineTypeTable[PConst.Kind.variable] = SymbolKind.Variable;
outlineTypeTable[PConst.Kind.const] = SymbolKind.Variable;
outlineTypeTable[PConst.Kind.localVariable] = SymbolKind.Variable;
outlineTypeTable[PConst.Kind.variable] = SymbolKind.Variable;
outlineTypeTable[PConst.Kind.function] = SymbolKind.Function;
outlineTypeTable[PConst.Kind.localFunction] = SymbolKind.Function;
export default class TypeScriptDocumentSymbolProvider implements DocumentSymbolProvider {
public constructor(
private readonly client: ITypeScriptServiceClient) { }
public async provideDocumentSymbols(resource: TextDocument, token: CancellationToken): Promise<SymbolInformation[]> {
const filepath = this.client.normalizePath(resource.uri);
if (!filepath) {
return [];
}
const args: Proto.FileRequestArgs = {
file: filepath
};
try {
const result: SymbolInformation[] = [];
if (this.client.apiVersion.has206Features()) {
const response = await this.client.execute('navtree', args, token);
if (response.body) {
// The root represents the file. Ignore this when showing in the UI
let tree = response.body;
if (tree.childItems) {
tree.childItems.forEach(item => TypeScriptDocumentSymbolProvider.convertNavTree(resource.uri, result, item));
}
}
} else {
const response = await this.client.execute('navbar', args, token);
if (response.body) {
let foldingMap: ObjectMap<SymbolInformation> = Object.create(null);
response.body.forEach(item => TypeScriptDocumentSymbolProvider.convertNavBar(resource.uri, 0, foldingMap, result, item));
}
}
return result;
} catch (e) {
return [];
}
}
private static convertNavBar(resource: Uri, indent: number, foldingMap: ObjectMap<SymbolInformation>, bucket: SymbolInformation[], item: Proto.NavigationBarItem, containerLabel?: string): void {
let realIndent = indent + item.indent;
let key = `${realIndent}|${item.text}`;
if (realIndent !== 0 && !foldingMap[key] && TypeScriptDocumentSymbolProvider.shouldInclueEntry(item.text)) {
let result = new SymbolInformation(item.text,
outlineTypeTable[item.kind as string] || SymbolKind.Variable,
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: Uri, bucket: SymbolInformation[], item: Proto.NavigationTree, containerLabel?: string): void {
const result = new SymbolInformation(item.text,
outlineTypeTable[item.kind as string] || SymbolKind.Variable,
containerLabel ? containerLabel : '',
typeConverters.Location.fromTextSpan(resource, item.spans[0])
);
if (item.childItems && item.childItems.length > 0) {
for (const child of item.childItems) {
TypeScriptDocumentSymbolProvider.convertNavTree(resource, bucket, child, result.name);
}
}
if (TypeScriptDocumentSymbolProvider.shouldInclueEntry(result.name)) {
bucket.push(result);
}
}
private static shouldInclueEntry(name: string): boolean {
return !!(name && name !== '<function>' && name !== '<class>');
}
}

View File

@@ -0,0 +1,42 @@
/*---------------------------------------------------------------------------------------------
* 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 * as typeConverters from '../utils/typeConverters';
import { ITypeScriptServiceClient } from '../typescriptService';
export default class TypeScriptFoldingProvider implements vscode.FoldingProvider {
public constructor(
private readonly client: ITypeScriptServiceClient
) { }
async provideFoldingRanges(
document: vscode.TextDocument,
_context: vscode.FoldingContext,
token: vscode.CancellationToken
): Promise<vscode.FoldingRangeList | undefined> {
if (!this.client.apiVersion.has280Features()) {
return;
}
const file = this.client.normalizePath(document.uri);
if (!file) {
return;
}
const args: Proto.FileRequestArgs = { file };
const response: Proto.OutliningSpansResponse = await this.client.execute('getOutliningSpans', args, token);
if (!response || !response.body) {
return;
}
return new vscode.FoldingRangeList(response.body.map(span => {
const range = typeConverters.Range.fromTextSpan(span.textSpan);
return new vscode.FoldingRange(range.start.line, range.end.line);
}));
}
}

View File

@@ -0,0 +1,125 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* 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 } from 'vscode';
import * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import * as languageIds from '../utils/languageModeIds';
namespace FormattingConfiguration {
export function equals(a: Proto.FormatCodeSettings, b: Proto.FormatCodeSettings): boolean {
let keys = Object.keys(a);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
if ((a as any)[key] !== (b as any)[key]) {
return false;
}
}
return true;
}
}
export default class FormattingConfigurationManager {
private onDidCloseTextDocumentSub: Disposable | undefined;
private formatOptions: { [key: string]: Proto.FormatCodeSettings | undefined; } = Object.create(null);
public constructor(
private readonly client: ITypeScriptServiceClient
) {
this.onDidCloseTextDocumentSub = Workspace.onDidCloseTextDocument((textDocument) => {
const key = textDocument.uri.toString();
// 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];
});
}
public dispose() {
if (this.onDidCloseTextDocumentSub) {
this.onDidCloseTextDocumentSub.dispose();
this.onDidCloseTextDocumentSub = undefined;
}
}
public async ensureFormatOptionsForDocument(
document: TextDocument,
token: CancellationToken | undefined
): 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;
return this.ensureFormatOptions(document, formattingOptions, token);
}
}
public async ensureFormatOptions(
document: TextDocument,
options: FormattingOptions,
token: CancellationToken | undefined
): Promise<void> {
const file = this.client.normalizePath(document.uri);
if (!file) {
return;
}
const key = document.uri.toString();
const cachedOptions = this.formatOptions[key];
const formatOptions = this.getFormatOptions(document, options);
if (cachedOptions && FormattingConfiguration.equals(cachedOptions, formatOptions)) {
return;
}
const args: Proto.ConfigureRequestArguments = {
file: file,
formatOptions: formatOptions
};
await this.client.execute('configure', args, token);
this.formatOptions[key] = formatOptions;
}
public reset() {
this.formatOptions = Object.create(null);
}
private getFormatOptions(
document: TextDocument,
options: FormattingOptions
): Proto.FormatCodeSettings {
const config = workspace.getConfiguration(
document.languageId === languageIds.typescript || document.languageId === languageIds.typescriptreact
? 'typescript.format'
: 'javascript.format',
document.uri);
return {
tabSize: options.tabSize,
indentSize: options.tabSize,
convertTabsToSpaces: options.insertSpaces,
// We can use \n here since the editor normalizes later on to its line endings.
newLineCharacter: '\n',
insertSpaceAfterCommaDelimiter: config.get<boolean>('insertSpaceAfterCommaDelimiter'),
insertSpaceAfterConstructor: config.get<boolean>('insertSpaceAfterConstructor'),
insertSpaceAfterSemicolonInForStatements: config.get<boolean>('insertSpaceAfterSemicolonInForStatements'),
insertSpaceBeforeAndAfterBinaryOperators: config.get<boolean>('insertSpaceBeforeAndAfterBinaryOperators'),
insertSpaceAfterKeywordsInControlFlowStatements: config.get<boolean>('insertSpaceAfterKeywordsInControlFlowStatements'),
insertSpaceAfterFunctionKeywordForAnonymousFunctions: config.get<boolean>('insertSpaceAfterFunctionKeywordForAnonymousFunctions'),
insertSpaceBeforeFunctionParenthesis: config.get<boolean>('insertSpaceBeforeFunctionParenthesis'),
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: config.get<boolean>('insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis'),
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: config.get<boolean>('insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets'),
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: config.get<boolean>('insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces'),
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: config.get<boolean>('insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces'),
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: config.get<boolean>('insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces'),
insertSpaceAfterTypeAssertion: config.get<boolean>('insertSpaceAfterTypeAssertion'),
placeOpenBraceOnNewLineForFunctions: config.get<boolean>('placeOpenBraceOnNewLineForFunctions'),
placeOpenBraceOnNewLineForControlBlocks: config.get<boolean>('placeOpenBraceOnNewLineForControlBlocks'),
};
}
}

View File

@@ -0,0 +1,146 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DocumentRangeFormattingEditProvider, OnTypeFormattingEditProvider, FormattingOptions, TextDocument, Position, Range, CancellationToken, TextEdit, WorkspaceConfiguration, Disposable, languages, workspace, DocumentSelector } from 'vscode';
import * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import * as typeConverters from '../utils/typeConverters';
import FormattingConfigurationManager from './formattingConfigurationManager';
export class TypeScriptFormattingProvider implements DocumentRangeFormattingEditProvider, OnTypeFormattingEditProvider {
private enabled: boolean = true;
public constructor(
private readonly client: ITypeScriptServiceClient,
private readonly formattingOptionsManager: FormattingConfigurationManager
) { }
public updateConfiguration(config: WorkspaceConfiguration): void {
this.enabled = config.get('format.enable', true);
}
public isEnabled(): boolean {
return this.enabled;
}
private async doFormat(
document: TextDocument,
options: FormattingOptions,
args: Proto.FormatRequestArgs,
token: CancellationToken
): Promise<TextEdit[]> {
await this.formattingOptionsManager.ensureFormatOptions(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: TextDocument,
range: Range,
options: FormattingOptions,
token: CancellationToken
): Promise<TextEdit[]> {
const absPath = this.client.normalizePath(document.uri);
if (!absPath) {
return [];
}
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);
}
public async provideOnTypeFormattingEdits(
document: TextDocument,
position: Position,
ch: string,
options: FormattingOptions,
token: CancellationToken
): Promise<TextEdit[]> {
const filepath = this.client.normalizePath(document.uri);
if (!filepath) {
return [];
}
await this.formattingOptionsManager.ensureFormatOptions(document, options, token);
const args: Proto.FormatOnKeyRequestArgs = {
file: filepath,
line: position.line + 1,
offset: position.character + 1,
key: ch
};
try {
const response = await this.client.execute('formatonkey', args, token);
const edits = response.body;
const result: TextEdit[] = [];
if (!edits) {
return result;
}
for (const edit of edits) {
const textEdit = typeConverters.TextEdit.fromCodeEdit(edit);
const range = textEdit.range;
// Work around for https://github.com/Microsoft/TypeScript/issues/6700.
// Check if we have an edit at the beginning of the line which only removes white spaces and leaves
// an empty line. Drop those edits
if (range.start.character === 0 && range.start.line === range.end.line && textEdit.newText === '') {
const lText = document.lineAt(range.start.line).text;
// If the edit leaves something on the line keep the edit (note that the end character is exclusive).
// Keep it also if it removes something else than whitespace
if (lText.trim().length > 0 || lText.length > range.end.character) {
result.push(textEdit);
}
} else {
result.push(textEdit);
}
}
return result;
} catch {
// noop
}
return [];
}
}
export class FormattingProviderManager {
private formattingProviderRegistration: Disposable | undefined;
constructor(
private readonly modeId: string,
private readonly formattingProvider: TypeScriptFormattingProvider,
private readonly selector: DocumentSelector
) { }
public dispose() {
if (this.formattingProviderRegistration) {
this.formattingProviderRegistration.dispose();
this.formattingProviderRegistration = undefined;
}
}
public updateConfiguration(): void {
const config = workspace.getConfiguration(this.modeId);
this.formattingProvider.updateConfiguration(config);
if (!this.formattingProvider.isEnabled() && this.formattingProviderRegistration) {
this.formattingProviderRegistration.dispose();
this.formattingProviderRegistration = undefined;
} else if (this.formattingProvider.isEnabled() && !this.formattingProviderRegistration) {
this.formattingProviderRegistration = languages.registerDocumentRangeFormattingEditProvider(this.selector, this.formattingProvider);
}
}
}

View File

@@ -0,0 +1,56 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { HoverProvider, Hover, TextDocument, Position, CancellationToken } from 'vscode';
import * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import { tagsMarkdownPreview } from '../utils/previewer';
import * as typeConverters from '../utils/typeConverters';
export default class TypeScriptHoverProvider implements HoverProvider {
public constructor(
private readonly client: ITypeScriptServiceClient
) { }
public async provideHover(
document: TextDocument,
position: Position,
token: CancellationToken
): Promise<Hover | undefined> {
const filepath = this.client.normalizePath(document.uri);
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;
return new Hover(
TypeScriptHoverProvider.getContents(data),
typeConverters.Range.fromTextSpan(data));
}
} catch (e) {
// noop
}
return undefined;
}
private static getContents(
data: Proto.QuickInfoResponseBody
) {
const parts = [];
if (data.displayString) {
parts.push({ language: 'typescript', value: data.displayString });
}
const tags = tagsMarkdownPreview(data.tags);
parts.push(data.documentation + (tags ? '\n\n' + tags : ''));
return parts;
}
}

View File

@@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ImplementationProvider, TextDocument, Position, CancellationToken, Definition } from 'vscode';
import DefinitionProviderBase from './definitionProviderBase';
export default class TypeScriptImplementationProvider extends DefinitionProviderBase implements ImplementationProvider {
public provideImplementation(document: TextDocument, position: Position, token: CancellationToken | boolean): Promise<Definition | undefined> {
return this.getSymbolLocations('implementation', document, position, token);
}
}

View File

@@ -0,0 +1,99 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CodeLens, CancellationToken, TextDocument, Range, Location, workspace } from 'vscode';
import * as Proto from '../protocol';
import * as PConst from '../protocol.const';
import { TypeScriptBaseCodeLensProvider, ReferencesCodeLens, CachedNavTreeResponse } from './baseCodeLensProvider';
import { ITypeScriptServiceClient } from '../typescriptService';
import * as typeConverters from '../utils/typeConverters';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export default class TypeScriptImplementationsCodeLensProvider extends TypeScriptBaseCodeLensProvider {
public constructor(
client: ITypeScriptServiceClient,
private readonly language: string,
cachedResponse: CachedNavTreeResponse
) {
super(client, cachedResponse);
}
public updateConfiguration(): void {
const config = workspace.getConfiguration(this.language);
this.setEnabled(config.get('implementationsCodeLens.enabled', false));
}
public async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
if (!this.client.apiVersion.has220Features()) {
return [];
}
return super.provideCodeLenses(document, token);
}
public resolveCodeLens(inputCodeLens: CodeLens, token: CancellationToken): Promise<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;
}
const locations = response.body
.map(reference =>
// Only take first line on implementation: https://github.com/Microsoft/vscode/issues/23924
new Location(this.client.asUrl(reference.file),
reference.start.line === reference.end.line
? typeConverters.Range.fromTextSpan(reference)
: new 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: 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;
});
}
protected extractSymbol(
document: TextDocument,
item: Proto.NavigationTree,
_parent: Proto.NavigationTree | null
): Range | null {
switch (item.kind) {
case PConst.Kind.interface:
return super.getSymbolRange(document, item);
case PConst.Kind.class:
case PConst.Kind.memberFunction:
case PConst.Kind.memberVariable:
case PConst.Kind.memberGetAccessor:
case PConst.Kind.memberSetAccessor:
if (item.kindModifiers.match(/\babstract\b/g)) {
return super.getSymbolRange(document, item);
}
break;
}
return null;
}
}

View File

@@ -0,0 +1,208 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Position, Range, CompletionItemProvider, CompletionItemKind, TextDocument, CancellationToken, CompletionItem, window, Uri, TextEditor, SnippetString, workspace } from 'vscode';
import { ITypeScriptServiceClient } from '../typescriptService';
import * as Proto from '../protocol';
import * as nls from 'vscode-nls';
import * as typeConverters from '../utils/typeConverters';
import { Command, CommandManager } from '../utils/commandManager';
const localize = nls.loadMessageBundle();
const configurationNamespace = 'jsDocCompletion';
namespace Configuration {
export const enabled = 'enabled';
}
class JsDocCompletionItem extends CompletionItem {
constructor(
document: TextDocument,
position: Position,
shouldGetJSDocFromTSServer: boolean,
) {
super('/** */', 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(
start,
position.translate(0, suffix ? suffix[0].length : 0));
this.command = {
title: 'Try Complete JSDoc',
command: TryCompleteJsDocCommand.COMMAND_NAME,
arguments: [document.uri, start, shouldGetJSDocFromTSServer]
};
}
}
export default class JsDocCompletionProvider implements CompletionItemProvider {
constructor(
private readonly client: ITypeScriptServiceClient,
commandManager: CommandManager
) {
commandManager.register(new TryCompleteJsDocCommand(client));
}
public async provideCompletionItems(
document: TextDocument,
position: Position,
token: CancellationToken
): Promise<CompletionItem[]> {
const file = this.client.normalizePath(document.uri);
if (!file) {
return [];
}
// TODO: unregister provider when disabled
const enableJsDocCompletions = workspace.getConfiguration(configurationNamespace, document.uri).get<boolean>(Configuration.enabled, true);
if (!enableJsDocCompletions) {
return [];
}
// 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);
if (prefix.match(/^\s*$|\/\*\*\s*$|^\s*\/\*\*+\s*$/) === null) {
return [];
}
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 [];
}
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;
}
if (!matchesPosition(body)) {
return [];
}
return [new JsDocCompletionItem(document, position, enableJsDocCompletions)];
}
public resolveCompletionItem(item: CompletionItem, _token: CancellationToken) {
return item;
}
}
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, shouldGetJSDocFromTSServer: boolean): Promise<boolean> {
const file = this.client.normalizePath(resource);
if (!file) {
return false;
}
const editor = window.activeTextEditor;
if (!editor || editor.document.uri.fsPath !== resource.fsPath) {
return false;
}
if (!shouldGetJSDocFromTSServer) {
return this.tryInsertDefaultDoc(editor, start);
}
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 TryCompleteJsDocCommand.templateToSnippet(res.body.newText);
}, () => undefined);
}
private static templateToSnippet(template: string): SnippetString {
// TODO: use append placeholder
let snippetIndex = 1;
template = template.replace(/^\s*(?=(\/|[ ]\*))/gm, '');
template = template.replace(/^(\/\*\*\s*\*[ ]*)$/m, (x) => x + `\$0`);
template = template.replace(/\* @param([ ]\{\S+\})?\s+(\S+)\s*$/gm, (_param, type, post) => {
let out = '* @param ';
if (type === ' {any}' || type === ' {*}') {
out += `{\$\{${snippetIndex++}:*\}} `;
} else if (type) {
out += type + ' ';
}
out += post + ` \${${snippetIndex++}}`;
return out;
});
return new SnippetString(template);
}
/**
* 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 });
}
}

View File

@@ -0,0 +1,85 @@
/*---------------------------------------------------------------------------------------------
* 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 { Command } from '../utils/commandManager';
import * as typeconverts from '../utils/typeConverters';
import { isSupportedLanguageMode } from '../utils/languageModeIds';
import API from '../utils/api';
import { Lazy } from '../utils/lazy';
import TypeScriptServiceClientHost from '../typeScriptServiceClientHost';
export class OrganizeImportsCommand implements Command {
public static readonly ID = 'typescript.organizeImports';
public readonly id = OrganizeImportsCommand.ID;
constructor(
private readonly lazyClientHost: Lazy<TypeScriptServiceClientHost>
) { }
public async execute(): Promise<boolean> {
// Don't force activation
if (!this.lazyClientHost.hasValue) {
return false;
}
const client = this.lazyClientHost.value.serviceClient;
if (!client.apiVersion.has280Features()) {
return false;
}
const editor = vscode.window.activeTextEditor;
if (!editor || !isSupportedLanguageMode(editor.document)) {
return false;
}
const file = client.normalizePath(editor.document.uri);
if (!file) {
return false;
}
const args: Proto.OrganizeImportsRequestArgs = {
scope: {
type: 'file',
args: {
file
}
}
};
const response = await client.execute('organizeImports', args);
if (!response || !response.success) {
return false;
}
const edits = typeconverts.WorkspaceEdit.fromFromFileCodeEdits(client, response.body);
return await vscode.workspace.applyEdit(edits);
}
}
/**
* When clause context set when the ts version supports organize imports.
*/
const contextName = 'typescript.canOrganizeImports';
export class OrganizeImportsContextManager {
private currentValue: boolean = false;
public onDidChangeApiVersion(apiVersion: API): any {
this.updateContext(apiVersion.has280Features());
}
private updateContext(newValue: boolean) {
if (newValue === this.currentValue) {
return;
}
vscode.commands.executeCommand('setContext', contextName, newValue);
this.currentValue = newValue;
}
}

View File

@@ -0,0 +1,248 @@
/*---------------------------------------------------------------------------------------------
* 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 * as typeConverters from '../utils/typeConverters';
import FormattingConfigurationManager from './formattingConfigurationManager';
import { getEditForCodeAction, applyCodeActionCommands } from '../utils/codeAction';
import { Command, CommandManager } from '../utils/commandManager';
import { DiagnosticsManager } from './diagnostics';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
class ApplyCodeActionCommand implements Command {
public static readonly ID = '_typescript.applyCodeActionCommand';
public readonly id = ApplyCodeActionCommand.ID;
constructor(
private readonly client: ITypeScriptServiceClient
) { }
public async execute(
actions: Proto.CodeAction
): Promise<boolean> {
return applyCodeActionCommands(this.client, actions);
}
}
class ApplyFixAllCodeAction implements Command {
public static readonly ID = '_typescript.applyFixAllCodeAction';
public readonly id = ApplyFixAllCodeAction.ID;
constructor(
private readonly client: ITypeScriptServiceClient
) { }
public async execute(
file: string,
tsAction: Proto.CodeFixAction,
): Promise<void> {
if (!tsAction.fixId) {
return;
}
const args: Proto.GetCombinedCodeFixRequestArgs = {
scope: {
type: 'file',
args: { file }
},
fixId: tsAction.fixId
};
try {
const combinedCodeFixesResponse = await this.client.execute('getCombinedCodeFix', args);
if (!combinedCodeFixesResponse.body) {
return;
}
const edit = typeConverters.WorkspaceEdit.fromFromFileCodeEdits(this.client, combinedCodeFixesResponse.body.changes);
await vscode.workspace.applyEdit(edit);
if (combinedCodeFixesResponse.command) {
await vscode.commands.executeCommand(ApplyCodeActionCommand.ID, combinedCodeFixesResponse.command);
}
} catch {
// noop
}
}
}
/**
* Unique set of diagnostics keyed on diagnostic range and error code.
*/
class DiagnosticsSet {
public static from(diagnostics: vscode.Diagnostic[]) {
const values = new Map<string, vscode.Diagnostic>();
for (const diagnostic of diagnostics) {
values.set(DiagnosticsSet.key(diagnostic), diagnostic);
}
return new DiagnosticsSet(values);
}
private static key(diagnostic: vscode.Diagnostic) {
const { start, end } = diagnostic.range;
return `${diagnostic.code}-${start.line},${start.character}-${end.line},${end.character}`;
}
private constructor(
private readonly _values: Map<string, vscode.Diagnostic>
) { }
public get values(): Iterable<vscode.Diagnostic> {
return this._values.values();
}
}
class SupportedCodeActionProvider {
private _supportedCodeActions?: Thenable<Set<number>>;
public constructor(
private readonly client: ITypeScriptServiceClient
) { }
public async getFixableDiagnosticsForContext(context: vscode.CodeActionContext): Promise<vscode.Diagnostic[]> {
const supportedActions = await this.supportedCodeActions;
const fixableDiagnostics = DiagnosticsSet.from(context.diagnostics.filter(diagnostic => supportedActions.has(+diagnostic.code)));
return Array.from(fixableDiagnostics.values);
}
private get supportedCodeActions(): Thenable<Set<number>> {
if (!this._supportedCodeActions) {
this._supportedCodeActions = this.client.execute('getSupportedCodeFixes', null, undefined)
.then(response => response.body || [])
.then(codes => codes.map(code => +code).filter(code => !isNaN(code)))
.then(codes => new Set(codes));
}
return this._supportedCodeActions;
}
}
export default class TypeScriptQuickFixProvider implements vscode.CodeActionProvider {
private readonly supportedCodeActionProvider: SupportedCodeActionProvider;
constructor(
private readonly client: ITypeScriptServiceClient,
private readonly formattingConfigurationManager: FormattingConfigurationManager,
commandManager: CommandManager,
private readonly diagnosticsManager: DiagnosticsManager
) {
commandManager.register(new ApplyCodeActionCommand(client));
commandManager.register(new ApplyFixAllCodeAction(client));
this.supportedCodeActionProvider = new SupportedCodeActionProvider(client);
}
public async provideCodeActions(
document: vscode.TextDocument,
_range: vscode.Range,
context: vscode.CodeActionContext,
token: vscode.CancellationToken
): Promise<vscode.CodeAction[]> {
if (!this.client.apiVersion.has213Features()) {
return [];
}
const file = this.client.normalizePath(document.uri);
if (!file) {
return [];
}
const fixableDiagnostics = await this.supportedCodeActionProvider.getFixableDiagnosticsForContext(context);
if (!fixableDiagnostics.length) {
return [];
}
await this.formattingConfigurationManager.ensureFormatOptionsForDocument(document, token);
const results: vscode.CodeAction[] = [];
for (const diagnostic of fixableDiagnostics) {
results.push(...await this.getFixesForDiagnostic(document, file, diagnostic, token));
}
return results;
}
private async getFixesForDiagnostic(
document: vscode.TextDocument,
file: string,
diagnostic: vscode.Diagnostic,
token: vscode.CancellationToken
): Promise<Iterable<vscode.CodeAction>> {
const args: Proto.CodeFixRequestArgs = {
...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;
}
return [];
}
private async getAllFixesForTsCodeAction(
document: vscode.TextDocument,
file: string,
diagnostic: vscode.Diagnostic,
tsAction: Proto.CodeFixAction
): Promise<Iterable<vscode.CodeAction>> {
const singleFix = this.getSingleFixForTsCodeAction(diagnostic, tsAction);
const fixAll = await this.getFixAllForTsCodeAction(document, file, diagnostic, tsAction);
return fixAll ? [singleFix, fixAll] : [singleFix];
}
private getSingleFixForTsCodeAction(
diagnostic: vscode.Diagnostic,
tsAction: Proto.CodeFixAction
): vscode.CodeAction {
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
};
}
return codeAction;
}
private async getFixAllForTsCodeAction(
document: vscode.TextDocument,
file: string,
diagnostic: vscode.Diagnostic,
tsAction: Proto.CodeFixAction,
): Promise<vscode.CodeAction | undefined> {
if (!tsAction.fixId || !this.client.apiVersion.has270Features()) {
return undefined;
}
// 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;
}
const action = new vscode.CodeAction(
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;
}
}

View File

@@ -0,0 +1,171 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import * as typeConverters from '../utils/typeConverters';
import FormattingOptionsManager from './formattingConfigurationManager';
import { CommandManager, Command } from '../utils/commandManager';
class ApplyRefactoringCommand implements Command {
public static readonly ID = '_typescript.applyRefactoring';
public readonly id = ApplyRefactoringCommand.ID;
constructor(
private readonly client: ITypeScriptServiceClient,
private readonly formattingOptionsManager: FormattingOptionsManager
) { }
public async execute(
document: vscode.TextDocument,
file: string,
refactor: string,
action: string,
range: vscode.Range
): Promise<boolean> {
await this.formattingOptionsManager.ensureFormatOptionsForDocument(document, undefined);
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) {
return false;
}
const edit = typeConverters.WorkspaceEdit.fromFromFileCodeEdits(this.client, response.body.edits);
if (!(await vscode.workspace.applyEdit(edit))) {
return false;
}
const renameLocation = response.body.renameLocation;
if (renameLocation) {
if (vscode.window.activeTextEditor && vscode.window.activeTextEditor.document.uri.fsPath === document.uri.fsPath) {
const pos = typeConverters.Position.fromLocation(renameLocation);
vscode.window.activeTextEditor.selection = new vscode.Selection(pos, pos);
await vscode.commands.executeCommand('editor.action.rename');
}
}
return true;
}
}
class SelectRefactorCommand implements Command {
public static readonly ID = '_typescript.selectRefactoring';
public readonly id = SelectRefactorCommand.ID;
constructor(
private readonly doRefactoring: ApplyRefactoringCommand
) { }
public async execute(
document: vscode.TextDocument,
file: string,
info: Proto.ApplicableRefactorInfo,
range: vscode.Range
): Promise<boolean> {
const selected = await vscode.window.showQuickPick(info.actions.map((action): vscode.QuickPickItem => ({
label: action.name,
description: action.description
})));
if (!selected) {
return false;
}
return this.doRefactoring.execute(document, file, info.name, selected.label, range);
}
}
export default class TypeScriptRefactorProvider implements vscode.CodeActionProvider {
private static readonly extractFunctionKind = vscode.CodeActionKind.RefactorExtract.append('function');
private static readonly extractConstantKind = vscode.CodeActionKind.RefactorExtract.append('constant');
constructor(
private readonly client: ITypeScriptServiceClient,
formattingOptionsManager: FormattingOptionsManager,
commandManager: CommandManager
) {
const doRefactoringCommand = commandManager.register(new ApplyRefactoringCommand(this.client, formattingOptionsManager));
commandManager.register(new SelectRefactorCommand(doRefactoringCommand));
}
public async provideCodeActions(
document: vscode.TextDocument,
_range: vscode.Range,
context: vscode.CodeActionContext,
token: vscode.CancellationToken
): Promise<vscode.CodeAction[]> {
if (!this.client.apiVersion.has240Features()) {
return [];
}
if (context.only && !vscode.CodeActionKind.Refactor.contains(context.only)) {
return [];
}
if (!vscode.window.activeTextEditor) {
return [];
}
const editor = vscode.window.activeTextEditor;
const file = this.client.normalizePath(document.uri);
if (!file || editor.document.uri.fsPath !== document.uri.fsPath) {
return [];
}
if (editor.selection.isEmpty) {
return [];
}
const range = editor.selection;
const args: Proto.GetApplicableRefactorsRequestArgs = typeConverters.Range.toFileRangeRequestArgs(file, range);
try {
const response = await this.client.execute('getApplicableRefactors', args, token);
if (!response || !response.body) {
return [];
}
const actions: vscode.CodeAction[] = [];
for (const info of response.body) {
if (info.inlineable === false) {
const codeAction = new vscode.CodeAction(info.description, vscode.CodeActionKind.Refactor);
codeAction.command = {
title: info.description,
command: SelectRefactorCommand.ID,
arguments: [document, file, info, range]
};
actions.push(codeAction);
} else {
for (const action of info.actions) {
const codeAction = new vscode.CodeAction(action.description, TypeScriptRefactorProvider.getKind(action));
codeAction.command = {
title: action.description,
command: ApplyRefactoringCommand.ID,
arguments: [document, file, info.name, action.name, range]
};
actions.push(codeAction);
}
}
}
return actions;
} catch {
return [];
}
}
private static getKind(refactor: Proto.RefactorActionInfo) {
if (refactor.name.startsWith('function_')) {
return TypeScriptRefactorProvider.extractFunctionKind;
} else if (refactor.name.startsWith('constant_')) {
return TypeScriptRefactorProvider.extractConstantKind;
}
return vscode.CodeActionKind.Refactor;
}
}

View File

@@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ReferenceProvider, Location, TextDocument, Position, CancellationToken } from 'vscode';
import { ITypeScriptServiceClient } from '../typescriptService';
import * as typeConverters from '../utils/typeConverters';
export default class TypeScriptReferenceSupport implements ReferenceProvider {
public constructor(
private readonly client: ITypeScriptServiceClient) { }
public async provideReferences(
document: TextDocument,
position: Position,
options: { includeDeclaration: boolean },
token: CancellationToken
): Promise<Location[]> {
const filepath = this.client.normalizePath(document.uri);
if (!filepath) {
return [];
}
const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
try {
const msg = await this.client.execute('references', args, token);
if (!msg.body) {
return [];
}
const result: Location[] = [];
const has203Features = this.client.apiVersion.has203Features();
for (const ref of msg.body.refs) {
if (!options.includeDeclaration && has203Features && ref.isDefinition) {
continue;
}
const url = this.client.asUrl(ref.file);
const location = typeConverters.Location.fromTextSpan(url, ref);
result.push(location);
}
return result;
} catch {
return [];
}
}
}

View File

@@ -0,0 +1,110 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CodeLens, CancellationToken, TextDocument, Range, workspace } from 'vscode';
import * as Proto from '../protocol';
import * as PConst from '../protocol.const';
import { TypeScriptBaseCodeLensProvider, ReferencesCodeLens, CachedNavTreeResponse } from './baseCodeLensProvider';
import { ITypeScriptServiceClient } from '../typescriptService';
import * as typeConverters from '../utils/typeConverters';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export default class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLensProvider {
public constructor(
client: ITypeScriptServiceClient,
private readonly language: string,
cachedResponse: CachedNavTreeResponse
) {
super(client, cachedResponse);
}
public updateConfiguration(): void {
const config = workspace.getConfiguration(this.language);
this.setEnabled(config.get('referencesCodeLens.enabled', false));
}
async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
if (!this.client.apiVersion.has206Features()) {
return [];
}
return super.provideCodeLenses(document, token);
}
public resolveCodeLens(inputCodeLens: CodeLens, token: CancellationToken): Promise<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) {
throw codeLens;
}
const locations = response.body.refs
.map(reference =>
typeConverters.Location.fromTextSpan(this.client.asUrl(reference.file), reference))
.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
? localize('oneReferenceLabel', '1 reference')
: localize('manyReferenceLabel', '{0} references', locations.length),
command: locations.length ? 'editor.action.showReferences' : '',
arguments: [codeLens.document, codeLens.range.start, locations]
};
return codeLens;
}).catch(() => {
codeLens.command = {
title: localize('referenceErrorLabel', 'Could not determine references'),
command: ''
};
return codeLens;
});
}
protected extractSymbol(
document: TextDocument,
item: Proto.NavigationTree,
parent: Proto.NavigationTree | null
): Range | null {
if (parent && parent.kind === PConst.Kind.enum) {
return super.getSymbolRange(document, item);
}
switch (item.kind) {
case PConst.Kind.const:
case PConst.Kind.let:
case PConst.Kind.variable:
case PConst.Kind.function:
// Only show references for exported variables
if (!item.kindModifiers.match(/\bexport\b/)) {
break;
}
// fallthrough
case PConst.Kind.class:
if (item.text === '<class>') {
break;
}
// fallthrough
case PConst.Kind.memberFunction:
case PConst.Kind.memberVariable:
case PConst.Kind.memberGetAccessor:
case PConst.Kind.memberSetAccessor:
case PConst.Kind.constructorImplementation:
case PConst.Kind.interface:
case PConst.Kind.type:
case PConst.Kind.enum:
return super.getSymbolRange(document, item);
}
return null;
}
}

View File

@@ -0,0 +1,67 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { RenameProvider, WorkspaceEdit, TextDocument, Position, CancellationToken } from 'vscode';
import * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import * as typeConverters from '../utils/typeConverters';
export default class TypeScriptRenameProvider implements RenameProvider {
public constructor(
private readonly client: ITypeScriptServiceClient
) { }
public async provideRenameEdits(
document: TextDocument,
position: Position,
newName: string,
token: CancellationToken
): Promise<WorkspaceEdit | null> {
const file = this.client.normalizePath(document.uri);
if (!file) {
return null;
}
const args: Proto.RenameRequestArgs = {
...typeConverters.Position.toFileLocationRequestArgs(file, position),
findInStrings: false,
findInComments: false
};
try {
const response = await this.client.execute('rename', args, token);
if (!response.body) {
return null;
}
const renameInfo = response.body.info;
if (!renameInfo.canRename) {
return Promise.reject<WorkspaceEdit>(renameInfo.localizedErrorMessage);
}
return this.toWorkspaceEdit(response.body.locs, newName);
} catch {
// noop
}
return null;
}
private toWorkspaceEdit(
locations: ReadonlyArray<Proto.SpanGroup>,
newName: string
) {
const result = new WorkspaceEdit();
for (const spanGroup of locations) {
const resource = this.client.asUrl(spanGroup.file);
if (resource) {
for (const textSpan of spanGroup.locs) {
result.replace(resource, typeConverters.Range.fromTextSpan(textSpan), newName);
}
}
}
return result;
}
}

View File

@@ -0,0 +1,63 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SignatureHelpProvider, SignatureHelp, SignatureInformation, ParameterInformation, TextDocument, Position, CancellationToken } from 'vscode';
import * as Previewer from '../utils/previewer';
import * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import * as typeConverters from '../utils/typeConverters';
export default class TypeScriptSignatureHelpProvider implements SignatureHelpProvider {
public constructor(
private readonly client: ITypeScriptServiceClient
) { }
public async provideSignatureHelp(document: TextDocument, position: Position, token: CancellationToken): Promise<SignatureHelp | undefined | null> {
const filepath = this.client.normalizePath(document.uri);
if (!filepath) {
return null;
}
const args: Proto.SignatureHelpRequestArgs = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
const response = await this.client.execute('signatureHelp', args, token);
const info = response.body;
if (!info) {
return null;
}
const result = new SignatureHelp();
result.activeSignature = info.selectedItemIndex;
result.activeParameter = info.argumentIndex;
info.items.forEach((item, i) => {
// keep active parameter in bounds
if (i === info.selectedItemIndex && item.isVariadic) {
result.activeParameter = Math.min(info.argumentIndex, item.parameters.length - 1);
}
const signature = new SignatureInformation('');
signature.label += Previewer.plain(item.prefixDisplayParts);
item.parameters.forEach((p, i, a) => {
const parameter = new ParameterInformation(
Previewer.plain(p.displayParts),
Previewer.plain(p.documentation));
signature.label += parameter.label;
signature.parameters.push(parameter);
if (i < a.length - 1) {
signature.label += Previewer.plain(item.separatorDisplayParts);
}
});
signature.label += Previewer.plain(item.suffixDisplayParts);
signature.documentation = Previewer.markdownDocumentation(item.documentation, item.tags);
result.signatures.push(signature);
});
return result;
}
}

View File

@@ -0,0 +1,269 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
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';
const localize = nls.loadMessageBundle();
type AutoDetect = 'on' | 'off' | 'build' | 'watch';
const exists = (file: string): Promise<boolean> =>
new Promise<boolean>((resolve, _reject) => {
fs.exists(file, (value: boolean) => {
resolve(value);
});
});
interface TypeScriptTaskDefinition extends vscode.TaskDefinition {
tsconfig: string;
option?: string;
}
/**
* Provides tasks for building `tsconfig.json` files in a project.
*/
class TscTaskProvider implements vscode.TaskProvider {
private autoDetect: AutoDetect = 'on';
private readonly tsconfigProvider: TsConfigProvider;
private readonly disposables: vscode.Disposable[] = [];
public constructor(
private readonly client: Lazy<ITypeScriptServiceClient>
) {
this.tsconfigProvider = new TsConfigProvider();
vscode.workspace.onDidChangeConfiguration(this.onConfigurationChanged, this, this.disposables);
this.onConfigurationChanged();
}
dispose() {
this.disposables.forEach(x => x.dispose());
}
public async provideTasks(token: vscode.CancellationToken): Promise<vscode.Task[]> {
const folders = vscode.workspace.workspaceFolders;
if (!folders || !folders.length) {
return [];
}
const configPaths: Set<string> = new Set();
const tasks: vscode.Task[] = [];
for (const project of await this.getAllTsConfigs(token)) {
if (!configPaths.has(project.path)) {
configPaths.add(project.path);
tasks.push(...(await this.getTasksForProject(project)));
}
}
return tasks;
}
public resolveTask(_task: vscode.Task): vscode.Task | undefined {
return undefined;
}
private async getAllTsConfigs(token: vscode.CancellationToken): Promise<TSConfig[]> {
const out = new Set<TSConfig>();
const configs = (await this.getTsConfigForActiveFile(token)).concat(await this.getTsConfigsInWorkspace());
for (const config of configs) {
if (await exists(config.path)) {
out.add(config);
}
}
return Array.from(out);
}
private async getTsConfigForActiveFile(token: vscode.CancellationToken): Promise<TSConfig[]> {
const editor = vscode.window.activeTextEditor;
if (editor) {
if (path.basename(editor.document.fileName).match(/^tsconfig\.(.\.)?json$/)) {
const uri = editor.document.uri;
return [{
path: uri.fsPath,
workspaceFolder: vscode.workspace.getWorkspaceFolder(uri)
}];
}
}
const file = this.getActiveTypeScriptFile();
if (!file) {
return [];
}
try {
const res: Proto.ProjectInfoResponse = await this.client.value.execute(
'projectInfo',
{ file, needFileNameList: false },
token);
if (!res || !res.body) {
return [];
}
const { configFileName } = res.body;
if (configFileName && !isImplicitProjectConfigFile(configFileName)) {
const normalizedConfigPath = path.normalize(configFileName);
const uri = vscode.Uri.file(normalizedConfigPath);
const folder = vscode.workspace.getWorkspaceFolder(uri);
return [{
path: normalizedConfigPath,
workspaceFolder: folder
}];
}
} catch (e) {
// noop
}
return [];
}
private async getTsConfigsInWorkspace(): Promise<TSConfig[]> {
return Array.from(await this.tsconfigProvider.getConfigsForWorkspace());
}
private static async getCommand(project: TSConfig): Promise<string> {
if (project.workspaceFolder) {
const localTsc = await TscTaskProvider.getLocalTscAtPath(path.dirname(project.path));
if (localTsc) {
return localTsc;
}
const workspaceTsc = await TscTaskProvider.getLocalTscAtPath(project.workspaceFolder.uri.fsPath);
if (workspaceTsc) {
return workspaceTsc;
}
}
// Use global tsc version
return 'tsc';
}
private static async getLocalTscAtPath(folderPath: string): Promise<string | undefined> {
const platform = process.platform;
const bin = path.join(folderPath, 'node_modules', '.bin');
if (platform === 'win32' && await exists(path.join(bin, 'tsc.cmd'))) {
return path.join(bin, 'tsc.cmd');
} else if ((platform === 'linux' || platform === 'darwin') && await exists(path.join(bin, 'tsc'))) {
return path.join(bin, 'tsc');
}
return undefined;
}
private getActiveTypeScriptFile(): string | null {
const editor = vscode.window.activeTextEditor;
if (editor) {
const document = editor.document;
if (document && (document.languageId === 'typescript' || document.languageId === 'typescriptreact')) {
return this.client.value.normalizePath(document.uri);
}
}
return null;
}
private async getTasksForProject(project: TSConfig): Promise<vscode.Task[]> {
const command = await TscTaskProvider.getCommand(project);
const label = this.getLabelForTasks(project);
const tasks: vscode.Task[] = [];
if (this.autoDetect === 'build' || this.autoDetect === 'on') {
const buildTaskidentifier: TypeScriptTaskDefinition = { type: 'typescript', tsconfig: label };
const buildTask = new vscode.Task(
buildTaskidentifier,
project.workspaceFolder || vscode.TaskScope.Workspace,
localize('buildTscLabel', 'build - {0}', label),
'tsc',
new vscode.ShellExecution(command, ['-p', project.path]),
'$tsc');
buildTask.group = vscode.TaskGroup.Build;
buildTask.isBackground = false;
tasks.push(buildTask);
}
if (this.autoDetect === 'watch' || this.autoDetect === 'on') {
const watchTaskidentifier: TypeScriptTaskDefinition = { type: 'typescript', tsconfig: label, option: 'watch' };
const watchTask = new vscode.Task(
watchTaskidentifier,
project.workspaceFolder || vscode.TaskScope.Workspace,
localize('buildAndWatchTscLabel', 'watch - {0}', label),
'tsc',
new vscode.ShellExecution(command, ['--watch', '-p', project.path]),
'$tsc-watch');
watchTask.group = vscode.TaskGroup.Build;
watchTask.isBackground = true;
tasks.push(watchTask);
}
return tasks;
}
private getLabelForTasks(project: TSConfig): string {
if (project.workspaceFolder) {
const projectFolder = project.workspaceFolder;
const workspaceFolders = vscode.workspace.workspaceFolders;
const relativePath = path.relative(project.workspaceFolder.uri.fsPath, project.path);
if (workspaceFolders && workspaceFolders.length > 1) {
// Use absolute path when we have multiple folders with the same name
if (workspaceFolders.filter(x => x.name === projectFolder.name).length > 1) {
return path.join(project.workspaceFolder.uri.fsPath, relativePath);
} else {
return path.join(project.workspaceFolder.name, relativePath);
}
} else {
return relativePath;
}
}
return project.path;
}
private onConfigurationChanged(): void {
const type = vscode.workspace.getConfiguration('typescript.tsc').get<AutoDetect>('autoDetect');
this.autoDetect = typeof type === 'undefined' ? 'on' : type;
}
}
/**
* Manages registrations of TypeScript task providers with VS Code.
*/
export default class TypeScriptTaskProviderManager {
private taskProviderSub: vscode.Disposable | undefined = undefined;
private readonly disposables: vscode.Disposable[] = [];
constructor(
private readonly client: Lazy<ITypeScriptServiceClient>
) {
vscode.workspace.onDidChangeConfiguration(this.onConfigurationChanged, this, this.disposables);
this.onConfigurationChanged();
}
dispose() {
if (this.taskProviderSub) {
this.taskProviderSub.dispose();
this.taskProviderSub = undefined;
}
this.disposables.forEach(x => x.dispose());
}
private onConfigurationChanged() {
const autoDetect = vscode.workspace.getConfiguration('typescript.tsc').get<AutoDetect>('autoDetect');
if (this.taskProviderSub && autoDetect === 'off') {
this.taskProviderSub.dispose();
this.taskProviderSub = undefined;
} else if (!this.taskProviderSub && autoDetect !== 'off') {
this.taskProviderSub = vscode.workspace.registerTaskProvider('typescript', new TscTaskProvider(this.client));
}
}
}

View File

@@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TypeDefinitionProvider, TextDocument, Position, CancellationToken, Definition } from 'vscode';
import DefinitionProviderBase from './definitionProviderBase';
export default class TypeScriptTypeDefinitionProvider extends DefinitionProviderBase implements TypeDefinitionProvider {
public provideTypeDefinition(document: TextDocument, position: Position, token: CancellationToken | boolean): Promise<Definition | undefined> {
return this.getSymbolLocations('typeDefinition', document, position, token);
}
}

View File

@@ -0,0 +1,94 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { workspace, window, Uri, WorkspaceSymbolProvider, SymbolInformation, SymbolKind, CancellationToken } from 'vscode';
import * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import * as typeConverters from '../utils/typeConverters';
function getSymbolKind(item: Proto.NavtoItem): SymbolKind {
switch (item.kind) {
case 'method': return SymbolKind.Method;
case 'enum': return SymbolKind.Enum;
case 'function': return SymbolKind.Function;
case 'class': return SymbolKind.Class;
case 'interface': return SymbolKind.Interface;
case 'var': return SymbolKind.Variable;
default: return SymbolKind.Variable;
}
}
export default class TypeScriptWorkspaceSymbolProvider implements WorkspaceSymbolProvider {
public constructor(
private readonly client: ITypeScriptServiceClient,
private readonly modeIds: string[]
) { }
public async provideWorkspaceSymbols(
search: string,
token: CancellationToken
): Promise<SymbolInformation[]> {
const uri = this.getUri();
if (!uri) {
return [];
}
const filepath = this.client.normalizePath(uri);
if (!filepath) {
return [];
}
const args: Proto.NavtoRequestArgs = {
file: filepath,
searchValue: search
};
const response = await this.client.execute('navto', args, token);
if (!response.body) {
return [];
}
const result: SymbolInformation[] = [];
for (const item of response.body) {
if (!item.containerName && item.kind === 'alias') {
continue;
}
const label = TypeScriptWorkspaceSymbolProvider.getLabel(item);
result.push(new SymbolInformation(label, getSymbolKind(item), item.containerName || '',
typeConverters.Location.fromTextSpan(this.client.asUrl(item.file), item)));
}
return result;
}
private static getLabel(item: Proto.NavtoItem) {
let label = item.name;
if (item.kind === 'method' || item.kind === 'function') {
label += '()';
}
return label;
}
private getUri(): Uri | undefined {
// typescript wants to have a resource even when asking
// general questions so we check the active editor. If this
// doesn't match we take the first TS document.
const editor = window.activeTextEditor;
if (editor) {
const document = editor.document;
if (document && this.modeIds.indexOf(document.languageId) >= 0) {
return document.uri;
}
}
const documents = workspace.textDocuments;
for (const document of documents) {
if (this.modeIds.indexOf(document.languageId) >= 0) {
return document.uri;
}
}
return undefined;
}
}

View File

@@ -0,0 +1,243 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { languages, workspace, Diagnostic, Disposable, Uri, TextDocument, DocumentFilter } from 'vscode';
import { basename } from 'path';
import TypeScriptServiceClient from './typescriptServiceClient';
import BufferSyncSupport from './features/bufferSyncSupport';
import TypingsStatus from './utils/typingsStatus';
import FormattingConfigurationManager from './features/formattingConfigurationManager';
import * as languageConfigurations from './utils/languageConfigurations';
import { CommandManager } from './utils/commandManager';
import { DiagnosticsManager, DiagnosticKind } from './features/diagnostics';
import { LanguageDescription } from './utils/languageDescription';
import * as fileSchemes from './utils/fileSchemes';
import { CachedNavTreeResponse } from './features/baseCodeLensProvider';
import { memoize } from './utils/memoize';
import { disposeAll } from './utils/dipose';
const validateSetting = 'validate.enable';
const foldingSetting = 'typescript.experimental.syntaxFolding';
export default class LanguageProvider {
private readonly diagnosticsManager: DiagnosticsManager;
private readonly bufferSyncSupport: BufferSyncSupport;
private readonly formattingOptionsManager: FormattingConfigurationManager;
private readonly toUpdateOnConfigurationChanged: ({ updateConfiguration: () => void })[] = [];
private _validate: boolean = true;
private readonly disposables: Disposable[] = [];
private readonly versionDependentDisposables: Disposable[] = [];
private foldingProviderRegistration: Disposable | undefined = void 0;
constructor(
private readonly client: TypeScriptServiceClient,
private readonly description: LanguageDescription,
commandManager: CommandManager,
typingsStatus: TypingsStatus
) {
this.formattingOptionsManager = new FormattingConfigurationManager(client);
this.bufferSyncSupport = new BufferSyncSupport(client, description.modeIds, {
delete: (resource) => {
this.diagnosticsManager.delete(resource);
}
}, this._validate);
this.diagnosticsManager = new DiagnosticsManager(description.id);
workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables);
this.configurationChanged();
client.onReady(async () => {
await this.registerProviders(client, commandManager, typingsStatus);
this.bufferSyncSupport.listen();
});
}
public dispose(): void {
disposeAll(this.disposables);
disposeAll(this.versionDependentDisposables);
this.diagnosticsManager.dispose();
this.bufferSyncSupport.dispose();
this.formattingOptionsManager.dispose();
}
@memoize
private get documentSelector(): DocumentFilter[] {
const documentSelector = [];
for (const language of this.description.modeIds) {
for (const scheme of fileSchemes.supportedSchemes) {
documentSelector.push({ language, scheme });
}
}
return documentSelector;
}
private async registerProviders(
client: TypeScriptServiceClient,
commandManager: CommandManager,
typingsStatus: TypingsStatus
): Promise<void> {
const selector = this.documentSelector;
const config = workspace.getConfiguration(this.id);
this.disposables.push(languages.registerCompletionItemProvider(selector,
new (await import('./features/completionItemProvider')).default(client, typingsStatus, commandManager),
'.', '"', '\'', '/', '@'));
this.disposables.push(languages.registerCompletionItemProvider(selector, new (await import('./features/directiveCommentCompletionProvider')).default(client), '@'));
const { TypeScriptFormattingProvider, FormattingProviderManager } = await import('./features/formattingProvider');
const formattingProvider = new TypeScriptFormattingProvider(client, this.formattingOptionsManager);
formattingProvider.updateConfiguration(config);
this.disposables.push(languages.registerOnTypeFormattingEditProvider(selector, formattingProvider, ';', '}', '\n'));
const formattingProviderManager = new FormattingProviderManager(this.description.id, formattingProvider, selector);
formattingProviderManager.updateConfiguration();
this.disposables.push(formattingProviderManager);
this.toUpdateOnConfigurationChanged.push(formattingProviderManager);
const cachedResponse = new CachedNavTreeResponse();
this.disposables.push(languages.registerCompletionItemProvider(selector, new (await import('./features/jsDocCompletionProvider')).default(client, commandManager), '*'));
this.disposables.push(languages.registerHoverProvider(selector, new (await import('./features/hoverProvider')).default(client)));
this.disposables.push(languages.registerDefinitionProvider(selector, new (await import('./features/definitionProvider')).default(client)));
this.disposables.push(languages.registerDocumentHighlightProvider(selector, new (await import('./features/documentHighlightProvider')).default(client)));
this.disposables.push(languages.registerReferenceProvider(selector, new (await import('./features/referenceProvider')).default(client)));
this.disposables.push(languages.registerDocumentSymbolProvider(selector, new (await import('./features/documentSymbolProvider')).default(client)));
this.disposables.push(languages.registerSignatureHelpProvider(selector, new (await import('./features/signatureHelpProvider')).default(client), '(', ','));
this.disposables.push(languages.registerRenameProvider(selector, new (await import('./features/renameProvider')).default(client)));
this.disposables.push(languages.registerCodeActionsProvider(selector, new (await import('./features/quickFixProvider')).default(client, this.formattingOptionsManager, commandManager, this.diagnosticsManager)));
this.disposables.push(languages.registerCodeActionsProvider(selector, new (await import('./features/refactorProvider')).default(client, this.formattingOptionsManager, commandManager)));
await this.initFoldingProvider();
this.disposables.push(workspace.onDidChangeConfiguration(c => {
if (c.affectsConfiguration(foldingSetting)) {
this.initFoldingProvider();
}
}));
this.disposables.push({ dispose: () => this.foldingProviderRegistration && this.foldingProviderRegistration.dispose() });
this.registerVersionDependentProviders();
const referenceCodeLensProvider = new (await import('./features/referencesCodeLensProvider')).default(client, this.description.id, cachedResponse);
referenceCodeLensProvider.updateConfiguration();
this.toUpdateOnConfigurationChanged.push(referenceCodeLensProvider);
this.disposables.push(languages.registerCodeLensProvider(selector, referenceCodeLensProvider));
const implementationCodeLensProvider = new (await import('./features/implementationsCodeLensProvider')).default(client, this.description.id, cachedResponse);
implementationCodeLensProvider.updateConfiguration();
this.toUpdateOnConfigurationChanged.push(implementationCodeLensProvider);
this.disposables.push(languages.registerCodeLensProvider(selector, implementationCodeLensProvider));
this.disposables.push(languages.registerWorkspaceSymbolProvider(new (await import('./features/workspaceSymbolProvider')).default(client, this.description.modeIds)));
if (!this.description.isExternal) {
for (const modeId of this.description.modeIds) {
this.disposables.push(languages.setLanguageConfiguration(modeId, languageConfigurations.jsTsLanguageConfiguration));
}
}
}
private async initFoldingProvider(): Promise<void> {
let enable = workspace.getConfiguration().get(foldingSetting, false);
if (enable) {
if (!this.foldingProviderRegistration) {
this.foldingProviderRegistration = languages.registerFoldingProvider(this.documentSelector, new (await import('./features/folderingProvider')).default(this.client));
}
} else {
if (this.foldingProviderRegistration) {
this.foldingProviderRegistration.dispose();
this.foldingProviderRegistration = void 0;
}
}
}
private configurationChanged(): void {
const config = workspace.getConfiguration(this.id);
this.updateValidate(config.get(validateSetting, true));
for (const toUpdate of this.toUpdateOnConfigurationChanged) {
toUpdate.updateConfiguration();
}
}
public handles(resource: Uri, doc: TextDocument): boolean {
if (doc && this.description.modeIds.indexOf(doc.languageId) >= 0) {
return true;
}
if (this.bufferSyncSupport.handles(resource)) {
return true;
}
const base = basename(resource.fsPath);
return !!base && base === this.description.configFile;
}
private get id(): string {
return this.description.id;
}
public get diagnosticSource(): string {
return this.description.diagnosticSource;
}
private updateValidate(value: boolean) {
if (this._validate === value) {
return;
}
this._validate = value;
this.bufferSyncSupport.validate = value;
this.diagnosticsManager.validate = value;
if (value) {
this.triggerAllDiagnostics();
}
}
public reInitialize(): void {
this.diagnosticsManager.reInitialize();
this.bufferSyncSupport.reOpenDocuments();
this.bufferSyncSupport.requestAllDiagnostics();
this.formattingOptionsManager.reset();
this.registerVersionDependentProviders();
}
private async registerVersionDependentProviders(): Promise<void> {
disposeAll(this.versionDependentDisposables);
if (!this.client) {
return;
}
const selector = this.documentSelector;
if (this.client.apiVersion.has220Features()) {
this.versionDependentDisposables.push(languages.registerImplementationProvider(selector, new (await import('./features/implementationProvider')).default(this.client)));
}
if (this.client.apiVersion.has213Features()) {
this.versionDependentDisposables.push(languages.registerTypeDefinitionProvider(selector, new (await import('./features/typeDefinitionProvider')).default(this.client)));
}
}
public triggerAllDiagnostics(): void {
this.bufferSyncSupport.requestAllDiagnostics();
}
public diagnosticsReceived(diagnosticsKind: DiagnosticKind, file: Uri, syntaxDiagnostics: Diagnostic[]): void {
this.diagnosticsManager.diagnosticsReceived(diagnosticsKind, file, syntaxDiagnostics);
}
public configFileDiagnosticsReceived(file: Uri, diagnostics: Diagnostic[]): void {
this.diagnosticsManager.configFileDiagnosticsReceived(file, diagnostics);
}
}

View File

@@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export class Kind {
public static readonly alias = 'alias';
public static readonly callSignature = 'call';
public static readonly class = 'class';
public static readonly const = 'const';
public static readonly constructorImplementation = 'constructor';
public static readonly constructSignature = 'construct';
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';
public static readonly keyword = 'keyword';
public static readonly let = 'let';
public static readonly localFunction = 'local function';
public static readonly localVariable = 'local var';
public static readonly memberFunction = 'method';
public static readonly memberGetAccessor = 'getter';
public static readonly memberSetAccessor = 'setter';
public static readonly memberVariable = 'property';
public static readonly module = 'module';
public static readonly primitiveType = 'primitive type';
public static readonly script = 'script';
public static readonly type = 'type';
public static readonly variable = 'var';
public static readonly warning = 'warning';
}
export class DiagnosticCategory {
public static readonly error = 'error';
public static readonly warning = 'warning';
public static readonly suggestion = 'suggestion';
}

View File

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

View File

@@ -0,0 +1,280 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* --------------------------------------------------------------------------------------------
* Includes code from typescript-sublime-plugin project, obtained from
* https://github.com/Microsoft/TypeScript-Sublime-Plugin/blob/master/TypeScript%20Indent.tmPreferences
* ------------------------------------------------------------------------------------------ */
import { workspace, Memento, Diagnostic, Range, Disposable, Uri, DiagnosticSeverity } from 'vscode';
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 { 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/dipose';
import { DiagnosticKind } from './features/diagnostics';
// Style check diagnostics that can be reported as warnings
const styleCheckDiagnostics = [
6133, // variable is declared but never used
6138, // property is declared but its value is never read
7027, // unreachable code detected
7028, // unused label
7029, // fall through case in switch
7030 // not all code paths return a value
];
export default class TypeScriptServiceClientHost {
private readonly ataProgressReporter: AtaProgressReporter;
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 reportStyleCheckAsWarnings: boolean = true;
constructor(
descriptions: LanguageDescription[],
workspaceState: Memento,
plugins: TypeScriptServerPlugin[],
private readonly commandManager: CommandManager,
logDirectoryProvider: LogDirectoryProvider
) {
const handleProjectCreateOrDelete = () => {
this.client.execute('reloadProjects', null, false);
this.triggerAllDiagnostics();
};
const handleProjectChange = () => {
setTimeout(() => {
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);
this.client = new TypeScriptServiceClient(workspaceState, version => this.versionStatus.onDidChangeTypeScriptVersion(version), plugins, logDirectoryProvider);
this.disposables.push(this.client);
this.client.onDiagnosticsReceived(({ kind, resource, diagnostics }) => {
this.diagnosticsReceived(kind, resource, diagnostics);
}, 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.normalizePath(resource));
this.disposables.push(this.versionStatus);
this.typingsStatus = new TypingsStatus(this.client);
this.ataProgressReporter = new AtaProgressReporter(this.client);
for (const description of descriptions) {
const manager = new LanguageProvider(this.client, description, this.commandManager, this.typingsStatus);
this.languages.push(manager);
this.disposables.push(manager);
this.languagePerId.set(description.id, manager);
}
this.client.ensureServiceStarted();
this.client.onReady(() => {
if (!this.client.apiVersion.has230Features()) {
return;
}
const languages = new Set<string>();
for (const plugin of plugins) {
for (const language of plugin.languages) {
languages.add(language);
}
}
if (languages.size) {
const description: LanguageDescription = {
id: 'typescript-plugins',
modeIds: Array.from(languages.values()),
diagnosticSource: 'ts-plugins',
isExternal: true
};
const manager = new LanguageProvider(this.client, description, this.commandManager, this.typingsStatus);
this.languages.push(manager);
this.disposables.push(manager);
this.languagePerId.set(description.id, manager);
}
});
this.client.onTsServerStarted(() => {
this.triggerAllDiagnostics();
});
workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables);
this.configurationChanged();
}
public dispose(): void {
disposeAll(this.disposables);
this.typingsStatus.dispose();
this.ataProgressReporter.dispose();
}
public get serviceClient(): TypeScriptServiceClient {
return this.client;
}
public reloadProjects(): void {
this.client.execute('reloadProjects', null, false);
this.triggerAllDiagnostics();
}
public handles(resource: Uri): boolean {
return !!this.findLanguage(resource);
}
private configurationChanged(): void {
const config = workspace.getConfiguration('typescript');
this.reportStyleCheckAsWarnings = config.get('reportStyleChecksAsWarnings', true);
}
private async findLanguage(resource: Uri): Promise<LanguageProvider | undefined> {
try {
const doc = await workspace.openTextDocument(resource);
return this.languages.find(language => language.handles(resource, doc));
} catch {
return undefined;
}
}
private triggerAllDiagnostics() {
for (const language of this.languagePerId.values()) {
language.triggerAllDiagnostics();
}
}
private populateService(): void {
// See https://github.com/Microsoft/TypeScript/issues/5530
workspace.saveAll(false).then(() => {
for (const language of this.languagePerId.values()) {
language.reInitialize();
}
});
}
private async diagnosticsReceived(
kind: DiagnosticKind,
resource: Uri,
diagnostics: Proto.Diagnostic[]
): Promise<void> {
const language = await this.findLanguage(resource);
if (language) {
language.diagnosticsReceived(
kind,
resource,
this.createMarkerDatas(diagnostics, language.diagnosticSource));
}
}
private configFileDiagnosticsReceived(event: Proto.ConfigFileDiagnosticEvent): void {
// See https://github.com/Microsoft/TypeScript/issues/10384
const body = event.body;
if (!body || !body.diagnostics || !body.configFile) {
return;
}
(this.findLanguage(this.client.asUrl(body.configFile))).then(language => {
if (!language) {
return;
}
if (body.diagnostics.length === 0) {
language.configFileDiagnosticsReceived(this.client.asUrl(body.configFile), []);
} else if (body.diagnostics.length >= 1) {
workspace.openTextDocument(Uri.file(body.configFile)).then((document) => {
let curly: [number, number, number] | undefined = undefined;
let nonCurly: [number, number, number] | undefined = undefined;
let diagnostic: Diagnostic;
for (let index = 0; index < document.lineCount; index++) {
const line = document.lineAt(index);
const text = line.text;
const firstNonWhitespaceCharacterIndex = line.firstNonWhitespaceCharacterIndex;
if (firstNonWhitespaceCharacterIndex < text.length) {
if (text.charAt(firstNonWhitespaceCharacterIndex) === '{') {
curly = [index, firstNonWhitespaceCharacterIndex, firstNonWhitespaceCharacterIndex + 1];
break;
} else {
const matches = /\s*([^\s]*)(?:\s*|$)/.exec(text.substr(firstNonWhitespaceCharacterIndex));
if (matches && matches.length >= 1) {
nonCurly = [index, firstNonWhitespaceCharacterIndex, firstNonWhitespaceCharacterIndex + matches[1].length];
}
}
}
}
const match = curly || nonCurly;
if (match) {
diagnostic = new Diagnostic(new 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);
}
if (diagnostic) {
diagnostic.source = language.diagnosticSource;
language.configFileDiagnosticsReceived(this.client.asUrl(body.configFile), [diagnostic]);
}
}, _error => {
language.configFileDiagnosticsReceived(this.client.asUrl(body.configFile), [new Diagnostic(new Range(0, 0, 0, 0), body.diagnostics[0].text)]);
});
}
});
}
private createMarkerDatas(diagnostics: Proto.Diagnostic[], source: string): Diagnostic[] {
return diagnostics.map(tsDiag => this.tsDiagnosticToVsDiagnostic(tsDiag, source));
}
private tsDiagnosticToVsDiagnostic(diagnostic: Proto.Diagnostic, source: string) {
const { start, end, text } = diagnostic;
const range = new Range(typeConverters.Position.fromLocation(start), typeConverters.Position.fromLocation(end));
const converted = new Diagnostic(range, text);
converted.severity = this.getDiagnosticSeverity(diagnostic);
converted.source = diagnostic.source || source;
if (diagnostic.code) {
converted.code = diagnostic.code;
}
return converted;
}
private getDiagnosticSeverity(diagnostic: Proto.Diagnostic): DiagnosticSeverity {
if (this.reportStyleCheckAsWarnings && this.isStyleCheckDiagnostic(diagnostic.code)) {
return DiagnosticSeverity.Warning;
}
switch (diagnostic.category) {
case PConst.DiagnosticCategory.error:
return DiagnosticSeverity.Error;
case PConst.DiagnosticCategory.warning:
return DiagnosticSeverity.Warning;
case PConst.DiagnosticCategory.suggestion:
return DiagnosticSeverity.Hint;
default:
return DiagnosticSeverity.Error;
}
}
private isStyleCheckDiagnostic(code: number | undefined): boolean {
return code ? styleCheckDiagnostics.indexOf(code) !== -1 : false;
}
}

View File

@@ -0,0 +1,63 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken, Uri, Event } from 'vscode';
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';
export interface ITypeScriptServiceClient {
normalizePath(resource: Uri): string | null;
asUrl(filepath: string): Uri;
getWorkspaceRootForResource(resource: Uri): string | undefined;
onTsServerStarted: Event<API>;
onProjectLanguageServiceStateChanged: Event<Proto.ProjectLanguageServiceStateEventBody>;
onDidBeginInstallTypings: Event<Proto.BeginInstallTypesEventBody>;
onDidEndInstallTypings: Event<Proto.EndInstallTypesEventBody>;
onTypesInstallerInitializationFailed: Event<Proto.TypesInstallerInitializationFailedEventBody>;
apiVersion: API;
plugins: TypeScriptServerPlugin[];
configuration: TypeScriptServiceConfiguration;
logger: Logger;
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: 'geterr', args: Proto.GeterrRequestArgs, 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: string, args: any, expectedResult: boolean | CancellationToken, token?: CancellationToken): Promise<any>;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
interface ObjectMap<V> {
[key: string]: V;
}

View File

@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../../src/vs/vscode.proposed.d.ts'/>
/// <reference types='@types/node'/>

View File

@@ -0,0 +1,104 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as semver from 'semver';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { memoize } from './memoize';
export default class API {
public static readonly defaultVersion = new API('1.0.0', '1.0.0');
public static fromVersionString(versionString: string): API {
let version = semver.valid(versionString);
if (!version) {
return new API(localize('invalidVersion', 'invalid version'), '1.0.0');
}
// Cut off any prerelease tag since we sometimes consume those on purpose.
const index = versionString.indexOf('-');
if (index >= 0) {
version = version.substr(0, index);
}
return new API(versionString, version);
}
private constructor(
public readonly versionString: string,
private readonly version: string
) { }
@memoize
public has203Features(): boolean {
return semver.gte(this.version, '2.0.3');
}
@memoize
public has206Features(): boolean {
return semver.gte(this.version, '2.0.6');
}
@memoize
public has208Features(): boolean {
return semver.gte(this.version, '2.0.8');
}
@memoize
public has213Features(): boolean {
return semver.gte(this.version, '2.1.3');
}
@memoize
public has220Features(): boolean {
return semver.gte(this.version, '2.2.0');
}
@memoize
public has222Features(): boolean {
return semver.gte(this.version, '2.2.2');
}
@memoize
public has230Features(): boolean {
return semver.gte(this.version, '2.3.0');
}
@memoize
public has234Features(): boolean {
return semver.gte(this.version, '2.3.4');
}
@memoize
public has240Features(): boolean {
return semver.gte(this.version, '2.4.0');
}
@memoize
public has250Features(): boolean {
return semver.gte(this.version, '2.5.0');
}
@memoize
public has260Features(): boolean {
return semver.gte(this.version, '2.6.0');
}
@memoize
public has262Features(): boolean {
return semver.gte(this.version, '2.6.2');
}
@memoize
public has270Features(): boolean {
return semver.gte(this.version, '2.7.0');
}
@memoize
public has280Features(): boolean {
return semver.gte(this.version, '2.8.0');
}
}

View File

@@ -0,0 +1,17 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function equals<T>(one: T[], other: T[], itemEquals: (a: T, b: T) => boolean = (a, b) => a === b): boolean {
if (one.length !== other.length) {
return false;
}
for (let i = 0, len = one.length; i < len; i++) {
if (!itemEquals(one[i], other[i])) {
return false;
}
}
return true;
}

View File

@@ -0,0 +1,62 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface ITask<T> {
(): T;
}
export class Delayer<T> {
public defaultDelay: number;
private timeout: any; // Timer
private completionPromise: Promise<T | null> | null;
private onSuccess: ((value?: T | Thenable<T>) => void) | null;
private task: ITask<T> | null;
constructor(defaultDelay: number) {
this.defaultDelay = defaultDelay;
this.timeout = null;
this.completionPromise = null;
this.onSuccess = null;
this.task = null;
}
public trigger(task: ITask<T>, delay: number = this.defaultDelay): Promise<T | null> {
this.task = task;
if (delay >= 0) {
this.cancelTimeout();
}
if (!this.completionPromise) {
this.completionPromise = new Promise<T>((resolve) => {
this.onSuccess = resolve;
}).then(() => {
this.completionPromise = null;
this.onSuccess = null;
var result = this.task && this.task();
this.task = null;
return result;
});
}
if (delay >= 0 || this.timeout === null) {
this.timeout = setTimeout(() => {
this.timeout = null;
if (this.onSuccess) {
this.onSuccess(undefined);
}
}, delay >= 0 ? delay : this.defaultDelay);
}
return this.completionPromise;
}
private cancelTimeout(): void {
if (this.timeout !== null) {
clearTimeout(this.timeout);
this.timeout = null;
}
}
}

View File

@@ -0,0 +1,46 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { WorkspaceEdit, workspace } from 'vscode';
import * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import * as typeConverters from './typeConverters';
export function getEditForCodeAction(
client: ITypeScriptServiceClient,
action: Proto.CodeAction
): WorkspaceEdit | undefined {
return action.changes && action.changes.length
? typeConverters.WorkspaceEdit.fromFromFileCodeEdits(client, action.changes)
: undefined;
}
export async function applyCodeAction(
client: ITypeScriptServiceClient,
action: Proto.CodeAction
): Promise<boolean> {
const workspaceEdit = getEditForCodeAction(client, action);
if (workspaceEdit) {
if (!(await workspace.applyEdit(workspaceEdit))) {
return false;
}
}
return applyCodeActionCommands(client, action);
}
export async function applyCodeActionCommands(
client: ITypeScriptServiceClient,
action: Proto.CodeAction
): 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;
}
}
}
return true;
}

View File

@@ -0,0 +1,36 @@
/*---------------------------------------------------------------------------------------------
* 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';
export interface Command {
readonly id: string;
execute(...args: any[]): void;
}
export class CommandManager {
private readonly commands = new Map<string, vscode.Disposable>();
public dispose() {
for (const registration of this.commands.values()) {
registration.dispose();
}
this.commands.clear();
}
public register<T extends Command>(command: T): T {
this.registerCommand(command.id, command.execute, command);
return command;
}
private registerCommand(id: string, impl: (...args: any[]) => void, thisArg?: any) {
if (this.commands.has(id)) {
return;
}
this.commands.set(id, vscode.commands.registerCommand(id, impl, thisArg));
}
}

View File

@@ -0,0 +1,130 @@
/*---------------------------------------------------------------------------------------------
* 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 arrays from './arrays';
export enum TsServerLogLevel {
Off,
Normal,
Terse,
Verbose,
}
export namespace TsServerLogLevel {
export function fromString(value: string): TsServerLogLevel {
switch (value && value.toLowerCase()) {
case 'normal':
return TsServerLogLevel.Normal;
case 'terse':
return TsServerLogLevel.Terse;
case 'verbose':
return TsServerLogLevel.Verbose;
case 'off':
default:
return TsServerLogLevel.Off;
}
}
export function toString(value: TsServerLogLevel): string {
switch (value) {
case TsServerLogLevel.Normal:
return 'normal';
case TsServerLogLevel.Terse:
return 'terse';
case TsServerLogLevel.Verbose:
return 'verbose';
case TsServerLogLevel.Off:
default:
return 'off';
}
}
}
export class TypeScriptServiceConfiguration {
public readonly locale: string | null;
public readonly globalTsdk: string | null;
public readonly localTsdk: string | null;
public readonly npmLocation: string | null;
public readonly tsServerLogLevel: TsServerLogLevel = TsServerLogLevel.Off;
public readonly tsServerPluginPaths: string[];
public readonly checkJs: boolean;
public readonly experimentalDecorators: boolean;
public readonly disableAutomaticTypeAcquisition: boolean;
public static loadFromWorkspace(): TypeScriptServiceConfiguration {
return new TypeScriptServiceConfiguration();
}
private constructor() {
const configuration = workspace.getConfiguration();
this.locale = TypeScriptServiceConfiguration.extractLocale(configuration);
this.globalTsdk = TypeScriptServiceConfiguration.extractGlobalTsdk(configuration);
this.localTsdk = TypeScriptServiceConfiguration.extractLocalTsdk(configuration);
this.npmLocation = TypeScriptServiceConfiguration.readNpmLocation(configuration);
this.tsServerLogLevel = TypeScriptServiceConfiguration.readTsServerLogLevel(configuration);
this.tsServerPluginPaths = TypeScriptServiceConfiguration.readTsServerPluginPaths(configuration);
this.checkJs = TypeScriptServiceConfiguration.readCheckJs(configuration);
this.experimentalDecorators = TypeScriptServiceConfiguration.readExperimentalDecorators(configuration);
this.disableAutomaticTypeAcquisition = TypeScriptServiceConfiguration.readDisableAutomaticTypeAcquisition(configuration);
}
public isEqualTo(other: TypeScriptServiceConfiguration): boolean {
return this.locale === other.locale
&& this.globalTsdk === other.globalTsdk
&& this.localTsdk === other.localTsdk
&& this.npmLocation === other.npmLocation
&& this.tsServerLogLevel === other.tsServerLogLevel
&& this.checkJs === other.checkJs
&& this.experimentalDecorators === other.experimentalDecorators
&& this.disableAutomaticTypeAcquisition === other.disableAutomaticTypeAcquisition
&& arrays.equals(this.tsServerPluginPaths, other.tsServerPluginPaths);
}
private static extractGlobalTsdk(configuration: WorkspaceConfiguration): string | null {
const inspect = configuration.inspect('typescript.tsdk');
if (inspect && inspect.globalValue && 'string' === typeof inspect.globalValue) {
return inspect.globalValue;
}
return null;
}
private static extractLocalTsdk(configuration: WorkspaceConfiguration): string | null {
const inspect = configuration.inspect('typescript.tsdk');
if (inspect && inspect.workspaceValue && 'string' === typeof inspect.workspaceValue) {
return inspect.workspaceValue;
}
return null;
}
private static readTsServerLogLevel(configuration: WorkspaceConfiguration): TsServerLogLevel {
const setting = configuration.get<string>('typescript.tsserver.log', 'off');
return TsServerLogLevel.fromString(setting);
}
private static readTsServerPluginPaths(configuration: WorkspaceConfiguration): string[] {
return configuration.get<string[]>('typescript.tsserver.pluginPaths', []);
}
private static readCheckJs(configuration: WorkspaceConfiguration): boolean {
return configuration.get<boolean>('javascript.implicitProjectConfig.checkJs', false);
}
private static readExperimentalDecorators(configuration: WorkspaceConfiguration): boolean {
return configuration.get<boolean>('javascript.implicitProjectConfig.experimentalDecorators', false);
}
private static readNpmLocation(configuration: WorkspaceConfiguration): string | null {
return configuration.get<string | null>('typescript.npm', null);
}
private static readDisableAutomaticTypeAcquisition(configuration: WorkspaceConfiguration): boolean {
return configuration.get<boolean>('typescript.disableAutomaticTypeAcquisition', false);
}
private static extractLocale(configuration: WorkspaceConfiguration): string | null {
return configuration.get<string | null>('typescript.locale', null);
}
}

View File

@@ -0,0 +1,15 @@
/*---------------------------------------------------------------------------------------------
* 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';
export function disposeAll(disposables: vscode.Disposable[]) {
while (disposables.length) {
const item = disposables.pop();
if (item) {
item.dispose();
}
}
}

View File

@@ -0,0 +1,153 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import path = require('path');
import os = require('os');
import net = require('net');
import cp = require('child_process');
import Logger from './logger';
export interface IForkOptions {
cwd?: string;
execArgv?: string[];
}
export function makeRandomHexString(length: number): string {
let chars = ['0', '1', '2', '3', '4', '5', '6', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
let result = '';
for (let i = 0; i < length; i++) {
const idx = Math.floor(chars.length * Math.random());
result += chars[idx];
}
return result;
}
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');
}
export function getTempFile(name: string): string {
const fullName = 'vscode-' + name;
return path.join(os.tmpdir(), fullName + '.sock');
}
function generatePatchedEnv(
env: any,
stdInPipeName: string,
stdOutPipeName: string,
stdErrPipeName: string
): 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
newEnv['PATH'] = newEnv['PATH'] || process.env.PATH;
return newEnv;
}
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);
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), {
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

@@ -0,0 +1,198 @@
/*---------------------------------------------------------------------------------------------
* 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

@@ -0,0 +1,20 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export const file = 'file';
export const untitled = 'untitled';
export const walkThroughSnippet = 'walkThroughSnippet';
export const supportedSchemes = [
file,
untitled,
walkThroughSnippet
];
export function isSupportedScheme(scheme: string): boolean {
return supportedSchemes.indexOf(scheme) >= 0;
}

View File

@@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const toString = Object.prototype.toString;
export function defined(value: any): boolean {
return typeof value !== 'undefined';
}
export function boolean(value: any): value is boolean {
return value === true || value === false;
}
export function string(value: any): value is string {
return toString.call(value) === '[object String]';
}

View File

@@ -0,0 +1,63 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* --------------------------------------------------------------------------------------------
* Includes code from typescript-sublime-plugin project, obtained from
* https://github.com/Microsoft/TypeScript-Sublime-Plugin/blob/master/TypeScript%20Indent.tmPreferences
* ------------------------------------------------------------------------------------------ */
import { IndentAction } from 'vscode';
export const jsTsLanguageConfiguration = {
indentationRules: {
// ^(.*\*/)?\s*\}.*$
decreaseIndentPattern: /^((?!.*?\/\*).*\*\/)?\s*[\}\]\)].*$/,
// ^.*\{[^}"']*$
increaseIndentPattern: /^((?!\/\/).)*(\{[^}"'`]*|\([^)"'`]*|\[[^\]"'`]*)$/
},
wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,
onEnterRules: [
{
// e.g. /** | */
beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/,
afterText: /^\s*\*\/$/,
action: { indentAction: IndentAction.IndentOutdent, appendText: ' * ' }
}, {
// e.g. /** ...|
beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/,
action: { indentAction: IndentAction.None, appendText: ' * ' }
}, {
// e.g. * ...|
beforeText: /^(\t|[ ])*[ ]\*([ ]([^\*]|\*(?!\/))*)?$/,
action: { indentAction: IndentAction.None, appendText: '* ' }
}, {
// e.g. */|
beforeText: /^(\t|[ ])*[ ]\*\/\s*$/,
action: { indentAction: IndentAction.None, removeText: 1 }
},
{
// e.g. *-----*/|
beforeText: /^(\t|[ ])*[ ]\*[^/]*\*\/\s*$/,
action: { indentAction: IndentAction.None, removeText: 1 }
}
]
};
const EMPTY_ELEMENTS: string[] = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr'];
export const jsxTags = {
wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\$\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\s]+)/g,
onEnterRules: [
{
beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))([_:\\w][_:\\w-.\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'),
afterText: /^<\/([_:\w][_:\w-.\d]*)\s*>$/i,
action: { indentAction: IndentAction.IndentOutdent }
},
{
beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'),
action: { indentAction: IndentAction.Indent }
}
],
};

View File

@@ -0,0 +1,27 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as languageModeIds from './languageModeIds';
export interface LanguageDescription {
id: string;
diagnosticSource: string;
modeIds: string[];
configFile?: string;
isExternal?: boolean;
}
export const standardLanguageDescriptions: LanguageDescription[] = [
{
id: 'typescript',
diagnosticSource: 'ts',
modeIds: [languageModeIds.typescript, languageModeIds.typescriptreact],
configFile: 'tsconfig.json'
}, {
id: 'javascript',
diagnosticSource: 'js',
modeIds: [languageModeIds.javascript, languageModeIds.javascriptreact],
configFile: 'jsconfig.json'
}
];

View File

@@ -0,0 +1,17 @@
/*---------------------------------------------------------------------------------------------
* 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';
export const typescript = 'typescript';
export const typescriptreact = 'typescriptreact';
export const javascript = 'javascript';
export const javascriptreact = 'javascriptreact';
export const jsxTags = 'jsx-tags';
export function isSupportedLanguageMode(doc: vscode.TextDocument) {
return vscode.languages.match([typescript, typescriptreact, javascript, javascriptreact], doc) > 0;
}

View File

@@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface Lazy<T> {
value: T;
hasValue: boolean;
map<R>(f: (x: T) => R): Lazy<R>;
}
class LazyValue<T> implements Lazy<T> {
private _hasValue: boolean = false;
private _value?: T;
constructor(
private readonly _getValue: () => T
) { }
get value(): T {
if (!this._hasValue) {
this._hasValue = true;
this._value = this._getValue();
}
return this._value!;
}
get hasValue(): boolean {
return this._hasValue;
}
public map<R>(f: (x: T) => R): Lazy<R> {
return new LazyValue(() => f(this.value));
}
}
export function lazy<T>(getValue: () => T): Lazy<T> {
return new LazyValue<T>(getValue);
}

View File

@@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* 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 path from 'path';
import * as vscode from 'vscode';
import { memoize } from './memoize';
export default class LogDirectoryProvider {
public constructor(
private readonly context: vscode.ExtensionContext
) { }
public async getNewLogDirectory(): Promise<string | undefined> {
const root = this.logDirectory();
if (root) {
try {
return fs.mkdtempSync(path.join(root, `tsserver-log-`));
} catch (e) {
return undefined;
}
}
return undefined;
}
@memoize
private logDirectory(): string | undefined {
try {
const path = this.context.logDirectory;
if (!fs.existsSync(path)) {
fs.mkdirSync(path);
}
return this.context.logDirectory;
} catch {
return undefined;
}
}
}

View File

@@ -0,0 +1,58 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { OutputChannel, window } from 'vscode';
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 data2String(data: any): string {
if (data instanceof Error) {
if (is.string(data.stack)) {
return data.stack;
}
return (data as Error).message;
}
if (is.boolean(data.success) && !data.success && is.string(data.message)) {
return data.message;
}
if (is.string(data)) {
return data;
}
return data.toString();
}
public info(message: string, data?: any): void {
this.logLevel('Info', message, data);
}
public warn(message: string, data?: any): void {
this.logLevel('Warn', message, data);
}
public error(message: string, data?: any): void {
// See https://github.com/Microsoft/TypeScript/issues/10496
if (data && data.message === 'No content available.') {
return;
}
this.logLevel('Error', message, data);
}
public logLevel(level: string, message: string, data?: any): void {
this.output.appendLine(`[${level} - ${(new Date().toLocaleTimeString())}] ${message}`);
if (data) {
this.output.appendLine(this.data2String(data));
}
}
}

View File

@@ -0,0 +1,48 @@
/*---------------------------------------------------------------------------------------------
* 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 { 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 isInManagedFileContext: boolean = false;
private readonly onDidChangeActiveTextEditorSub: vscode.Disposable;
public constructor(
private readonly normalizePath: (resource: vscode.Uri) => string | null
) {
this.onDidChangeActiveTextEditorSub = vscode.window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this);
this.onDidChangeActiveTextEditor(vscode.window.activeTextEditor);
}
public dispose() {
this.onDidChangeActiveTextEditorSub.dispose();
}
private onDidChangeActiveTextEditor(editor?: vscode.TextEditor): any {
if (editor) {
const isManagedFile = isSupportedLanguageMode(editor.document) && this.normalizePath(editor.document.uri) !== null;
this.updateContext(isManagedFile);
}
}
private updateContext(newValue: boolean) {
if (newValue === this.isInManagedFileContext) {
return;
}
vscode.commands.executeCommand('setContext', isManagedFile_contextName, newValue);
this.isInManagedFileContext = newValue;
}
}

View File

@@ -0,0 +1,34 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function memoize(_target: any, key: string, descriptor: any) {
let fnKey: string | undefined = undefined;
let fn: Function | undefined = undefined;
if (typeof descriptor.value === 'function') {
fnKey = 'value';
fn = descriptor.value;
} else if (typeof descriptor.get === 'function') {
fnKey = 'get';
fn = descriptor.get;
} else {
throw new Error('not supported');
}
const memoizeKey = `$memoize$${key}`;
descriptor[fnKey] = function (...args: any[]) {
if (!this.hasOwnProperty(memoizeKey)) {
Object.defineProperty(this, memoizeKey, {
configurable: false,
enumerable: false,
writable: false,
value: fn!.apply(this, args)
});
}
return this[memoizeKey];
};
}

View File

@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* 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 { TypeScriptServiceConfiguration } from './configuration';
import { RelativeWorkspacePathResolver } from './relativePathResolver';
export class TypeScriptPluginPathsProvider {
public readonly relativePathResolver: RelativeWorkspacePathResolver = new RelativeWorkspacePathResolver();
public constructor(
private configuration: TypeScriptServiceConfiguration
) { }
public updateConfiguration(configuration: TypeScriptServiceConfiguration): void {
this.configuration = configuration;
}
public getPluginPaths(): string[] {
const pluginPaths = [];
for (const pluginPath of this.configuration.tsServerPluginPaths) {
pluginPaths.push(...this.resolvePluginPath(pluginPath));
}
return pluginPaths;
}
private resolvePluginPath(pluginPath: string): string[] {
if (path.isAbsolute(pluginPath)) {
return [pluginPath];
}
const workspacePath = this.relativePathResolver.asAbsoluteWorkspacePath(pluginPath);
if (workspacePath !== undefined) {
return [workspacePath];
}
return (workspace.workspaceFolders || [])
.map(workspaceFolder => path.join(workspaceFolder.uri.fsPath, pluginPath));
}
}

View File

@@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { extensions } from 'vscode';
export interface TypeScriptServerPlugin {
readonly path: string;
readonly name: string;
readonly languages: string[];
}
export function getContributedTypeScriptServerPlugins(): TypeScriptServerPlugin[] {
const plugins: TypeScriptServerPlugin[] = [];
for (const extension of extensions.all) {
const pack = extension.packageJSON;
if (pack.contributes && pack.contributes.typescriptServerPlugins && Array.isArray(pack.contributes.typescriptServerPlugins)) {
for (const plugin of pack.contributes.typescriptServerPlugins) {
plugins.push({
name: plugin.name,
path: extension.extensionPath,
languages: Array.isArray(plugin.languages) ? plugin.languages : [],
});
}
}
}
return plugins;
}

View File

@@ -0,0 +1,84 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as Proto from '../protocol';
import { MarkdownString } from 'vscode';
function getTagBodyText(tag: Proto.JSDocTagInfo): string | undefined {
if (!tag.text) {
return undefined;
}
switch (tag.name) {
case 'example':
case 'default':
// Convert to markdown code block if it not already one
if (tag.text.match(/^\s*[~`]{3}/g)) {
return tag.text;
}
return '```\n' + tag.text + '\n```';
}
return tag.text;
}
function getTagDocumentation(tag: Proto.JSDocTagInfo): string | undefined {
switch (tag.name) {
case 'param':
const body = (tag.text || '').split(/^([\w\.]+)\s*/);
if (body && body.length === 3) {
const param = body[1];
const doc = body[2];
const label = `*@${tag.name}* \`${param}\``;
if (!doc) {
return label;
}
return label + (doc.match(/\r\n|\n/g) ? ' \n' + doc : `${doc}`);
}
}
// Generic tag
const label = `*@${tag.name}*`;
const text = getTagBodyText(tag);
if (!text) {
return label;
}
return label + (text.match(/\r\n|\n/g) ? ' \n' + text : `${text}`);
}
export function plain(parts: Proto.SymbolDisplayPart[]): string {
if (!parts) {
return '';
}
return parts.map(part => part.text).join('');
}
export function tagsMarkdownPreview(tags: Proto.JSDocTagInfo[]): string {
return (tags || [])
.map(getTagDocumentation)
.join(' \n\n');
}
export function markdownDocumentation(
documentation: Proto.SymbolDisplayPart[],
tags: Proto.JSDocTagInfo[]
): MarkdownString {
const out = new 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);
}
return out;
}

View File

@@ -0,0 +1,238 @@
/*---------------------------------------------------------------------------------------------
* 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 { ITypeScriptServiceClient } from '../typescriptService';
import { loadMessageBundle } from 'vscode-nls';
import { dirname } from 'path';
import { openOrCreateConfigFile, isImplicitProjectConfigFile } from './tsconfig';
import * as languageModeIds from '../utils/languageModeIds';
import TelemetryReporter from './telemetry';
const localize = loadMessageBundle();
const selector = [languageModeIds.javascript, languageModeIds.javascriptreact];
interface Hint {
message: string;
}
interface ProjectHintedMap {
[k: string]: boolean;
}
const fileLimit = 500;
class ExcludeHintItem {
public configFileName?: string;
private _item: vscode.StatusBarItem;
private _currentHint?: Hint;
constructor(
private readonly telemetryReporter: TelemetryReporter
) {
this._item = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 98 /* to the right of typescript version status (99) */);
this._item.command = 'js.projectStatus.command';
}
public getCurrentHint(): Hint {
return this._currentHint!;
}
public hide() {
this._item.hide();
}
public show(largeRoots?: string) {
this._currentHint = {
message: largeRoots
? localize('hintExclude', "To enable project-wide JavaScript/TypeScript language features, exclude folders with many files, like: {0}", largeRoots)
: localize('hintExclude.generic', "To enable project-wide JavaScript/TypeScript language features, exclude large folders with source files that you do not work on.")
};
this._item.tooltip = this._currentHint.message;
this._item.text = localize('large.label', "Configure Excludes");
this._item.tooltip = localize('hintExclude.tooltip', "To enable project-wide JavaScript/TypeScript language features, exclude large folders with source files that you do not work on.");
this._item.color = '#A5DF3B';
this._item.show();
/* __GDPR__
"js.hintProjectExcludes" : {}
*/
this.telemetryReporter.logTelemetry('js.hintProjectExcludes');
}
}
function createLargeProjectMonitorForProject(item: ExcludeHintItem, client: ITypeScriptServiceClient, isOpen: (resource: vscode.Uri) => Promise<boolean>, memento: vscode.Memento): vscode.Disposable[] {
const toDispose: vscode.Disposable[] = [];
const projectHinted: ProjectHintedMap = Object.create(null);
const projectHintIgnoreList = memento.get<string[]>('projectHintIgnoreList', []);
for (let path of projectHintIgnoreList) {
if (path === null) {
path = 'undefined';
}
projectHinted[path] = true;
}
function onEditor(editor: vscode.TextEditor | undefined): void {
if (!editor
|| !vscode.languages.match(selector, editor.document)
|| !client.normalizePath(editor.document.uri)) {
item.hide();
return;
}
const file = client.normalizePath(editor.document.uri);
if (!file) {
return;
}
isOpen(editor.document.uri).then(value => {
if (!value) {
return;
}
return client.execute('projectInfo', { file, needFileNameList: true } as protocol.ProjectInfoRequestArgs).then(res => {
if (!res.body) {
return;
}
let { configFileName, fileNames } = res.body;
if (projectHinted[configFileName] === true || !fileNames) {
return;
}
if (fileNames.length > fileLimit || res.body.languageServiceDisabled) {
let largeRoots = computeLargeRoots(configFileName, fileNames).map(f => `'/${f}/'`).join(', ');
item.show(largeRoots);
projectHinted[configFileName] = true;
} else {
item.hide();
}
});
}).catch(err => {
client.logger.warn(err);
});
}
toDispose.push(vscode.workspace.onDidChangeTextDocument(e => {
delete projectHinted[e.document.fileName];
}));
toDispose.push(vscode.window.onDidChangeActiveTextEditor(onEditor));
onEditor(vscode.window.activeTextEditor);
return toDispose;
}
function createLargeProjectMonitorFromTypeScript(item: ExcludeHintItem, client: ITypeScriptServiceClient): vscode.Disposable {
interface LargeProjectMessageItem extends vscode.MessageItem {
index: number;
}
return client.onProjectLanguageServiceStateChanged(body => {
if (body.languageServiceEnabled) {
item.hide();
} else {
item.show();
const configFileName = body.projectName;
if (configFileName) {
item.configFileName = configFileName;
vscode.window.showWarningMessage<LargeProjectMessageItem>(item.getCurrentHint().message,
{
title: localize('large.label', "Configure Excludes"),
index: 0
}).then(selected => {
if (selected && selected.index === 0) {
onConfigureExcludesSelected(client, configFileName);
}
});
}
}
});
}
function onConfigureExcludesSelected(
client: ITypeScriptServiceClient,
configFileName: string
) {
if (!isImplicitProjectConfigFile(configFileName)) {
vscode.workspace.openTextDocument(configFileName)
.then(vscode.window.showTextDocument);
} else {
const root = client.getWorkspaceRootForResource(vscode.Uri.file(configFileName));
if (root) {
openOrCreateConfigFile(
configFileName.match(/tsconfig\.?.*\.json/) !== null,
root,
client.configuration);
}
}
}
export function create(
client: ITypeScriptServiceClient,
telemetryReporter: TelemetryReporter,
isOpen: (resource: vscode.Uri) => Promise<boolean>,
memento: vscode.Memento
) {
const toDispose: vscode.Disposable[] = [];
const item = new ExcludeHintItem(telemetryReporter);
toDispose.push(vscode.commands.registerCommand('js.projectStatus.command', () => {
if (item.configFileName) {
onConfigureExcludesSelected(client, item.configFileName);
}
let { message } = item.getCurrentHint();
return vscode.window.showInformationMessage(message);
}));
if (client.apiVersion.has213Features()) {
toDispose.push(createLargeProjectMonitorFromTypeScript(item, client));
} else {
toDispose.push(...createLargeProjectMonitorForProject(item, client, isOpen, memento));
}
return vscode.Disposable.from(...toDispose);
}
function computeLargeRoots(configFileName: string, fileNames: string[]): string[] {
let roots: { [first: string]: number } = Object.create(null);
let dir = dirname(configFileName);
// console.log(dir, fileNames);
for (const fileName of fileNames) {
if (fileName.indexOf(dir) === 0) {
let first = fileName.substring(dir.length + 1);
first = first.substring(0, first.indexOf('/'));
if (first) {
roots[first] = (roots[first] || 0) + 1;
}
}
}
let data: { root: string; count: number }[] = [];
for (let key in roots) {
data.push({ root: key, count: roots[key] });
}
data
.sort((a, b) => b.count - a.count)
.filter(s => s.root === 'src' || s.root === 'test' || s.root === 'tests');
let result: string[] = [];
let sum = 0;
for (const e of data) {
sum += e.count;
result.push(e.root);
if (fileNames.length - sum < fileLimit) {
break;
}
}
return result;
}

View File

@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function escapeRegExp(text: string) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
}

View File

@@ -0,0 +1,21 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import { workspace } from 'vscode';
export class RelativeWorkspacePathResolver {
public asAbsoluteWorkspacePath(relativePath: string): string | undefined {
for (const root of workspace.workspaceFolders || []) {
const rootPrefixes = [`./${root.name}/`, `${root.name}/`, `.\\${root.name}\\`, `${root.name}\\`];
for (const rootPrefix of rootPrefixes) {
if (relativePath.startsWith(rootPrefix)) {
return path.join(root.uri.fsPath, relativePath.replace(rootPrefix, ''));
}
}
}
return undefined;
}
}

View File

@@ -0,0 +1,67 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import VsCodeTelemetryReporter from 'vscode-extension-telemetry';
import { memoize } from './memoize';
interface IPackageInfo {
readonly name: string;
readonly version: string;
readonly aiKey: string;
}
export default class TelemetryReporter {
private _reporter: VsCodeTelemetryReporter | null = null;
dispose() {
if (this._reporter) {
this._reporter.dispose();
this._reporter = null;
}
}
constructor(
private readonly clientVersionDelegate: () => string
) { }
public logTelemetry(eventName: string, properties?: { [prop: string]: string }) {
const reporter = this.reporter;
if (reporter) {
if (!properties) {
properties = {};
}
properties['version'] = this.clientVersionDelegate();
reporter.sendTelemetryEvent(eventName, properties);
}
}
@memoize
private get reporter(): VsCodeTelemetryReporter | null {
if (this.packageInfo && this.packageInfo.aiKey) {
this._reporter = new VsCodeTelemetryReporter(
this.packageInfo.name,
this.packageInfo.version,
this.packageInfo.aiKey);
return this._reporter;
}
return null;
}
@memoize
private get packageInfo(): IPackageInfo | null {
const packagePath = path.join(__dirname, '..', '..', 'package.json');
const extensionPackage = require(packagePath);
if (extensionPackage) {
return {
name: extensionPackage.name,
version: extensionPackage.version,
aiKey: extensionPackage.aiKey
};
}
return null;
}
}

View File

@@ -0,0 +1,93 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { workspace } from 'vscode';
import * as Proto from '../protocol';
import Logger from './logger';
enum Trace {
Off,
Messages,
Verbose
}
namespace Trace {
export function fromString(value: string): Trace {
value = value.toLowerCase();
switch (value) {
case 'off':
return Trace.Off;
case 'messages':
return Trace.Messages;
case 'verbose':
return Trace.Verbose;
default:
return Trace.Off;
}
}
}
export default class Tracer {
private trace?: Trace;
constructor(
private readonly logger: Logger
) {
this.updateConfiguration();
}
public updateConfiguration() {
this.trace = Tracer.readTrace();
}
private static readTrace(): Trace {
let result: Trace = Trace.fromString(workspace.getConfiguration().get<string>('typescript.tsserver.trace', 'off'));
if (result === Trace.Off && !!process.env.TSS_TRACE) {
result = Trace.Messages;
}
return result;
}
public traceRequest(request: Proto.Request, responseExpected: boolean, queueLength: number): void {
if (this.trace === Trace.Off) {
return;
}
let data: string | undefined = undefined;
if (this.trace === Trace.Verbose && request.arguments) {
data = `Arguments: ${JSON.stringify(request.arguments, null, 4)}`;
}
this.logTrace(`Sending request: ${request.command} (${request.seq}). Response expected: ${responseExpected ? 'yes' : 'no'}. Current queue length: ${queueLength}`, data);
}
public traceResponse(response: Proto.Response, startTime: number): void {
if (this.trace === Trace.Off) {
return;
}
let data: string | undefined = undefined;
if (this.trace === Trace.Verbose && response.body) {
data = `Result: ${JSON.stringify(response.body, null, 4)}`;
}
this.logTrace(`Response received: ${response.command} (${response.request_seq}). Request took ${Date.now() - startTime} ms. Success: ${response.success} ${!response.success ? '. Message: ' + response.message : ''}`, data);
}
public traceEvent(event: Proto.Event): void {
if (this.trace === Trace.Off) {
return;
}
let data: string | undefined = undefined;
if (this.trace === Trace.Verbose && event.body) {
data = `Data: ${JSON.stringify(event.body, null, 4)}`;
}
this.logTrace(`Event received: ${event.event} (${event.seq}).`, data);
}
public logTrace(message: string, data?: any): void {
if (this.trace !== Trace.Off) {
this.logger.logLevel('Trace', message, data);
}
}
}

View File

@@ -0,0 +1,70 @@
/*---------------------------------------------------------------------------------------------
* 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 path from 'path';
import * as Proto from '../protocol';
import { TypeScriptServiceConfiguration } from './configuration';
export function isImplicitProjectConfigFile(configFileName: string) {
return configFileName.indexOf('/dev/null/') === 0;
}
export function inferredProjectConfig(
config: TypeScriptServiceConfiguration
): Proto.ExternalProjectCompilerOptions {
const base: Proto.ExternalProjectCompilerOptions = {
module: 'commonjs' as Proto.ModuleKind,
target: 'es2016' as Proto.ScriptTarget,
jsx: 'preserve' as Proto.JsxEmit
};
if (config.checkJs) {
base.checkJs = true;
}
if (config.experimentalDecorators) {
base.experimentalDecorators = true;
}
return base;
}
function inferredProjectConfigSnippet(
config: TypeScriptServiceConfiguration
) {
const baseConfig = inferredProjectConfig(config);
const compilerOptions = Object.keys(baseConfig).map(key => `"${key}": ${JSON.stringify(baseConfig[key])}`);
return new vscode.SnippetString(`{
"compilerOptions": {
${compilerOptions.join(',\n\t\t')}$0
},
"exclude": [
"node_modules",
"**/node_modules/*"
]
}`);
}
export async function openOrCreateConfigFile(
isTypeScriptProject: boolean,
rootPath: string,
config: TypeScriptServiceConfiguration
): Promise<vscode.TextEditor | null> {
const configFile = vscode.Uri.file(path.join(rootPath, isTypeScriptProject ? 'tsconfig.json' : 'jsconfig.json'));
const col = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
try {
const doc = await vscode.workspace.openTextDocument(configFile);
return vscode.window.showTextDocument(doc, col);
} catch {
const doc = await vscode.workspace.openTextDocument(configFile.with({ scheme: 'untitled' }));
const editor = await vscode.window.showTextDocument(doc, col);
if (editor.document.getText().length === 0) {
await editor.insertSnippet(inferredProjectConfigSnippet(config));
}
return editor;
}
}

View File

@@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* 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';
export interface TSConfig {
path: string;
workspaceFolder?: vscode.WorkspaceFolder;
}
export default class TsConfigProvider {
public async getConfigsForWorkspace(): Promise<Iterable<TSConfig>> {
if (!vscode.workspace.workspaceFolders) {
return [];
}
const configs = new Map<string, TSConfig>();
for (const config of await vscode.workspace.findFiles('**/tsconfig*.json', '**/node_modules/**')) {
const root = vscode.workspace.getWorkspaceFolder(config);
if (root) {
configs.set(config.fsPath, {
path: config.fsPath,
workspaceFolder: root
});
}
}
return configs.values();
}
}

View File

@@ -0,0 +1,68 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Helpers for converting FROM vscode types TO ts types
*/
import * as vscode from 'vscode';
import * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
export namespace Range {
export const fromTextSpan = (span: Proto.TextSpan): vscode.Range =>
new vscode.Range(
span.start.line - 1, span.start.offset - 1,
span.end.line - 1, span.end.offset - 1);
export const toFileRangeRequestArgs = (file: string, range: vscode.Range): Proto.FileRangeRequestArgs => ({
file,
startLine: range.start.line + 1,
startOffset: range.start.character + 1,
endLine: range.end.line + 1,
endOffset: range.end.character + 1
});
}
export namespace Position {
export const fromLocation = (tslocation: Proto.Location): vscode.Position =>
new vscode.Position(tslocation.line - 1, tslocation.offset - 1);
export const toFileLocationRequestArgs = (file: string, position: vscode.Position): Proto.FileLocationRequestArgs => ({
file,
line: position.line + 1,
offset: position.character + 1
});
}
export namespace Location {
export const fromTextSpan = (resource: vscode.Uri, tsTextSpan: Proto.TextSpan): vscode.Location =>
new vscode.Location(resource, Range.fromTextSpan(tsTextSpan));
}
export namespace TextEdit {
export const fromCodeEdit = (edit: Proto.CodeEdit): vscode.TextEdit =>
new vscode.TextEdit(
Range.fromTextSpan(edit),
edit.newText);
}
export namespace WorkspaceEdit {
export function fromFromFileCodeEdits(
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.asUrl(edit.fileName),
Range.fromTextSpan(textChange),
textChange.newText);
}
}
return workspaceEdit;
}
}

View File

@@ -0,0 +1,128 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* 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 { loadMessageBundle } from 'vscode-nls';
const localize = loadMessageBundle();
const typingsInstallTimeout = 30 * 1000;
export default class TypingsStatus extends Disposable {
private _acquiringTypings: { [eventId: string]: NodeJS.Timer } = Object.create({});
private _client: ITypeScriptServiceClient;
private _subscriptions: Disposable[] = [];
constructor(client: ITypeScriptServiceClient) {
super(() => this.dispose());
this._client = client;
this._subscriptions.push(
this._client.onDidBeginInstallTypings(event => this.onBeginInstallTypings(event.eventId)));
this._subscriptions.push(
this._client.onDidEndInstallTypings(event => this.onEndInstallTypings(event.eventId)));
}
public dispose(): void {
this._subscriptions.forEach(x => x.dispose());
for (const eventId of Object.keys(this._acquiringTypings)) {
clearTimeout(this._acquiringTypings[eventId]);
}
}
public get isAcquiringTypings(): boolean {
return Object.keys(this._acquiringTypings).length > 0;
}
private onBeginInstallTypings(eventId: number): void {
if (this._acquiringTypings[eventId]) {
return;
}
this._acquiringTypings[eventId] = setTimeout(() => {
this.onEndInstallTypings(eventId);
}, typingsInstallTimeout);
}
private onEndInstallTypings(eventId: number): void {
const timer = this._acquiringTypings[eventId];
if (timer) {
clearTimeout(timer);
}
delete this._acquiringTypings[eventId];
}
}
export class AtaProgressReporter {
private _promises = new Map<number, Function>();
private _disposable: Disposable;
constructor(client: ITypeScriptServiceClient) {
this._disposable = Disposable.from(
client.onDidBeginInstallTypings(e => this._onBegin(e.eventId)),
client.onDidEndInstallTypings(e => this._onEndOrTimeout(e.eventId)),
client.onTypesInstallerInitializationFailed(_ => this.onTypesInstallerInitializationFailed()));
}
dispose(): void {
this._disposable.dispose();
this._promises.forEach(value => value());
}
private _onBegin(eventId: number): void {
const handle = setTimeout(() => this._onEndOrTimeout(eventId), typingsInstallTimeout);
const promise = new Promise(resolve => {
this._promises.set(eventId, () => {
clearTimeout(handle);
resolve();
});
});
window.withProgress({
location: ProgressLocation.Window,
title: localize('installingPackages', "Fetching data for better TypeScript IntelliSense")
}, () => promise);
}
private _onEndOrTimeout(eventId: number): void {
const resolve = this._promises.get(eventId);
if (resolve) {
this._promises.delete(eventId);
resolve();
}
}
private onTypesInstallerInitializationFailed() {
interface MyMessageItem extends MessageItem {
id: number;
}
if (workspace.getConfiguration('typescript').get<boolean>('check.npmIsInstalled', true)) {
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.",
'https://go.microsoft.com/fwlink/?linkid=847635'
), {
title: localize('typesInstallerInitializationFailed.doNotCheckAgain', "Don't Show Again"),
id: 1
}
).then(selected => {
if (!selected) {
return;
}
switch (selected.id) {
case 1:
const tsConfig = workspace.getConfiguration('typescript');
tsConfig.update('check.npmIsInstalled', false, true);
break;
}
});
}
}
}

View File

@@ -0,0 +1,124 @@
/*---------------------------------------------------------------------------------------------
* 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';
import { TypeScriptVersionProvider, TypeScriptVersion } from './versionProvider';
import { Memento, commands, Uri, window, QuickPickItem, workspace } from 'vscode';
const localize = nls.loadMessageBundle();
const useWorkspaceTsdkStorageKey = 'typescript.useWorkspaceTsdk';
interface MyQuickPickItem extends QuickPickItem {
id: MessageAction;
version?: TypeScriptVersion;
}
enum MessageAction {
useLocal,
useBundled,
learnMore
}
export class TypeScriptVersionPicker {
private _currentVersion: TypeScriptVersion;
public constructor(
private readonly versionProvider: TypeScriptVersionProvider,
private readonly workspaceState: Memento
) {
this._currentVersion = this.versionProvider.defaultVersion;
if (this.useWorkspaceTsdkSetting) {
const localVersion = this.versionProvider.localVersion;
if (localVersion) {
this._currentVersion = localVersion;
}
}
}
public get useWorkspaceTsdkSetting(): boolean {
return this.workspaceState.get<boolean>(useWorkspaceTsdkStorageKey, false);
}
public get currentVersion(): TypeScriptVersion {
return this._currentVersion;
}
public useBundledVersion(): void {
this._currentVersion = this.versionProvider.bundledVersion;
}
public async show(firstRun?: boolean): Promise<{ oldVersion?: TypeScriptVersion, newVersion?: TypeScriptVersion }> {
const pickOptions: MyQuickPickItem[] = [];
const shippedVersion = this.versionProvider.defaultVersion;
pickOptions.push({
label: (!this.useWorkspaceTsdkSetting
? '• '
: '') + localize('useVSCodeVersionOption', 'Use VS Code\'s Version'),
description: shippedVersion.versionString,
detail: shippedVersion.pathLabel,
id: MessageAction.useBundled
});
for (const version of this.versionProvider.localVersions) {
pickOptions.push({
label: (this.useWorkspaceTsdkSetting && this.currentVersion.path === version.path
? '• '
: '') + localize('useWorkspaceVersionOption', 'Use Workspace Version'),
description: version.versionString,
detail: version.pathLabel,
id: MessageAction.useLocal,
version: version
});
}
pickOptions.push({
label: localize('learnMore', 'Learn More'),
description: '',
id: MessageAction.learnMore
});
const selected = await window.showQuickPick<MyQuickPickItem>(pickOptions, {
placeHolder: localize(
'selectTsVersion',
'Select the TypeScript version used for JavaScript and TypeScript language features'),
ignoreFocusOut: firstRun
});
if (!selected) {
return { oldVersion: this.currentVersion };
}
switch (selected.id) {
case MessageAction.useLocal:
await this.workspaceState.update(useWorkspaceTsdkStorageKey, true);
if (selected.version) {
const tsConfig = workspace.getConfiguration('typescript');
await tsConfig.update('tsdk', selected.version.pathLabel, false);
const previousVersion = this.currentVersion;
this._currentVersion = selected.version;
return { oldVersion: previousVersion, newVersion: selected.version };
}
return { oldVersion: this.currentVersion };
case MessageAction.useBundled:
await this.workspaceState.update(useWorkspaceTsdkStorageKey, false);
const previousVersion = this.currentVersion;
this._currentVersion = shippedVersion;
return { oldVersion: previousVersion, newVersion: shippedVersion };
case MessageAction.learnMore:
commands.executeCommand('vscode.open', Uri.parse('https://go.microsoft.com/fwlink/?linkid=839919'));
return { oldVersion: this.currentVersion };
default:
return { oldVersion: this.currentVersion };
}
}
}

View File

@@ -0,0 +1,200 @@
/*---------------------------------------------------------------------------------------------
* 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 { TypeScriptServiceConfiguration } from './configuration';
import { RelativeWorkspacePathResolver } from './relativePathResolver';
import API from './api';
export class TypeScriptVersion {
constructor(
public readonly path: string,
private readonly _pathLabel?: string
) { }
public get tsServerPath(): string {
return path.join(this.path, 'tsserver.js');
}
public get pathLabel(): string {
return typeof this._pathLabel === 'undefined' ? this.path : this._pathLabel;
}
public get isValid(): boolean {
return this.version !== undefined;
}
public get version(): API | undefined {
const version = this.getTypeScriptVersion(this.tsServerPath);
if (version) {
return version;
}
// Allow TS developers to provide custom version
const tsdkVersion = workspace.getConfiguration().get<string | undefined>('typescript.tsdk_version', undefined);
if (tsdkVersion) {
return API.fromVersionString(tsdkVersion);
}
return undefined;
}
public get versionString(): string {
const version = this.version;
return version ? version.versionString : localize(
'couldNotLoadTsVersion', 'Could not load the TypeScript version at this path');
}
private getTypeScriptVersion(serverPath: string): API | undefined {
if (!fs.existsSync(serverPath)) {
return undefined;
}
const p = serverPath.split(path.sep);
if (p.length <= 2) {
return undefined;
}
const p2 = p.slice(0, -2);
const modulePath = p2.join(path.sep);
let fileName = path.join(modulePath, 'package.json');
if (!fs.existsSync(fileName)) {
// Special case for ts dev versions
if (path.basename(modulePath) === 'built') {
fileName = path.join(modulePath, '..', 'package.json');
}
}
if (!fs.existsSync(fileName)) {
return undefined;
}
const contents = fs.readFileSync(fileName).toString();
let desc: any = null;
try {
desc = JSON.parse(contents);
} catch (err) {
return undefined;
}
if (!desc || !desc.version) {
return undefined;
}
return desc.version ? API.fromVersionString(desc.version) : undefined;
}
}
export class TypeScriptVersionProvider {
private readonly relativePathResolver: RelativeWorkspacePathResolver = new RelativeWorkspacePathResolver();
public constructor(
private configuration: TypeScriptServiceConfiguration
) { }
public updateConfiguration(configuration: TypeScriptServiceConfiguration): void {
this.configuration = configuration;
}
public get defaultVersion(): TypeScriptVersion {
return this.globalVersion || this.bundledVersion;
}
public get globalVersion(): TypeScriptVersion | undefined {
if (this.configuration.globalTsdk) {
const globals = this.loadVersionsFromSetting(this.configuration.globalTsdk);
if (globals && globals.length) {
return globals[0];
}
}
return undefined;
}
public get localVersion(): TypeScriptVersion | undefined {
const tsdkVersions = this.localTsdkVersions;
if (tsdkVersions && tsdkVersions.length) {
return tsdkVersions[0];
}
const nodeVersions = this.localNodeModulesVersions;
if (nodeVersions && nodeVersions.length === 1) {
return nodeVersions[0];
}
return undefined;
}
public get localVersions(): TypeScriptVersion[] {
const allVersions = this.localTsdkVersions.concat(this.localNodeModulesVersions);
const paths = new Set<string>();
return allVersions.filter(x => {
if (paths.has(x.path)) {
return false;
}
paths.add(x.path);
return true;
});
}
public get bundledVersion(): TypeScriptVersion {
try {
const bundledVersion = new TypeScriptVersion(
path.dirname(require.resolve('typescript/lib/tsserver.js')),
'');
if (bundledVersion.isValid) {
return bundledVersion;
}
} catch (e) {
// noop
}
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');
}
private get localTsdkVersions(): TypeScriptVersion[] {
const localTsdk = this.configuration.localTsdk;
return localTsdk ? this.loadVersionsFromSetting(localTsdk) : [];
}
private loadVersionsFromSetting(tsdkPathSetting: string): TypeScriptVersion[] {
if (path.isAbsolute(tsdkPathSetting)) {
return [new TypeScriptVersion(tsdkPathSetting)];
}
const workspacePath = this.relativePathResolver.asAbsoluteWorkspacePath(tsdkPathSetting);
if (workspacePath !== undefined) {
return [new TypeScriptVersion(workspacePath, tsdkPathSetting)];
}
return this.loadTypeScriptVersionsFromPath(tsdkPathSetting);
}
private get localNodeModulesVersions(): TypeScriptVersion[] {
return this.loadTypeScriptVersionsFromPath(path.join('node_modules', 'typescript', 'lib'))
.filter(x => x.isValid);
}
private loadTypeScriptVersionsFromPath(relativePath: string): TypeScriptVersion[] {
if (!workspace.workspaceFolders) {
return [];
}
const versions: TypeScriptVersion[] = [];
for (const root of workspace.workspaceFolders) {
let label: string = relativePath;
if (workspace.workspaceFolders && workspace.workspaceFolders.length > 1) {
label = path.join(root.name, relativePath);
}
versions.push(new TypeScriptVersion(path.join(root.uri.fsPath, relativePath), label));
}
return versions;
}
}

View File

@@ -0,0 +1,57 @@
/*---------------------------------------------------------------------------------------------
* 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 { TypeScriptVersion } from './versionProvider';
import * as languageModeIds from './languageModeIds';
export default class VersionStatus {
private readonly _onChangeEditorSub: vscode.Disposable;
private readonly _versionBarEntry: vscode.StatusBarItem;
constructor(
private readonly _normalizePath: (resource: vscode.Uri) => string | null
) {
this._versionBarEntry = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 99 /* to the right of editor status (100) */);
this._onChangeEditorSub = vscode.window.onDidChangeActiveTextEditor(this.showHideStatus, this);
}
dispose() {
this._versionBarEntry.dispose();
this._onChangeEditorSub.dispose();
}
public onDidChangeTypeScriptVersion(version: TypeScriptVersion) {
this.showHideStatus();
this._versionBarEntry.text = version.versionString;
this._versionBarEntry.tooltip = version.path;
this._versionBarEntry.command = 'typescript.selectTypeScriptVersion';
}
private showHideStatus() {
if (!vscode.window.activeTextEditor) {
this._versionBarEntry.hide();
return;
}
const doc = vscode.window.activeTextEditor.document;
if (vscode.languages.match([languageModeIds.typescript, languageModeIds.typescriptreact], doc)) {
if (this._normalizePath(doc.uri)) {
this._versionBarEntry.show();
} else {
this._versionBarEntry.hide();
}
return;
}
if (!vscode.window.activeTextEditor.viewColumn) {
// viewColumn is undefined for the debug/output panel, but we still want
// to show the version info in the existing editor
return;
}
this._versionBarEntry.hide();
}
}

View File

@@ -0,0 +1,122 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as stream from 'stream';
const DefaultSize: number = 8192;
const ContentLength: string = 'Content-Length: ';
const ContentLengthSize: number = Buffer.byteLength(ContentLength, 'utf8');
const Blank: number = Buffer.from(' ', 'utf8')[0];
const BackslashR: number = Buffer.from('\r', 'utf8')[0];
const BackslashN: number = Buffer.from('\n', 'utf8')[0];
class ProtocolBuffer {
private index: number = 0;
private buffer: Buffer = Buffer.allocUnsafe(DefaultSize);
public append(data: string | Buffer): void {
let toAppend: Buffer | null = null;
if (Buffer.isBuffer(data)) {
toAppend = <Buffer>data;
} else {
toAppend = Buffer.from(<string>data, 'utf8');
}
if (this.buffer.length - this.index >= toAppend.length) {
toAppend.copy(this.buffer, this.index, 0, toAppend.length);
} else {
let newSize = (Math.ceil((this.index + toAppend.length) / DefaultSize) + 1) * DefaultSize;
if (this.index === 0) {
this.buffer = Buffer.allocUnsafe(newSize);
toAppend.copy(this.buffer, 0, 0, toAppend.length);
} else {
this.buffer = Buffer.concat([this.buffer.slice(0, this.index), toAppend], newSize);
}
}
this.index += toAppend.length;
}
public tryReadContentLength(): number {
let result = -1;
let current = 0;
// we are utf8 encoding...
while (current < this.index && (this.buffer[current] === Blank || this.buffer[current] === BackslashR || this.buffer[current] === BackslashN)) {
current++;
}
if (this.index < current + ContentLengthSize) {
return result;
}
current += ContentLengthSize;
let start = current;
while (current < this.index && this.buffer[current] !== BackslashR) {
current++;
}
if (current + 3 >= this.index || this.buffer[current + 1] !== BackslashN || this.buffer[current + 2] !== BackslashR || this.buffer[current + 3] !== BackslashN) {
return result;
}
let data = this.buffer.toString('utf8', start, current);
result = parseInt(data);
this.buffer = this.buffer.slice(current + 4);
this.index = this.index - (current + 4);
return result;
}
public tryReadContent(length: number): string | null {
if (this.index < length) {
return null;
}
let result = this.buffer.toString('utf8', 0, length);
let sourceStart = length;
while (sourceStart < this.index && (this.buffer[sourceStart] === BackslashR || this.buffer[sourceStart] === BackslashN)) {
sourceStart++;
}
this.buffer.copy(this.buffer, 0, sourceStart);
this.index = this.index - sourceStart;
return result;
}
}
export interface ICallback<T> {
(data: T): void;
}
export class Reader<T> {
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);
});
}
private onLengthData(data: Buffer): void {
try {
this.buffer.append(data);
while (true) {
if (this.nextMessageLength === -1) {
this.nextMessageLength = this.buffer.tryReadContentLength();
if (this.nextMessageLength === -1) {
return;
}
}
const msg = this.buffer.tryReadContent(this.nextMessageLength);
if (msg === null) {
return;
}
this.nextMessageLength = -1;
const json = JSON.parse(msg);
this.callback(json);
}
} catch (e) {
this.onError(e);
}
}
}