Skip to content

Commit c40296e

Browse files
committed
Add support for completions with code actions
1 parent 096c3a8 commit c40296e

File tree

5 files changed

+66
-5
lines changed

5 files changed

+66
-5
lines changed

lib/client/client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const commandWithResponseMap: {readonly [K in CommandsWithResponse]: true} = {
4242
organizeImports: true,
4343
signatureHelp: true,
4444
getEditsForFileRename: true,
45+
applyCodeActionCommand: true,
4546
}
4647

4748
const commandsWithMultistepMap: {readonly [K in CommandsWithMultistep]: true} = {

lib/client/commandArgsResponseMap.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ export interface CommandArgResponseMap {
3838
exit: () => void
3939
signatureHelp: (x: p.SignatureHelpRequestArgs) => p.SignatureHelpResponse
4040
getEditsForFileRename: (x: p.GetEditsForFileRenameRequestArgs) => p.GetEditsForFileRenameResponse
41+
applyCodeActionCommand: (
42+
x: p.ApplyCodeActionCommandRequestArgs,
43+
) => p.ApplyCodeActionCommandResponse
4144
}
4245

4346
export type AllTSClientCommands = keyof CommandArgResponseMap

lib/main/atom/autoCompleteProvider.ts

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,23 @@
22
import * as Atom from "atom"
33
import * as ACP from "atom/autocomplete-plus"
44
import * as fuzzaldrin from "fuzzaldrin"
5+
import {CompletionEntryDetails, CompletionEntryIdentifier} from "typescript/lib/protocol"
56
import {GetClientFunction, TSClient} from "../../client"
7+
import {handlePromise} from "../../utils"
8+
import {ApplyEdits} from "../pluginManager"
9+
import {codeActionTemplate} from "./codeActionTemplate"
610
import {FileLocationQuery, spanToRange, typeScriptScopes} from "./utils"
11+
import {selectListView} from "./views/simpleSelectionView"
712

813
type SuggestionWithDetails = ACP.TextSuggestion & {
914
replacementRange?: Atom.Range
1015
isMemberCompletion?: boolean
16+
identifier?: CompletionEntryIdentifier | string
17+
hasAction?: boolean
1118
}
1219

1320
interface Details {
21+
details: CompletionEntryDetails
1422
rightLabel: string
1523
description?: string
1624
}
@@ -41,7 +49,7 @@ export class AutocompleteProvider implements ACP.AutocompleteProvider {
4149
details: Map<string, Details>
4250
}
4351

44-
constructor(private getClient: GetClientFunction) {}
52+
constructor(private getClient: GetClientFunction, private applyEdits: ApplyEdits) {}
4553

4654
public async getSuggestions(opts: ACP.SuggestionsRequestedEvent): Promise<ACP.AnySuggestion[]> {
4755
const location = getLocationQuery(opts)
@@ -91,10 +99,45 @@ export class AutocompleteProvider implements ACP.AutocompleteProvider {
9199
}
92100
}
93101

102+
public onDidInsertSuggestion(evt: ACP.SuggestionInsertedEvent) {
103+
const s = evt.suggestion as SuggestionWithDetails
104+
if (!s.hasAction) return
105+
if (!this.lastSuggestions) return
106+
const client = this.lastSuggestions.client
107+
let details = this.getDetailsFromCache(s)
108+
handlePromise(
109+
(async () => {
110+
if (!details) details = await this.getAdditionalDetails(s)
111+
if (!details?.details.codeActions) return
112+
let action
113+
if (details.details.codeActions.length === 1) {
114+
action = details.details.codeActions[0]
115+
} else {
116+
action = await selectListView({
117+
items: details.details.codeActions,
118+
itemTemplate: codeActionTemplate,
119+
itemFilterKey: "description",
120+
})
121+
}
122+
if (!action) return
123+
await this.applyEdits(action.changes)
124+
if (!action.commands) return
125+
await Promise.all(
126+
action.commands.map((cmd) =>
127+
client.execute("applyCodeActionCommand", {
128+
command: cmd,
129+
}),
130+
),
131+
)
132+
})(),
133+
)
134+
}
135+
94136
private async getAdditionalDetails(suggestion: SuggestionWithDetails) {
137+
if (suggestion.identifier === undefined) return null
95138
if (!this.lastSuggestions) return null
96139
const reply = await this.lastSuggestions.client.execute("completionEntryDetails", {
97-
entryNames: [suggestion.displayText!],
140+
entryNames: [suggestion.identifier],
98141
...this.lastSuggestions.location,
99142
})
100143
if (!reply.body) return null
@@ -111,11 +154,17 @@ export class AutocompleteProvider implements ACP.AutocompleteProvider {
111154
) {
112155
parts = parts.slice(3)
113156
}
114-
const rightLabel = parts.map((d) => d.text).join("")
157+
let rightLabel = parts.map((d) => d.text).join("")
158+
const actionDesc =
159+
suggestion.hasAction && details.codeActions?.length === 1
160+
? `${details.codeActions[0].description}\n\n`
161+
: ""
162+
if (actionDesc) rightLabel = actionDesc
115163
const description =
164+
actionDesc +
116165
details.displayParts.map((d) => d.text).join("") +
117166
(details.documentation ? "\n\n" + details.documentation.map((d) => d.text).join(" ") : "")
118-
this.lastSuggestions.details.set(suggestion.displayText!, {rightLabel, description})
167+
this.lastSuggestions.details.set(suggestion.displayText!, {details, rightLabel, description})
119168
return {
120169
...suggestion,
121170
details,
@@ -264,6 +313,8 @@ function completionEntryToSuggestion(
264313
replacementRange: entry.replacementSpan ? spanToRange(entry.replacementSpan) : undefined,
265314
type: kindMap[entry.kind],
266315
isMemberCompletion,
316+
identifier: entry.source !== undefined ? {name: entry.name, source: entry.source} : entry.name,
317+
hasAction: entry.hasAction,
267318
}
268319
}
269320

lib/main/atom/codeActionTemplate.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import * as etch from "etch"
2+
import {CodeAction} from "typescript/lib/protocol"
3+
4+
export function codeActionTemplate(codeAction: CodeAction) {
5+
return <li>{codeAction.description}</li>
6+
}

lib/main/pluginManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ export class PluginManager {
224224

225225
// Registering an autocomplete provider
226226
public provideAutocomplete() {
227-
return [new AutocompleteProvider(this.getClient)]
227+
return [new AutocompleteProvider(this.getClient, this.applyEdits)]
228228
}
229229

230230
public provideIntentions() {

0 commit comments

Comments
 (0)