Organize imports for js and ts (#45237)
* Organize imports for js and ts Adds a new 'Organize Imports' command for js and ts. This command is only availible on TS 2.8+. We'll hold off on merging this PR until we pick up a TS 2.8 insiders build Fixes #45108 * Add keybinding for organize imports
This commit is contained in:
parent
3eff6ac72d
commit
5be9e9e7ee
10 changed files with 139 additions and 23 deletions
|
@ -36,6 +36,7 @@
|
|||
"onCommand:javascript.goToProjectConfig",
|
||||
"onCommand:typescript.goToProjectConfig",
|
||||
"onCommand:typescript.openTsServerLog",
|
||||
"onCommand:typescript.organizeImports",
|
||||
"onCommand:workbench.action.tasks.runTask"
|
||||
],
|
||||
"main": "./out/extension",
|
||||
|
@ -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": {
|
||||
|
@ -507,6 +520,10 @@
|
|||
{
|
||||
"command": "typescript.restartTsServer",
|
||||
"when": "typescript.isManagedFile"
|
||||
},
|
||||
{
|
||||
"command": "typescript.organizeImports",
|
||||
"when": "typescript.isManagedFile && typescript.canOrganizeImports"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -79,6 +87,7 @@ function createLazyClientHost(
|
|||
path => new Promise<boolean>(resolve => setTimeout(() => resolve(clientHost.handles(path)), 750)),
|
||||
context.workspaceState));
|
||||
});
|
||||
|
||||
return clientHost;
|
||||
});
|
||||
}
|
||||
|
@ -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(
|
||||
|
|
85
extensions/typescript/src/features/organizeImports.ts
Normal file
85
extensions/typescript/src/features/organizeImports.ts
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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>;
|
||||
|
@ -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>;
|
||||
}
|
|
@ -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>();
|
||||
|
@ -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();
|
||||
|
@ -285,7 +290,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
|
|||
}
|
||||
}
|
||||
|
||||
get onTsServerStarted(): Event<void> {
|
||||
get onTsServerStarted(): Event<API> {
|
||||
return this._onTsServerStarted.event;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue