diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..0a56ba75 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +* text=auto + +LICENSE.txt eol=crlf +ThirdPartyNotices.txt eol=crlf + +*.bat eol=crlf +*.cmd eol=crlf +*.ps1 eol=lf +*.sh eol=lf \ No newline at end of file diff --git a/client/package.json b/client/package.json index f7de8880..7572eeda 100644 --- a/client/package.json +++ b/client/package.json @@ -5,7 +5,7 @@ "author": "Microsoft Corporation", "license": "MIT", "engines": { - "vscode": "^1.22" + "vscode": "^1.23" }, "repository": { "type": "git", diff --git a/client/src/client.ts b/client/src/client.ts index f2b040e3..dba61aa7 100644 --- a/client/src/client.ts +++ b/client/src/client.ts @@ -10,9 +10,9 @@ import { FileSystemWatcher as VFileSystemWatcher, DiagnosticCollection, Diagnostic as VDiagnostic, Uri, ProviderResult, CancellationToken, Position as VPosition, Location as VLocation, Range as VRange, CompletionItem as VCompletionItem, CompletionList as VCompletionList, SignatureHelp as VSignatureHelp, Definition as VDefinition, DocumentHighlight as VDocumentHighlight, - SymbolInformation as VSymbolInformation, CodeActionContext as VCodeActionContext, Command as VCommand, CodeLens as VCodeLens, + SymbolInformation as VSymbolInformation, CodeActionContext as VCodeActionContext, Command as VCommand, CodeLens as VCodeLens, CodeActionKind as VCodeActionKind, FormattingOptions as VFormattingOptions, TextEdit as VTextEdit, WorkspaceEdit as VWorkspaceEdit, MessageItem, - Hover as VHover, + Hover as VHover, CodeAction as VCodeAction, DocumentLink as VDocumentLink, TextDocumentWillSaveEvent, WorkspaceFolder as VWorkspaceFolder, CompletionContext as VCompletionContext } from 'vscode'; @@ -53,7 +53,7 @@ import { DocumentLinkRequest, DocumentLinkResolveRequest, DocumentLinkRegistrationOptions, ExecuteCommandRequest, ExecuteCommandParams, ExecuteCommandRegistrationOptions, ApplyWorkspaceEditRequest, ApplyWorkspaceEditParams, ApplyWorkspaceEditResponse, - MarkupKind, SymbolKind, CompletionItemKind + MarkupKind, SymbolKind, CompletionItemKind, Command } from 'vscode-languageserver-protocol'; import { ColorProviderMiddleware } from './colorProvider'; @@ -359,7 +359,7 @@ export interface ProvideWorkspaceSymbolsSignature { } export interface ProvideCodeActionsSignature { - (document: TextDocument, range: VRange, context: VCodeActionContext, token: CancellationToken): ProviderResult; + (document: TextDocument, range: VRange, context: VCodeActionContext, token: CancellationToken): ProviderResult<(VCommand | VCodeAction)[]>; } export interface ProvideCodeLensesSignature { @@ -430,7 +430,7 @@ export interface _Middleware { provideDocumentHighlights?: (this: void, document: TextDocument, position: VPosition, token: CancellationToken, next: ProvideDocumentHighlightsSignature) => ProviderResult; provideDocumentSymbols?: (this: void, document: TextDocument, token: CancellationToken, next: ProvideDocumentSymbolsSignature) => ProviderResult; provideWorkspaceSymbols?: (this: void, query: string, token: CancellationToken, next: ProvideWorkspaceSymbolsSignature) => ProviderResult; - provideCodeActions?: (this: void, document: TextDocument, range: VRange, context: VCodeActionContext, token: CancellationToken, next: ProvideCodeActionsSignature) => ProviderResult; + provideCodeActions?: (this: void, document: TextDocument, range: VRange, context: VCodeActionContext, token: CancellationToken, next: ProvideCodeActionsSignature) => ProviderResult<(VCommand | VCodeAction)[]>; provideCodeLenses?: (this: void, document: TextDocument, token: CancellationToken, next: ProvideCodeLensesSignature) => ProviderResult; resolveCodeLens?: (this: void, codeLens: VCodeLens, token: CancellationToken, next: ResolveCodeLensSignature) => ProviderResult; provideDocumentFormattingEdits?: (this: void, document: TextDocument, options: VFormattingOptions, token: CancellationToken, next: ProvideDocumentFormattingEditsSignature) => ProviderResult; @@ -1671,7 +1671,22 @@ class CodeActionFeature extends TextDocumentFeature { + if (values === null) { + return undefined; + } + let result: (VCommand | VCodeAction)[] = []; + for (let item of values) { + if (Command.is(item)) { + result.push(client.protocol2CodeConverter.asCommand(item)) + } else { + result.push(client.protocol2CodeConverter.asCodeAction(item)); + }; + } + return result; + }, (error) => { client.logFailedRequest(CodeActionRequest.type, error); return Promise.resolve([]); @@ -1702,7 +1729,7 @@ class CodeActionFeature extends TextDocumentFeature => { + provideCodeActions: (document: TextDocument, range: VRange, context: VCodeActionContext, token: CancellationToken): ProviderResult<(VCommand | VCodeAction)[]> => { return middleware.provideCodeActions ? middleware.provideCodeActions(document, range, context, token, provideCodeActions) : provideCodeActions(document, range, context, token); diff --git a/client/src/protocolConverter.ts b/client/src/protocolConverter.ts index e2d5e4c3..5c8efb69 100644 --- a/client/src/protocolConverter.ts +++ b/client/src/protocolConverter.ts @@ -92,6 +92,10 @@ export interface Converter { asCommands(items: undefined | null): undefined asCommands(items: ls.Command[] | undefined | null): code.Command[] | undefined; + asCodeAction(item: ls.CodeAction): code.CodeAction; + asCodeAction(item: undefined | null): undefined; + asCodeAction(item: ls.CodeAction | undefined | null): code.CodeAction | undefined; + asCodeLens(item: ls.CodeLens): code.CodeLens; asCodeLens(item: undefined | null): undefined; asCodeLens(item: ls.CodeLens | undefined | null): code.CodeLens | undefined; @@ -494,6 +498,50 @@ export function createConverter(uriConverter?: URIConverter): Converter { return items.map(asCommand); } + const kindMapping: Map = new Map(); + kindMapping.set('', code.CodeActionKind.Empty); + kindMapping.set(ls.CodeActionKind.QuickFix, code.CodeActionKind.QuickFix); + kindMapping.set(ls.CodeActionKind.Refactor, code.CodeActionKind.Refactor); + kindMapping.set(ls.CodeActionKind.RefactorExtract, code.CodeActionKind.RefactorExtract); + kindMapping.set(ls.CodeActionKind.RefactorInline, code.CodeActionKind.RefactorInline); + kindMapping.set(ls.CodeActionKind.RefactorRewrite, code.CodeActionKind.RefactorRewrite); + kindMapping.set(ls.CodeActionKind.Source, code.CodeActionKind.Source); + kindMapping.set(ls.CodeActionKind.SourceOrganizeImports, code.CodeActionKind.SourceOrganizeImports); + + function asCodeActionKind(item: null | undefined): undefined; + function asCodeActionKind(item: ls.CodeActionKind): code.CodeActionKind; + function asCodeActionKind(item: ls.CodeActionKind | null | undefined): code.CodeActionKind | undefined; + function asCodeActionKind(item: ls.CodeActionKind | null | undefined): code.CodeActionKind | undefined { + if (item === void 0 || item === null) { + return undefined + } + let result: code.CodeActionKind | undefined = kindMapping.get(item); + if (result) { + return result; + } + let parts = item.split('.'); + result = code.CodeActionKind.Empty; + for (let part of parts) { + result = result.append(part); + } + return result; + } + + function asCodeAction(item: ls.CodeAction): code.CodeAction; + function asCodeAction(item: undefined | null): undefined; + function asCodeAction(item: ls.CodeAction | undefined | null): code.CodeAction | undefined; + function asCodeAction(item: ls.CodeAction | undefined | null): code.CodeAction | undefined { + if (item === void 0 || item === null) { + return undefined; + } + let result = new code.CodeAction(item.title); + if (item.kind !== void 0) { result.kind = asCodeActionKind(item.kind); } + if (item.diagnostics) { result.diagnostics = asDiagnostics(item.diagnostics); } + if (item.edit) { result.edit = asWorkspaceEdit(item.edit); } + if (item.command) { result.command = asCommand(item.command); } + return result; + } + function asCodeLens(item: ls.CodeLens): code.CodeLens; function asCodeLens(item: undefined | null): undefined; function asCodeLens(item: ls.CodeLens | undefined | null): code.CodeLens | undefined; @@ -583,6 +631,7 @@ export function createConverter(uriConverter?: URIConverter): Converter { asSymbolInformation, asCommand, asCommands, + asCodeAction, asCodeLens, asCodeLenses, asWorkspaceEdit, diff --git a/protocol/src/protocol.ts b/protocol/src/protocol.ts index 8f30c3cd..4075edf4 100644 --- a/protocol/src/protocol.ts +++ b/protocol/src/protocol.ts @@ -15,7 +15,7 @@ import { CompletionItem, CompletionList, Hover, SignatureHelp, Definition, ReferenceContext, DocumentHighlight, DocumentSymbolParams, SymbolInformation, CodeLens, CodeActionContext, FormattingOptions, DocumentLink, MarkupKind, - SymbolKind, CompletionItemKind + SymbolKind, CompletionItemKind, CodeAction, CodeActionKind } from 'vscode-languageserver-types'; import { ImplementationRequest, ImplementationClientCapabilities, ImplementationServerCapabilities } from './protocol.implementation'; @@ -460,6 +460,27 @@ export interface TextDocumentClientCapabilities { * Whether code action supports dynamic registration. */ dynamicRegistration?: boolean; + + /** + * The client support code action literals as a valid + * response of the `textDocument/codeAction` request. + */ + codeActionLiteralSupport?: { + /** + * The code action kind is support with the following value + * set. + */ + codeActionKind: { + + /** + * The code action kind values the client supports. When this + * property exists the client also guarantees that it will + * handle values outside its set gracefully and falls back + * to a default value when unknown. + */ + valueSet: CodeActionKind[]; + }; + }; }; /** @@ -1522,7 +1543,7 @@ export interface CodeActionParams { * A request to provide commands for the given text document and range. */ export namespace CodeActionRequest { - export const type = new RequestType('textDocument/codeAction'); + export const type = new RequestType('textDocument/codeAction'); } //---- Code Lens Provider ------------------------------------------- diff --git a/server/src/main.ts b/server/src/main.ts index faba2c3f..3e0279d6 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -44,7 +44,7 @@ import { ExecuteCommandRequest, ExecuteCommandParams, ApplyWorkspaceEditRequest, ApplyWorkspaceEditParams, ApplyWorkspaceEditResponse, ClientCapabilities, ServerCapabilities, ProtocolConnection, createProtocolConnection, TypeDefinitionRequest, ImplementationRequest, - DocumentColorRequest, DocumentColorParams, ColorInformation, ColorPresentationParams, ColorPresentation, ColorPresentationRequest + DocumentColorRequest, DocumentColorParams, ColorInformation, ColorPresentationParams, ColorPresentation, ColorPresentationRequest, CodeAction } from 'vscode-languageserver-protocol'; import { Configuration, ConfigurationFeature } from './configuration'; @@ -1249,7 +1249,7 @@ export interface Connection): void; + onCodeAction(handler: RequestHandler): void; /** * Compute a list of [lenses](#CodeLens). This call should return as fast as possible and if diff --git a/types/src/main.ts b/types/src/main.ts index 889c0269..7af02b28 100644 --- a/types/src/main.ts +++ b/types/src/main.ts @@ -339,7 +339,7 @@ export namespace Command { */ export function is(value: any): value is Command { let candidate = value as Command; - return Is.defined(candidate) && Is.string(candidate.title) && Is.string(candidate.title); + return Is.defined(candidate) && Is.string(candidate.title) && Is.string(candidate.command); } } @@ -446,6 +446,15 @@ export interface WorkspaceEdit { documentChanges?: TextDocumentEdit[]; } +export namespace WorkspaceEdit { + export function is(value: any): value is WorkspaceEdit { + let candidate: WorkspaceEdit = value; + return candidate && + (candidate.changes !== void 0 || candidate.documentChanges !== void 0) && + (candidate.documentChanges === void 0 || Is.typedArray(candidate.documentChanges, TextDocumentEdit.is)); + } +} + /** * A change to capture text edits for existing resources. */ @@ -1341,6 +1350,83 @@ export interface WorkspaceSymbolParams { query: string; } +/** + * The kind of a code action. + * + * Kinds are a hierarchical list of identifiers separated by `.`, e.g. `"refactor.extract.function"`. + * + * The set of kinds is open and client needs to announce the kinds it supports to the server during + * initialization. + */ +export type CodeActionKind = string; + +/** + * A set of predefined code action kinds + */ +export namespace CodeActionKind { + /** + * Base kind for quickfix actions: 'quickfix' + */ + export const QuickFix: CodeActionKind = 'quickfix'; + + /** + * Base kind for refactoring actions: 'refactor' + */ + export const Refactor: CodeActionKind = 'refactor'; + + /** + * Base kind for refactoring extraction actions: 'refactor.extract' + * + * Example extract actions: + * + * - Extract method + * - Extract function + * - Extract variable + * - Extract interface from class + * - ... + */ + export const RefactorExtract: CodeActionKind = 'refactor.extract'; + + /** + * Base kind for refactoring inline actions: 'refactor.inline' + * + * Example inline actions: + * + * - Inline function + * - Inline variable + * - Inline constant + * - ... + */ + export const RefactorInline: CodeActionKind = 'refactor.inline'; + + /** + * Base kind for refactoring rewrite actions: 'refactor.rewrite' + * + * Example rewrite actions: + * + * - Convert JavaScript function to class + * - Add or remove parameter + * - Encapsulate field + * - Make method static + * - Move method to base class + * - ... + */ + export const RefactorRewrite: CodeActionKind = 'refactor.rewrite'; + + /** + * Base kind for source actions: `source` + * + * Source code actions apply to the entire file. + */ + export const Source: CodeActionKind = 'source'; + + /** + * Base kind for an organize imports source action: `source.organizeImports` + */ + export const SourceOrganizeImports: CodeActionKind = 'source.organizeImports'; +} + + /** * Contains additional diagnostic information about the context in which * a [code action](#CodeActionProvider.provideCodeActions) is run. @@ -1350,6 +1436,14 @@ export interface CodeActionContext { * An array of diagnostics. */ diagnostics: Diagnostic[]; + + /** + * Requested kind of actions to return. + * + * Actions not of this kind are filtered out by the client before being shown. So servers + * can omit computing them. + */ + only?: CodeActionKind[]; } /** @@ -1360,15 +1454,69 @@ export namespace CodeActionContext { /** * Creates a new CodeActionContext literal. */ - export function create(diagnostics: Diagnostic[]): CodeActionContext { - return { diagnostics }; + export function create(diagnostics: Diagnostic[], only?: CodeActionKind[]): CodeActionContext { + let result: CodeActionContext = { diagnostics }; + if (only !== void 0 && only !== null) { + result.only = only; + } + return result; } /** * Checks whether the given literal conforms to the [CodeActionContext](#CodeActionContext) interface. */ export function is(value: any): value is CodeActionContext { let candidate = value as CodeActionContext; - return Is.defined(candidate) && Is.typedArray(candidate.diagnostics, Diagnostic.is); + return Is.defined(candidate) && Is.typedArray(candidate.diagnostics, Diagnostic.is) && (candidate.only === void 0 || Is.typedArray(candidate.only, Is.string)); + } +} + +/** + * A code action represents a change that can be performed in code, e.g. to fix a problem or + * to refactor code. + * + * A CodeAction must set either `edit` and/or a `command`. If both are supplied, the `edit` is applied first, then the `command` is executed. + */ +export type CodeAction = { + + /** + * A short, human-readable, title for this code action. + */ + title: string; + + /** + * The kind of the code action. + * + * Used to filter code actions. + */ + kind?: CodeActionKind; + + /** + * The diagnostics that this code action resolves. + */ + diagnostics?: Diagnostic[]; + + /** + * The workspace edit this code action performs. + */ + edit?: WorkspaceEdit; + + /** + * A command this code action executes. If a code action + * provides a edit and a command, first the edit is + * executed and then the command. + */ + command?: Command; +} + +export namespace CodeAction { + export function is(value: any): value is CodeAction { + let candidate: CodeAction = value; + return candidate && Is.string(candidate.title) && + (candidate.diagnostics === void 0 || Is.typedArray(candidate.diagnostics, Diagnostic.is)) && + (candidate.kind === void 0 || Is.string(candidate.kind)) && + (candidate.edit !== void 0 || candidate.command !== void 0) && + (candidate.command === void 0 || Command.is(candidate.command)) && + (candidate.edit === void 0 || WorkspaceEdit.is(candidate.edit)); } }