Skip to content

Commit 33887a6

Browse files
authored
Merge pull request #139 from atom-community/code-actions
2 parents 21aff92 + a548bdf commit 33887a6

9 files changed

+400
-113
lines changed

lib/adapters/code-action-adapter.ts

+45-32
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
import type * as atomIde from "atom-ide-base"
2+
import * as linter from "atom/linter"
23
import LinterPushV2Adapter from "./linter-push-v2-adapter"
4+
/* eslint-disable import/no-deprecated */
5+
import IdeDiagnosticAdapter from "./diagnostic-adapter"
36
import assert = require("assert")
47
import Convert from "../convert"
58
import ApplyEditAdapter from "./apply-edit-adapter"
69
import {
710
CodeAction,
811
CodeActionParams,
912
Command,
13+
Diagnostic,
1014
LanguageClientConnection,
1115
ServerCapabilities,
1216
WorkspaceEdit,
1317
} from "../languageclient"
1418
import { Range, TextEditor } from "atom"
15-
import CommandExecutionAdapter from "./command-execution-adapter"
1619

1720
export default class CodeActionAdapter {
1821
/** @returns A {Boolean} indicating this adapter can adapt the server based on the given serverCapabilities. */
@@ -28,24 +31,24 @@ export default class CodeActionAdapter {
2831
* @param serverCapabilities The {ServerCapabilities} of the language server that will be used.
2932
* @param editor The Atom {TextEditor} containing the diagnostics.
3033
* @param range The Atom {Range} to fetch code actions for.
31-
* @param diagnostics An {Array<atomIde$Diagnostic>} to fetch code actions for. This is typically a list of
32-
* diagnostics intersecting `range`.
34+
* @param linterMessages An {Array<linter$Message>} to fetch code actions for. This is typically a list of messages
35+
* intersecting `range`.
3336
* @returns A {Promise} of an {Array} of {atomIde$CodeAction}s to display.
3437
*/
3538
public static async getCodeActions(
3639
connection: LanguageClientConnection,
3740
serverCapabilities: ServerCapabilities,
38-
linterAdapter: LinterPushV2Adapter | undefined,
41+
linterAdapter: LinterPushV2Adapter | IdeDiagnosticAdapter | undefined,
3942
editor: TextEditor,
4043
range: Range,
41-
diagnostics: atomIde.Diagnostic[]
44+
linterMessages: linter.Message[] | atomIde.Diagnostic[]
4245
): Promise<atomIde.CodeAction[]> {
4346
if (linterAdapter == null) {
4447
return []
4548
}
4649
assert(serverCapabilities.codeActionProvider, "Must have the textDocument/codeAction capability")
4750

48-
const params = CodeActionAdapter.createCodeActionParams(linterAdapter, editor, range, diagnostics)
51+
const params = createCodeActionParams(linterAdapter, editor, range, linterMessages)
4952
const actions = await connection.codeAction(params)
5053
if (actions === null) {
5154
return []
@@ -81,34 +84,44 @@ export default class CodeActionAdapter {
8184

8285
private static async executeCommand(command: any, connection: LanguageClientConnection): Promise<void> {
8386
if (Command.is(command)) {
84-
await CommandExecutionAdapter.executeCommand(connection, command.command, command.arguments)
87+
await connection.executeCommand({
88+
command: command.command,
89+
arguments: command.arguments,
90+
})
8591
}
8692
}
93+
}
8794

88-
private static createCodeActionParams(
89-
linterAdapter: LinterPushV2Adapter,
90-
editor: TextEditor,
91-
range: Range,
92-
diagnostics: atomIde.Diagnostic[]
93-
): CodeActionParams {
94-
return {
95-
textDocument: Convert.editorToTextDocumentIdentifier(editor),
96-
range: Convert.atomRangeToLSRange(range),
97-
context: {
98-
diagnostics: diagnostics.map((diagnostic) => {
99-
// Retrieve the stored diagnostic code if it exists.
100-
// Until the Linter API provides a place to store the code,
101-
// there's no real way for the code actions API to give it back to us.
102-
const converted = Convert.atomIdeDiagnosticToLSDiagnostic(diagnostic)
103-
if (diagnostic.range != null && diagnostic.text != null) {
104-
const code = linterAdapter.getDiagnosticCode(editor, diagnostic.range, diagnostic.text)
105-
if (code != null) {
106-
converted.code = code
107-
}
108-
}
109-
return converted
110-
}),
111-
},
112-
}
95+
function createCodeActionParams(
96+
linterAdapter: LinterPushV2Adapter | IdeDiagnosticAdapter,
97+
editor: TextEditor,
98+
range: Range,
99+
linterMessages: linter.Message[] | atomIde.Diagnostic[]
100+
): CodeActionParams {
101+
let diagnostics: Diagnostic[]
102+
if (linterMessages.length === 0) {
103+
diagnostics = []
104+
} else {
105+
// TODO compile time dispatch using function names
106+
diagnostics = areLinterMessages(linterMessages)
107+
? linterAdapter.getLSDiagnosticsForMessages(linterMessages as linter.Message[])
108+
: (linterAdapter as IdeDiagnosticAdapter).getLSDiagnosticsForIdeDiagnostics(
109+
linterMessages as atomIde.Diagnostic[],
110+
editor
111+
)
112+
}
113+
return {
114+
textDocument: Convert.editorToTextDocumentIdentifier(editor),
115+
range: Convert.atomRangeToLSRange(range),
116+
context: {
117+
diagnostics,
118+
},
119+
}
120+
}
121+
122+
function areLinterMessages(linterMessages: linter.Message[] | atomIde.Diagnostic[]): boolean {
123+
if ("excerpt" in linterMessages[0]) {
124+
return true
113125
}
126+
return false
114127
}

lib/adapters/diagnostic-adapter.ts

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import * as atomIde from "atom-ide-base"
2+
import * as atom from "atom"
3+
import * as ls from "../languageclient"
4+
import Convert from "../convert"
5+
import LinterPushV2Adapter from "./linter-push-v2-adapter"
6+
7+
/** @deprecated Use Linter V2 service */
8+
export type DiagnosticCode = number | string
9+
10+
/** @deprecated Use Linter V2 service */
11+
export default class IdeDiagnosticAdapter extends LinterPushV2Adapter {
12+
private _diagnosticCodes: Map<string, Map<string, DiagnosticCode | null>> = new Map()
13+
14+
/**
15+
* Public: Capture the diagnostics sent from a langguage server, convert them to the Linter V2 format and forward them
16+
* on to any attached {V2IndieDelegate}s.
17+
*
18+
* @deprecated Use Linter V2 service
19+
* @param params The {PublishDiagnosticsParams} received from the language server that should be captured and
20+
* forwarded on to any attached {V2IndieDelegate}s.
21+
*/
22+
public captureDiagnostics(params: ls.PublishDiagnosticsParams): void {
23+
const path = Convert.uriToPath(params.uri)
24+
const codeMap = new Map()
25+
const messages = params.diagnostics.map((d) => {
26+
const linterMessage = this.diagnosticToV2Message(path, d)
27+
codeMap.set(getCodeKey(linterMessage.location.position, d.message), d.code)
28+
return linterMessage
29+
})
30+
this._diagnosticMap.set(path, messages)
31+
this._diagnosticCodes.set(path, codeMap)
32+
this._indies.forEach((i) => i.setMessages(path, messages))
33+
}
34+
35+
/**
36+
* Public: get diagnostics for the given linter messages
37+
*
38+
* @deprecated Use Linter V2 service
39+
* @param linterMessages An array of linter {V2Message}
40+
* @param editor
41+
* @returns An array of LS {Diagnostic[]}
42+
*/
43+
public getLSDiagnosticsForIdeDiagnostics(
44+
diagnostics: atomIde.Diagnostic[],
45+
editor: atom.TextEditor
46+
): ls.Diagnostic[] {
47+
return diagnostics.map((diagnostic) => this.getLSDiagnosticForIdeDiagnostic(diagnostic, editor))
48+
}
49+
50+
/**
51+
* Public: Get the {Diagnostic} that is associated with the given {atomIde.Diagnostic}.
52+
*
53+
* @deprecated Use Linter V2 service
54+
* @param diagnostic The {atomIde.Diagnostic} object to fetch the {Diagnostic} for.
55+
* @param editor
56+
* @returns The associated {Diagnostic}.
57+
*/
58+
public getLSDiagnosticForIdeDiagnostic(diagnostic: atomIde.Diagnostic, editor: atom.TextEditor): ls.Diagnostic {
59+
// Retrieve the stored diagnostic code if it exists.
60+
// Until the Linter API provides a place to store the code,
61+
// there's no real way for the code actions API to give it back to us.
62+
const converted = atomIdeDiagnosticToLSDiagnostic(diagnostic)
63+
if (diagnostic.range != null && diagnostic.text != null) {
64+
const code = this.getDiagnosticCode(editor, diagnostic.range, diagnostic.text)
65+
if (code != null) {
66+
converted.code = code
67+
}
68+
}
69+
return converted
70+
}
71+
72+
/**
73+
* Private: Get the recorded diagnostic code for a range/message. Diagnostic codes are tricky because there's no
74+
* suitable place in the Linter API for them. For now, we'll record the original code for each range/message
75+
* combination and retrieve it when needed (e.g. for passing back into code actions)
76+
*/
77+
private getDiagnosticCode(editor: atom.TextEditor, range: atom.Range, text: string): DiagnosticCode | null {
78+
const path = editor.getPath()
79+
if (path != null) {
80+
const diagnosticCodes = this._diagnosticCodes.get(path)
81+
if (diagnosticCodes != null) {
82+
return diagnosticCodes.get(getCodeKey(range, text)) || null
83+
}
84+
}
85+
return null
86+
}
87+
}
88+
89+
/** @deprecated Use Linter V2 service */
90+
export function atomIdeDiagnosticToLSDiagnostic(diagnostic: atomIde.Diagnostic): ls.Diagnostic {
91+
// TODO: support diagnostic codes and codeDescriptions
92+
// TODO!: support data
93+
return {
94+
range: Convert.atomRangeToLSRange(diagnostic.range),
95+
severity: diagnosticTypeToLSSeverity(diagnostic.type),
96+
source: diagnostic.providerName,
97+
message: diagnostic.text || "",
98+
}
99+
}
100+
101+
/** @deprecated Use Linter V2 service */
102+
export function diagnosticTypeToLSSeverity(type: atomIde.DiagnosticType): ls.DiagnosticSeverity {
103+
switch (type) {
104+
case "Error":
105+
return ls.DiagnosticSeverity.Error
106+
case "Warning":
107+
return ls.DiagnosticSeverity.Warning
108+
case "Info":
109+
return ls.DiagnosticSeverity.Information
110+
default:
111+
throw Error(`Unexpected diagnostic type ${type}`)
112+
}
113+
}
114+
115+
function getCodeKey(range: atom.Range, text: string): string {
116+
return ([] as any[]).concat(...range.serialize(), text).join(",")
117+
}

0 commit comments

Comments
 (0)