Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions extensions/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"onCommand:javascript.goToProjectConfig",
"onCommand:typescript.goToProjectConfig",
"onCommand:typescript.openTsServerLog",
"onCommand:typescript.organizeImports",
"onCommand:workbench.action.tasks.runTask"
],
"main": "./out/extension",
Expand Down Expand Up @@ -460,6 +461,18 @@
"value": "%typescript.restartTsServer%"
},
"category": "TypeScript"
},
{
"command": "typescript.organizeImports",
"title": "%typescript.organizeImports%",
"category": "TypeScript"
}
],
"keybindings": [
{
"command": "typescript.organizeImports",
"key": "shift+ctrl+o",
"when": "typescript.isManagedFile && typescript.canOrganizeImports"
}
],
"menus": {
Expand Down Expand Up @@ -507,6 +520,10 @@
{
"command": "typescript.restartTsServer",
"when": "typescript.isManagedFile"
},
{
"command": "typescript.organizeImports",
"when": "typescript.isManagedFile && typescript.canOrganizeImports"
}
]
},
Expand Down
3 changes: 2 additions & 1 deletion extensions/typescript/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,6 @@
"javascript.implicitProjectConfig.experimentalDecorators": "Enable/disable 'experimentalDecorators' for JavaScript files that are not part of a project. Existing jsconfig.json or tsconfig.json files override this setting. Requires TypeScript >=2.3.1.",
"typescript.autoImportSuggestions.enabled": "Enable/disable auto import suggestions. Requires TypeScript >=2.6.1",
"typescript.experimental.syntaxFolding": "Enables/disables syntax aware folding markers.",
"taskDefinition.tsconfig.description": "The tsconfig file that defines the TS build."
"taskDefinition.tsconfig.description": "The tsconfig file that defines the TS build.",
"typescript.organizeImports": "Organize Imports"
}
10 changes: 10 additions & 0 deletions extensions/typescript/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ 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
Expand Down Expand Up @@ -70,7 +71,14 @@ function createLazyClientHost(
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(
Expand All @@ -79,6 +87,7 @@ function createLazyClientHost(
path => new Promise<boolean>(resolve => setTimeout(() => resolve(clientHost.handles(path)), 750)),
context.workspaceState));
});

return clientHost;
});
}
Expand All @@ -94,6 +103,7 @@ function registerCommands(
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(
Expand Down
85 changes: 85 additions & 0 deletions extensions/typescript/src/features/organizeImports.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
14 changes: 1 addition & 13 deletions extensions/typescript/src/features/refactorProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class ApplyRefactoringCommand implements Command {
return false;
}

const edit = this.toWorkspaceEdit(response.body.edits);
const edit = typeConverters.WorkspaceEdit.fromFromFileCodeEdits(this.client, response.body.edits);
if (!(await vscode.workspace.applyEdit(edit))) {
return false;
}
Expand All @@ -56,18 +56,6 @@ class ApplyRefactoringCommand implements Command {
}
return true;
}

private toWorkspaceEdit(edits: Proto.FileCodeEdits[]): vscode.WorkspaceEdit {
const workspaceEdit = new vscode.WorkspaceEdit();
for (const edit of edits) {
for (const textChange of edit.textChanges) {
workspaceEdit.replace(this.client.asUrl(edit.fileName),
typeConverters.Range.fromTextSpan(textChange),
textChange.newText);
}
}
return workspaceEdit;
}
}

class SelectRefactorCommand implements Command {
Expand Down
3 changes: 2 additions & 1 deletion extensions/typescript/src/typescriptService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface ITypeScriptServiceClient {
asUrl(filepath: string): Uri;
getWorkspaceRootForResource(resource: Uri): string | undefined;

onTsServerStarted: Event<void>;
onTsServerStarted: Event<API>;
onProjectLanguageServiceStateChanged: Event<Proto.ProjectLanguageServiceStateEventBody>;
onDidBeginInstallTypings: Event<Proto.BeginInstallTypesEventBody>;
onDidEndInstallTypings: Event<Proto.EndInstallTypesEventBody>;
Expand Down Expand Up @@ -58,5 +58,6 @@ export interface ITypeScriptServiceClient {
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: string, args: any, expectedResult: boolean | CancellationToken, token?: CancellationToken): Promise<any>;
}
11 changes: 8 additions & 3 deletions extensions/typescript/src/typescriptServiceClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
private requestQueue: RequestQueue;
private callbacks: CallbackMap;

private readonly _onTsServerStarted = new EventEmitter<void>();
private readonly _onTsServerStarted = new EventEmitter<API>();
private readonly _onProjectLanguageServiceStateChanged = new EventEmitter<Proto.ProjectLanguageServiceStateEventBody>();
private readonly _onDidBeginInstallTypings = new EventEmitter<Proto.BeginInstallTypesEventBody>();
private readonly _onDidEndInstallTypings = new EventEmitter<Proto.EndInstallTypesEventBody>();
Expand Down Expand Up @@ -254,6 +254,11 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
}

public dispose() {
this._onTsServerStarted.dispose();
this._onDidBeginInstallTypings.dispose();
this._onDidEndInstallTypings.dispose();
this._onTypesInstallerInitializationFailed.dispose();

if (this.servicePromise) {
this.servicePromise.then(childProcess => {
childProcess.kill();
Expand Down Expand Up @@ -285,7 +290,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
}
}

get onTsServerStarted(): Event<void> {
get onTsServerStarted(): Event<API> {
return this._onTsServerStarted.event;
}

Expand Down Expand Up @@ -425,7 +430,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient

this._onReady!.resolve();
resolve(handle);
this._onTsServerStarted.fire();
this._onTsServerStarted.fire(currentVersion.version);

this.serviceStarted(resendModels);
});
Expand Down
5 changes: 5 additions & 0 deletions extensions/typescript/src/utils/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,9 @@ export default class API {
public has270Features(): boolean {
return semver.gte(this.version, '2.7.0');
}

@memoize
public has280Features(): boolean {
return semver.gte(this.version, '2.8.0');
}
}
9 changes: 8 additions & 1 deletion extensions/typescript/src/utils/languageModeIds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@
* 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 const jsxTags = 'jsx-tags';


export function isSupportedLanguageMode(doc: vscode.TextDocument) {
return vscode.languages.match([typescript, typescriptreact, javascript, javascriptreact], doc) > 0;
}
5 changes: 1 addition & 4 deletions extensions/typescript/src/utils/managedFileContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';
import * as languageModeIds from './languageModeIds';
import { isSupportedLanguageMode } from './languageModeIds';

/**
* When clause context set when the current file is managed by vscode's built-in typescript extension.
Expand Down Expand Up @@ -46,6 +46,3 @@ export default class ManagedFileContextManager {
}
}

function isSupportedLanguageMode(doc: vscode.TextDocument) {
return vscode.languages.match([languageModeIds.typescript, languageModeIds.typescriptreact, languageModeIds.javascript, languageModeIds.javascriptreact], doc) > 0;
}