diff options
Diffstat (limited to 'editors/code/src/commands')
-rw-r--r-- | editors/code/src/commands/analyzer_status.ts | 74 | ||||
-rw-r--r-- | editors/code/src/commands/apply_source_change.ts | 54 | ||||
-rw-r--r-- | editors/code/src/commands/cargo_watch.ts | 264 | ||||
-rw-r--r-- | editors/code/src/commands/expand_macro.ts | 103 | ||||
-rw-r--r-- | editors/code/src/commands/index.ts | 66 | ||||
-rw-r--r-- | editors/code/src/commands/inlay_hints.ts | 115 | ||||
-rw-r--r-- | editors/code/src/commands/join_lines.ts | 46 | ||||
-rw-r--r-- | editors/code/src/commands/line_buffer.ts | 16 | ||||
-rw-r--r-- | editors/code/src/commands/matching_brace.ts | 58 | ||||
-rw-r--r-- | editors/code/src/commands/on_enter.ts | 53 | ||||
-rw-r--r-- | editors/code/src/commands/parent_module.ts | 55 | ||||
-rw-r--r-- | editors/code/src/commands/runnables.ts | 214 | ||||
-rw-r--r-- | editors/code/src/commands/syntaxTree.ts | 76 | ||||
-rw-r--r-- | editors/code/src/commands/syntax_tree.ts | 104 | ||||
-rw-r--r-- | editors/code/src/commands/watch_status.ts | 63 |
15 files changed, 408 insertions, 953 deletions
diff --git a/editors/code/src/commands/analyzer_status.ts b/editors/code/src/commands/analyzer_status.ts index 2777ced24..cfe7d1af0 100644 --- a/editors/code/src/commands/analyzer_status.ts +++ b/editors/code/src/commands/analyzer_status.ts | |||
@@ -1,45 +1,20 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import { Server } from '../server'; | ||
3 | 2 | ||
4 | const statusUri = vscode.Uri.parse('rust-analyzer-status://status'); | 3 | import { Ctx, Cmd } from '../ctx'; |
5 | |||
6 | export class TextDocumentContentProvider | ||
7 | implements vscode.TextDocumentContentProvider { | ||
8 | public eventEmitter = new vscode.EventEmitter<vscode.Uri>(); | ||
9 | public syntaxTree: string = 'Not available'; | ||
10 | |||
11 | public provideTextDocumentContent( | ||
12 | _uri: vscode.Uri, | ||
13 | ): vscode.ProviderResult<string> { | ||
14 | const editor = vscode.window.activeTextEditor; | ||
15 | if (editor == null) { | ||
16 | return ''; | ||
17 | } | ||
18 | return Server.client.sendRequest<string>( | ||
19 | 'rust-analyzer/analyzerStatus', | ||
20 | null, | ||
21 | ); | ||
22 | } | ||
23 | |||
24 | get onDidChange(): vscode.Event<vscode.Uri> { | ||
25 | return this.eventEmitter.event; | ||
26 | } | ||
27 | } | ||
28 | |||
29 | let poller: NodeJS.Timer | null = null; | ||
30 | 4 | ||
31 | // Shows status of rust-analyzer (for debugging) | 5 | // Shows status of rust-analyzer (for debugging) |
6 | export function analyzerStatus(ctx: Ctx): Cmd { | ||
7 | let poller: NodeJS.Timer | null = null; | ||
8 | const tdcp = new TextDocumentContentProvider(ctx); | ||
32 | 9 | ||
33 | export function makeCommand(context: vscode.ExtensionContext) { | 10 | ctx.pushCleanup( |
34 | const textDocumentContentProvider = new TextDocumentContentProvider(); | ||
35 | context.subscriptions.push( | ||
36 | vscode.workspace.registerTextDocumentContentProvider( | 11 | vscode.workspace.registerTextDocumentContentProvider( |
37 | 'rust-analyzer-status', | 12 | 'rust-analyzer-status', |
38 | textDocumentContentProvider, | 13 | tdcp, |
39 | ), | 14 | ), |
40 | ); | 15 | ); |
41 | 16 | ||
42 | context.subscriptions.push({ | 17 | ctx.pushCleanup({ |
43 | dispose() { | 18 | dispose() { |
44 | if (poller != null) { | 19 | if (poller != null) { |
45 | clearInterval(poller); | 20 | clearInterval(poller); |
@@ -49,12 +24,9 @@ export function makeCommand(context: vscode.ExtensionContext) { | |||
49 | 24 | ||
50 | return async function handle() { | 25 | return async function handle() { |
51 | if (poller == null) { | 26 | if (poller == null) { |
52 | poller = setInterval( | 27 | poller = setInterval(() => tdcp.eventEmitter.fire(tdcp.uri), 1000); |
53 | () => textDocumentContentProvider.eventEmitter.fire(statusUri), | ||
54 | 1000, | ||
55 | ); | ||
56 | } | 28 | } |
57 | const document = await vscode.workspace.openTextDocument(statusUri); | 29 | const document = await vscode.workspace.openTextDocument(tdcp.uri); |
58 | return vscode.window.showTextDocument( | 30 | return vscode.window.showTextDocument( |
59 | document, | 31 | document, |
60 | vscode.ViewColumn.Two, | 32 | vscode.ViewColumn.Two, |
@@ -62,3 +34,31 @@ export function makeCommand(context: vscode.ExtensionContext) { | |||
62 | ); | 34 | ); |
63 | }; | 35 | }; |
64 | } | 36 | } |
37 | |||
38 | class TextDocumentContentProvider | ||
39 | implements vscode.TextDocumentContentProvider { | ||
40 | private ctx: Ctx; | ||
41 | uri = vscode.Uri.parse('rust-analyzer-status://status'); | ||
42 | eventEmitter = new vscode.EventEmitter<vscode.Uri>(); | ||
43 | |||
44 | constructor(ctx: Ctx) { | ||
45 | this.ctx = ctx; | ||
46 | } | ||
47 | |||
48 | provideTextDocumentContent( | ||
49 | _uri: vscode.Uri, | ||
50 | ): vscode.ProviderResult<string> { | ||
51 | const editor = vscode.window.activeTextEditor; | ||
52 | const client = this.ctx.client; | ||
53 | if (!editor || !client) return ''; | ||
54 | |||
55 | return client.sendRequest<string>( | ||
56 | 'rust-analyzer/analyzerStatus', | ||
57 | null, | ||
58 | ); | ||
59 | } | ||
60 | |||
61 | get onDidChange(): vscode.Event<vscode.Uri> { | ||
62 | return this.eventEmitter.event; | ||
63 | } | ||
64 | } | ||
diff --git a/editors/code/src/commands/apply_source_change.ts b/editors/code/src/commands/apply_source_change.ts deleted file mode 100644 index 8167398b1..000000000 --- a/editors/code/src/commands/apply_source_change.ts +++ /dev/null | |||
@@ -1,54 +0,0 @@ | |||
1 | import * as vscode from 'vscode'; | ||
2 | import * as lc from 'vscode-languageclient'; | ||
3 | |||
4 | import { Server } from '../server'; | ||
5 | |||
6 | export interface SourceChange { | ||
7 | label: string; | ||
8 | workspaceEdit: lc.WorkspaceEdit; | ||
9 | cursorPosition?: lc.TextDocumentPositionParams; | ||
10 | } | ||
11 | |||
12 | export async function handle(change: SourceChange) { | ||
13 | const wsEdit = Server.client.protocol2CodeConverter.asWorkspaceEdit( | ||
14 | change.workspaceEdit, | ||
15 | ); | ||
16 | let created; | ||
17 | let moved; | ||
18 | if (change.workspaceEdit.documentChanges) { | ||
19 | for (const docChange of change.workspaceEdit.documentChanges) { | ||
20 | if (lc.CreateFile.is(docChange)) { | ||
21 | created = docChange.uri; | ||
22 | } else if (lc.RenameFile.is(docChange)) { | ||
23 | moved = docChange.newUri; | ||
24 | } | ||
25 | } | ||
26 | } | ||
27 | const toOpen = created || moved; | ||
28 | const toReveal = change.cursorPosition; | ||
29 | await vscode.workspace.applyEdit(wsEdit); | ||
30 | if (toOpen) { | ||
31 | const toOpenUri = vscode.Uri.parse(toOpen); | ||
32 | const doc = await vscode.workspace.openTextDocument(toOpenUri); | ||
33 | await vscode.window.showTextDocument(doc); | ||
34 | } else if (toReveal) { | ||
35 | const uri = Server.client.protocol2CodeConverter.asUri( | ||
36 | toReveal.textDocument.uri, | ||
37 | ); | ||
38 | const position = Server.client.protocol2CodeConverter.asPosition( | ||
39 | toReveal.position, | ||
40 | ); | ||
41 | const editor = vscode.window.activeTextEditor; | ||
42 | if (!editor || editor.document.uri.toString() !== uri.toString()) { | ||
43 | return; | ||
44 | } | ||
45 | if (!editor.selection.isEmpty) { | ||
46 | return; | ||
47 | } | ||
48 | editor.selection = new vscode.Selection(position, position); | ||
49 | editor.revealRange( | ||
50 | new vscode.Range(position, position), | ||
51 | vscode.TextEditorRevealType.Default, | ||
52 | ); | ||
53 | } | ||
54 | } | ||
diff --git a/editors/code/src/commands/cargo_watch.ts b/editors/code/src/commands/cargo_watch.ts deleted file mode 100644 index ac62bdd48..000000000 --- a/editors/code/src/commands/cargo_watch.ts +++ /dev/null | |||
@@ -1,264 +0,0 @@ | |||
1 | import * as child_process from 'child_process'; | ||
2 | import * as path from 'path'; | ||
3 | import * as vscode from 'vscode'; | ||
4 | |||
5 | import { Server } from '../server'; | ||
6 | import { terminate } from '../utils/processes'; | ||
7 | import { LineBuffer } from './line_buffer'; | ||
8 | import { StatusDisplay } from './watch_status'; | ||
9 | |||
10 | import { | ||
11 | mapRustDiagnosticToVsCode, | ||
12 | RustDiagnostic, | ||
13 | } from '../utils/diagnostics/rust'; | ||
14 | import SuggestedFixCollection from '../utils/diagnostics/SuggestedFixCollection'; | ||
15 | import { areDiagnosticsEqual } from '../utils/diagnostics/vscode'; | ||
16 | |||
17 | export async function registerCargoWatchProvider( | ||
18 | subscriptions: vscode.Disposable[], | ||
19 | ): Promise<CargoWatchProvider | undefined> { | ||
20 | let cargoExists = false; | ||
21 | |||
22 | // Check if the working directory is valid cargo root path | ||
23 | const cargoTomlPath = path.join(vscode.workspace.rootPath!, 'Cargo.toml'); | ||
24 | const cargoTomlUri = vscode.Uri.file(cargoTomlPath); | ||
25 | const cargoTomlFileInfo = await vscode.workspace.fs.stat(cargoTomlUri); | ||
26 | |||
27 | if (cargoTomlFileInfo) { | ||
28 | cargoExists = true; | ||
29 | } | ||
30 | |||
31 | if (!cargoExists) { | ||
32 | vscode.window.showErrorMessage( | ||
33 | `Couldn\'t find \'Cargo.toml\' at ${cargoTomlPath}`, | ||
34 | ); | ||
35 | return; | ||
36 | } | ||
37 | |||
38 | const provider = new CargoWatchProvider(); | ||
39 | subscriptions.push(provider); | ||
40 | return provider; | ||
41 | } | ||
42 | |||
43 | export class CargoWatchProvider implements vscode.Disposable { | ||
44 | private readonly diagnosticCollection: vscode.DiagnosticCollection; | ||
45 | private readonly statusDisplay: StatusDisplay; | ||
46 | private readonly outputChannel: vscode.OutputChannel; | ||
47 | |||
48 | private suggestedFixCollection: SuggestedFixCollection; | ||
49 | private codeActionDispose: vscode.Disposable; | ||
50 | |||
51 | private cargoProcess?: child_process.ChildProcess; | ||
52 | |||
53 | constructor() { | ||
54 | this.diagnosticCollection = vscode.languages.createDiagnosticCollection( | ||
55 | 'rustc', | ||
56 | ); | ||
57 | this.statusDisplay = new StatusDisplay( | ||
58 | Server.config.cargoWatchOptions.command, | ||
59 | ); | ||
60 | this.outputChannel = vscode.window.createOutputChannel( | ||
61 | 'Cargo Watch Trace', | ||
62 | ); | ||
63 | |||
64 | // Track `rustc`'s suggested fixes so we can convert them to code actions | ||
65 | this.suggestedFixCollection = new SuggestedFixCollection(); | ||
66 | this.codeActionDispose = vscode.languages.registerCodeActionsProvider( | ||
67 | [{ scheme: 'file', language: 'rust' }], | ||
68 | this.suggestedFixCollection, | ||
69 | { | ||
70 | providedCodeActionKinds: | ||
71 | SuggestedFixCollection.PROVIDED_CODE_ACTION_KINDS, | ||
72 | }, | ||
73 | ); | ||
74 | } | ||
75 | |||
76 | public start() { | ||
77 | if (this.cargoProcess) { | ||
78 | vscode.window.showInformationMessage( | ||
79 | 'Cargo Watch is already running', | ||
80 | ); | ||
81 | return; | ||
82 | } | ||
83 | |||
84 | let args = | ||
85 | Server.config.cargoWatchOptions.command + ' --message-format json'; | ||
86 | if (Server.config.cargoWatchOptions.allTargets) { | ||
87 | args += ' --all-targets'; | ||
88 | } | ||
89 | if (Server.config.cargoWatchOptions.command.length > 0) { | ||
90 | // Excape the double quote string: | ||
91 | args += ' ' + Server.config.cargoWatchOptions.arguments; | ||
92 | } | ||
93 | // Windows handles arguments differently than the unix-likes, so we need to wrap the args in double quotes | ||
94 | if (process.platform === 'win32') { | ||
95 | args = '"' + args + '"'; | ||
96 | } | ||
97 | |||
98 | const ignoreFlags = Server.config.cargoWatchOptions.ignore.reduce( | ||
99 | (flags, pattern) => [...flags, '--ignore', pattern], | ||
100 | [] as string[], | ||
101 | ); | ||
102 | |||
103 | // Start the cargo watch with json message | ||
104 | this.cargoProcess = child_process.spawn( | ||
105 | 'cargo', | ||
106 | ['watch', '-x', args, ...ignoreFlags], | ||
107 | { | ||
108 | stdio: ['ignore', 'pipe', 'pipe'], | ||
109 | cwd: vscode.workspace.rootPath, | ||
110 | windowsVerbatimArguments: true, | ||
111 | }, | ||
112 | ); | ||
113 | |||
114 | if (!this.cargoProcess) { | ||
115 | vscode.window.showErrorMessage('Cargo Watch failed to start'); | ||
116 | return; | ||
117 | } | ||
118 | |||
119 | const stdoutData = new LineBuffer(); | ||
120 | this.cargoProcess.stdout?.on('data', (s: string) => { | ||
121 | stdoutData.processOutput(s, line => { | ||
122 | this.logInfo(line); | ||
123 | try { | ||
124 | this.parseLine(line); | ||
125 | } catch (err) { | ||
126 | this.logError(`Failed to parse: ${err}, content : ${line}`); | ||
127 | } | ||
128 | }); | ||
129 | }); | ||
130 | |||
131 | const stderrData = new LineBuffer(); | ||
132 | this.cargoProcess.stderr?.on('data', (s: string) => { | ||
133 | stderrData.processOutput(s, line => { | ||
134 | this.logError('Error on cargo-watch : {\n' + line + '}\n'); | ||
135 | }); | ||
136 | }); | ||
137 | |||
138 | this.cargoProcess.on('error', (err: Error) => { | ||
139 | this.logError( | ||
140 | 'Error on cargo-watch process : {\n' + err.message + '}\n', | ||
141 | ); | ||
142 | }); | ||
143 | |||
144 | this.logInfo('cargo-watch started.'); | ||
145 | } | ||
146 | |||
147 | public stop() { | ||
148 | if (this.cargoProcess) { | ||
149 | this.cargoProcess.kill(); | ||
150 | terminate(this.cargoProcess); | ||
151 | this.cargoProcess = undefined; | ||
152 | } else { | ||
153 | vscode.window.showInformationMessage('Cargo Watch is not running'); | ||
154 | } | ||
155 | } | ||
156 | |||
157 | public dispose(): void { | ||
158 | this.stop(); | ||
159 | |||
160 | this.diagnosticCollection.clear(); | ||
161 | this.diagnosticCollection.dispose(); | ||
162 | this.outputChannel.dispose(); | ||
163 | this.statusDisplay.dispose(); | ||
164 | this.codeActionDispose.dispose(); | ||
165 | } | ||
166 | |||
167 | private logInfo(line: string) { | ||
168 | if (Server.config.cargoWatchOptions.trace === 'verbose') { | ||
169 | this.outputChannel.append(line); | ||
170 | } | ||
171 | } | ||
172 | |||
173 | private logError(line: string) { | ||
174 | if ( | ||
175 | Server.config.cargoWatchOptions.trace === 'error' || | ||
176 | Server.config.cargoWatchOptions.trace === 'verbose' | ||
177 | ) { | ||
178 | this.outputChannel.append(line); | ||
179 | } | ||
180 | } | ||
181 | |||
182 | private parseLine(line: string) { | ||
183 | if (line.startsWith('[Running')) { | ||
184 | this.diagnosticCollection.clear(); | ||
185 | this.suggestedFixCollection.clear(); | ||
186 | this.statusDisplay.show(); | ||
187 | } | ||
188 | |||
189 | if (line.startsWith('[Finished running')) { | ||
190 | this.statusDisplay.hide(); | ||
191 | } | ||
192 | |||
193 | interface CargoArtifact { | ||
194 | reason: string; | ||
195 | package_id: string; | ||
196 | } | ||
197 | |||
198 | // https://github.com/rust-lang/cargo/blob/master/src/cargo/util/machine_message.rs | ||
199 | interface CargoMessage { | ||
200 | reason: string; | ||
201 | package_id: string; | ||
202 | message: RustDiagnostic; | ||
203 | } | ||
204 | |||
205 | // cargo-watch itself output non json format | ||
206 | // Ignore these lines | ||
207 | let data: CargoMessage; | ||
208 | try { | ||
209 | data = JSON.parse(line.trim()); | ||
210 | } catch (error) { | ||
211 | this.logError(`Fail to parse to json : { ${error} }`); | ||
212 | return; | ||
213 | } | ||
214 | |||
215 | if (data.reason === 'compiler-artifact') { | ||
216 | const msg = data as CargoArtifact; | ||
217 | |||
218 | // The format of the package_id is "{name} {version} ({source_id})", | ||
219 | // https://github.com/rust-lang/cargo/blob/37ad03f86e895bb80b474c1c088322634f4725f5/src/cargo/core/package_id.rs#L53 | ||
220 | this.statusDisplay.packageName = msg.package_id.split(' ')[0]; | ||
221 | } else if (data.reason === 'compiler-message') { | ||
222 | const msg = data.message as RustDiagnostic; | ||
223 | |||
224 | const mapResult = mapRustDiagnosticToVsCode(msg); | ||
225 | if (!mapResult) { | ||
226 | return; | ||
227 | } | ||
228 | |||
229 | const { location, diagnostic, suggestedFixes } = mapResult; | ||
230 | const fileUri = location.uri; | ||
231 | |||
232 | const diagnostics: vscode.Diagnostic[] = [ | ||
233 | ...(this.diagnosticCollection!.get(fileUri) || []), | ||
234 | ]; | ||
235 | |||
236 | // If we're building multiple targets it's possible we've already seen this diagnostic | ||
237 | const isDuplicate = diagnostics.some(d => | ||
238 | areDiagnosticsEqual(d, diagnostic), | ||
239 | ); | ||
240 | if (isDuplicate) { | ||
241 | return; | ||
242 | } | ||
243 | |||
244 | diagnostics.push(diagnostic); | ||
245 | this.diagnosticCollection!.set(fileUri, diagnostics); | ||
246 | |||
247 | if (suggestedFixes.length) { | ||
248 | for (const suggestedFix of suggestedFixes) { | ||
249 | this.suggestedFixCollection.addSuggestedFixForDiagnostic( | ||
250 | suggestedFix, | ||
251 | diagnostic, | ||
252 | ); | ||
253 | } | ||
254 | |||
255 | // Have VsCode query us for the code actions | ||
256 | vscode.commands.executeCommand( | ||
257 | 'vscode.executeCodeActionProvider', | ||
258 | fileUri, | ||
259 | diagnostic.range, | ||
260 | ); | ||
261 | } | ||
262 | } | ||
263 | } | ||
264 | } | ||
diff --git a/editors/code/src/commands/expand_macro.ts b/editors/code/src/commands/expand_macro.ts index 17c78280a..dcdde78af 100644 --- a/editors/code/src/commands/expand_macro.ts +++ b/editors/code/src/commands/expand_macro.ts | |||
@@ -1,60 +1,23 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import { Position, TextDocumentIdentifier } from 'vscode-languageclient'; | 2 | import * as lc from 'vscode-languageclient'; |
3 | import { Server } from '../server'; | ||
4 | 3 | ||
5 | export const expandMacroUri = vscode.Uri.parse( | 4 | import { Ctx, Cmd } from '../ctx'; |
6 | 'rust-analyzer://expandMacro/[EXPANSION].rs', | ||
7 | ); | ||
8 | |||
9 | export class ExpandMacroContentProvider | ||
10 | implements vscode.TextDocumentContentProvider { | ||
11 | public eventEmitter = new vscode.EventEmitter<vscode.Uri>(); | ||
12 | |||
13 | public provideTextDocumentContent( | ||
14 | _uri: vscode.Uri, | ||
15 | ): vscode.ProviderResult<string> { | ||
16 | async function handle() { | ||
17 | const editor = vscode.window.activeTextEditor; | ||
18 | if (editor == null) { | ||
19 | return ''; | ||
20 | } | ||
21 | |||
22 | const position = editor.selection.active; | ||
23 | const request: MacroExpandParams = { | ||
24 | textDocument: { uri: editor.document.uri.toString() }, | ||
25 | position, | ||
26 | }; | ||
27 | const expanded = await Server.client.sendRequest<ExpandedMacro>( | ||
28 | 'rust-analyzer/expandMacro', | ||
29 | request, | ||
30 | ); | ||
31 | |||
32 | if (expanded == null) { | ||
33 | return 'Not available'; | ||
34 | } | ||
35 | |||
36 | return code_format(expanded); | ||
37 | } | ||
38 | |||
39 | return handle(); | ||
40 | } | ||
41 | |||
42 | get onDidChange(): vscode.Event<vscode.Uri> { | ||
43 | return this.eventEmitter.event; | ||
44 | } | ||
45 | } | ||
46 | 5 | ||
47 | // Opens the virtual file that will show the syntax tree | 6 | // Opens the virtual file that will show the syntax tree |
48 | // | 7 | // |
49 | // The contents of the file come from the `TextDocumentContentProvider` | 8 | // The contents of the file come from the `TextDocumentContentProvider` |
50 | export function createHandle(provider: ExpandMacroContentProvider) { | 9 | export function expandMacro(ctx: Ctx): Cmd { |
51 | return async () => { | 10 | const tdcp = new TextDocumentContentProvider(ctx); |
52 | const uri = expandMacroUri; | 11 | ctx.pushCleanup( |
53 | 12 | vscode.workspace.registerTextDocumentContentProvider( | |
54 | const document = await vscode.workspace.openTextDocument(uri); | 13 | 'rust-analyzer', |
55 | 14 | tdcp, | |
56 | provider.eventEmitter.fire(uri); | 15 | ), |
16 | ); | ||
57 | 17 | ||
18 | return async () => { | ||
19 | const document = await vscode.workspace.openTextDocument(tdcp.uri); | ||
20 | tdcp.eventEmitter.fire(tdcp.uri); | ||
58 | return vscode.window.showTextDocument( | 21 | return vscode.window.showTextDocument( |
59 | document, | 22 | document, |
60 | vscode.ViewColumn.Two, | 23 | vscode.ViewColumn.Two, |
@@ -63,11 +26,6 @@ export function createHandle(provider: ExpandMacroContentProvider) { | |||
63 | }; | 26 | }; |
64 | } | 27 | } |
65 | 28 | ||
66 | interface MacroExpandParams { | ||
67 | textDocument: TextDocumentIdentifier; | ||
68 | position: Position; | ||
69 | } | ||
70 | |||
71 | interface ExpandedMacro { | 29 | interface ExpandedMacro { |
72 | name: string; | 30 | name: string; |
73 | expansion: string; | 31 | expansion: string; |
@@ -81,3 +39,38 @@ function code_format(expanded: ExpandedMacro): string { | |||
81 | 39 | ||
82 | return result; | 40 | return result; |
83 | } | 41 | } |
42 | |||
43 | class TextDocumentContentProvider | ||
44 | implements vscode.TextDocumentContentProvider { | ||
45 | private ctx: Ctx; | ||
46 | uri = vscode.Uri.parse('rust-analyzer://expandMacro/[EXPANSION].rs'); | ||
47 | eventEmitter = new vscode.EventEmitter<vscode.Uri>(); | ||
48 | |||
49 | constructor(ctx: Ctx) { | ||
50 | this.ctx = ctx; | ||
51 | } | ||
52 | |||
53 | async provideTextDocumentContent(_uri: vscode.Uri): Promise<string> { | ||
54 | const editor = vscode.window.activeTextEditor; | ||
55 | const client = this.ctx.client; | ||
56 | if (!editor || !client) return ''; | ||
57 | |||
58 | const position = editor.selection.active; | ||
59 | const request: lc.TextDocumentPositionParams = { | ||
60 | textDocument: { uri: editor.document.uri.toString() }, | ||
61 | position, | ||
62 | }; | ||
63 | const expanded = await client.sendRequest<ExpandedMacro>( | ||
64 | 'rust-analyzer/expandMacro', | ||
65 | request, | ||
66 | ); | ||
67 | |||
68 | if (expanded == null) return 'Not available'; | ||
69 | |||
70 | return code_format(expanded); | ||
71 | } | ||
72 | |||
73 | get onDidChange(): vscode.Event<vscode.Uri> { | ||
74 | return this.eventEmitter.event; | ||
75 | } | ||
76 | } | ||
diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts index 13a696758..9a1697dcb 100644 --- a/editors/code/src/commands/index.ts +++ b/editors/code/src/commands/index.ts | |||
@@ -1,23 +1,63 @@ | |||
1 | import * as analyzerStatus from './analyzer_status'; | 1 | import * as vscode from 'vscode'; |
2 | import * as applySourceChange from './apply_source_change'; | 2 | import * as lc from 'vscode-languageclient'; |
3 | import * as expandMacro from './expand_macro'; | 3 | |
4 | import * as inlayHints from './inlay_hints'; | 4 | import { Ctx, Cmd } from '../ctx'; |
5 | import * as joinLines from './join_lines'; | 5 | import * as sourceChange from '../source_change'; |
6 | import * as matchingBrace from './matching_brace'; | 6 | |
7 | import * as onEnter from './on_enter'; | 7 | import { analyzerStatus } from './analyzer_status'; |
8 | import * as parentModule from './parent_module'; | 8 | import { matchingBrace } from './matching_brace'; |
9 | import * as runnables from './runnables'; | 9 | import { joinLines } from './join_lines'; |
10 | import * as syntaxTree from './syntaxTree'; | 10 | import { onEnter } from './on_enter'; |
11 | import { parentModule } from './parent_module'; | ||
12 | import { syntaxTree } from './syntax_tree'; | ||
13 | import { expandMacro } from './expand_macro'; | ||
14 | import { run, runSingle } from './runnables'; | ||
15 | |||
16 | function collectGarbage(ctx: Ctx): Cmd { | ||
17 | return async () => { | ||
18 | ctx.client?.sendRequest<null>('rust-analyzer/collectGarbage', null); | ||
19 | }; | ||
20 | } | ||
21 | |||
22 | function showReferences(ctx: Ctx): Cmd { | ||
23 | return (uri: string, position: lc.Position, locations: lc.Location[]) => { | ||
24 | let client = ctx.client; | ||
25 | if (client) { | ||
26 | vscode.commands.executeCommand( | ||
27 | 'editor.action.showReferences', | ||
28 | vscode.Uri.parse(uri), | ||
29 | client.protocol2CodeConverter.asPosition(position), | ||
30 | locations.map(client.protocol2CodeConverter.asLocation), | ||
31 | ); | ||
32 | } | ||
33 | }; | ||
34 | } | ||
35 | |||
36 | function applySourceChange(ctx: Ctx): Cmd { | ||
37 | return async (change: sourceChange.SourceChange) => { | ||
38 | sourceChange.applySourceChange(ctx, change); | ||
39 | }; | ||
40 | } | ||
41 | |||
42 | function reload(ctx: Ctx): Cmd { | ||
43 | return async () => { | ||
44 | vscode.window.showInformationMessage('Reloading rust-analyzer...'); | ||
45 | await ctx.restartServer(); | ||
46 | }; | ||
47 | } | ||
11 | 48 | ||
12 | export { | 49 | export { |
13 | analyzerStatus, | 50 | analyzerStatus, |
14 | applySourceChange, | ||
15 | expandMacro, | 51 | expandMacro, |
16 | joinLines, | 52 | joinLines, |
17 | matchingBrace, | 53 | matchingBrace, |
18 | parentModule, | 54 | parentModule, |
19 | runnables, | ||
20 | syntaxTree, | 55 | syntaxTree, |
21 | onEnter, | 56 | onEnter, |
22 | inlayHints, | 57 | collectGarbage, |
58 | run, | ||
59 | runSingle, | ||
60 | showReferences, | ||
61 | applySourceChange, | ||
62 | reload | ||
23 | }; | 63 | }; |
diff --git a/editors/code/src/commands/inlay_hints.ts b/editors/code/src/commands/inlay_hints.ts deleted file mode 100644 index ac7dcce60..000000000 --- a/editors/code/src/commands/inlay_hints.ts +++ /dev/null | |||
@@ -1,115 +0,0 @@ | |||
1 | import * as vscode from 'vscode'; | ||
2 | import { Range, TextDocumentChangeEvent, TextEditor } from 'vscode'; | ||
3 | import { TextDocumentIdentifier } from 'vscode-languageclient'; | ||
4 | import { Server } from '../server'; | ||
5 | |||
6 | interface InlayHintsParams { | ||
7 | textDocument: TextDocumentIdentifier; | ||
8 | } | ||
9 | |||
10 | interface InlayHint { | ||
11 | range: Range; | ||
12 | kind: string; | ||
13 | label: string; | ||
14 | } | ||
15 | |||
16 | const typeHintDecorationType = vscode.window.createTextEditorDecorationType({ | ||
17 | after: { | ||
18 | color: new vscode.ThemeColor('ralsp.inlayHint'), | ||
19 | }, | ||
20 | }); | ||
21 | |||
22 | export class HintsUpdater { | ||
23 | private displayHints = true; | ||
24 | |||
25 | public async toggleHintsDisplay(displayHints: boolean): Promise<void> { | ||
26 | if (this.displayHints !== displayHints) { | ||
27 | this.displayHints = displayHints; | ||
28 | return this.refreshVisibleEditorsHints( | ||
29 | displayHints ? undefined : [], | ||
30 | ); | ||
31 | } | ||
32 | } | ||
33 | |||
34 | public async refreshHintsForVisibleEditors( | ||
35 | cause?: TextDocumentChangeEvent, | ||
36 | ): Promise<void> { | ||
37 | if (!this.displayHints) { | ||
38 | return; | ||
39 | } | ||
40 | if ( | ||
41 | cause !== undefined && | ||
42 | (cause.contentChanges.length === 0 || | ||
43 | !this.isRustDocument(cause.document)) | ||
44 | ) { | ||
45 | return; | ||
46 | } | ||
47 | return this.refreshVisibleEditorsHints(); | ||
48 | } | ||
49 | |||
50 | private async refreshVisibleEditorsHints( | ||
51 | newDecorations?: vscode.DecorationOptions[], | ||
52 | ) { | ||
53 | const promises: Array<Promise<void>> = []; | ||
54 | |||
55 | for (const rustEditor of vscode.window.visibleTextEditors.filter( | ||
56 | editor => this.isRustDocument(editor.document), | ||
57 | )) { | ||
58 | if (newDecorations !== undefined) { | ||
59 | promises.push( | ||
60 | Promise.resolve( | ||
61 | rustEditor.setDecorations( | ||
62 | typeHintDecorationType, | ||
63 | newDecorations, | ||
64 | ), | ||
65 | ), | ||
66 | ); | ||
67 | } else { | ||
68 | promises.push(this.updateDecorationsFromServer(rustEditor)); | ||
69 | } | ||
70 | } | ||
71 | |||
72 | for (const promise of promises) { | ||
73 | await promise; | ||
74 | } | ||
75 | } | ||
76 | |||
77 | private isRustDocument(document: vscode.TextDocument): boolean { | ||
78 | return document && document.languageId === 'rust'; | ||
79 | } | ||
80 | |||
81 | private async updateDecorationsFromServer( | ||
82 | editor: TextEditor, | ||
83 | ): Promise<void> { | ||
84 | const newHints = await this.queryHints(editor.document.uri.toString()); | ||
85 | if (newHints !== null) { | ||
86 | const newDecorations = newHints.map(hint => ({ | ||
87 | range: hint.range, | ||
88 | renderOptions: { | ||
89 | after: { | ||
90 | contentText: `: ${hint.label}`, | ||
91 | }, | ||
92 | }, | ||
93 | })); | ||
94 | return editor.setDecorations( | ||
95 | typeHintDecorationType, | ||
96 | newDecorations, | ||
97 | ); | ||
98 | } | ||
99 | } | ||
100 | |||
101 | private async queryHints(documentUri: string): Promise<InlayHint[] | null> { | ||
102 | const request: InlayHintsParams = { | ||
103 | textDocument: { uri: documentUri }, | ||
104 | }; | ||
105 | const client = Server.client; | ||
106 | return client | ||
107 | .onReady() | ||
108 | .then(() => | ||
109 | client.sendRequest<InlayHint[] | null>( | ||
110 | 'rust-analyzer/inlayHints', | ||
111 | request, | ||
112 | ), | ||
113 | ); | ||
114 | } | ||
115 | } | ||
diff --git a/editors/code/src/commands/join_lines.ts b/editors/code/src/commands/join_lines.ts index 134ddc801..7b08c3255 100644 --- a/editors/code/src/commands/join_lines.ts +++ b/editors/code/src/commands/join_lines.ts | |||
@@ -1,29 +1,27 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as lc from 'vscode-languageclient'; |
2 | 2 | ||
3 | import { Range, TextDocumentIdentifier } from 'vscode-languageclient'; | 3 | import { Ctx, Cmd } from '../ctx'; |
4 | import { Server } from '../server'; | 4 | import { applySourceChange, SourceChange } from '../source_change'; |
5 | import { | ||
6 | handle as applySourceChange, | ||
7 | SourceChange, | ||
8 | } from './apply_source_change'; | ||
9 | 5 | ||
10 | interface JoinLinesParams { | 6 | export function joinLines(ctx: Ctx): Cmd { |
11 | textDocument: TextDocumentIdentifier; | 7 | return async () => { |
12 | range: Range; | 8 | const editor = ctx.activeRustEditor; |
13 | } | 9 | const client = ctx.client; |
10 | if (!editor || !client) return; | ||
14 | 11 | ||
15 | export async function handle() { | 12 | const request: JoinLinesParams = { |
16 | const editor = vscode.window.activeTextEditor; | 13 | range: client.code2ProtocolConverter.asRange(editor.selection), |
17 | if (editor == null || editor.document.languageId !== 'rust') { | 14 | textDocument: { uri: editor.document.uri.toString() }, |
18 | return; | 15 | }; |
19 | } | 16 | const change = await client.sendRequest<SourceChange>( |
20 | const request: JoinLinesParams = { | 17 | 'rust-analyzer/joinLines', |
21 | range: Server.client.code2ProtocolConverter.asRange(editor.selection), | 18 | request, |
22 | textDocument: { uri: editor.document.uri.toString() }, | 19 | ); |
20 | await applySourceChange(ctx, change); | ||
23 | }; | 21 | }; |
24 | const change = await Server.client.sendRequest<SourceChange>( | 22 | } |
25 | 'rust-analyzer/joinLines', | 23 | |
26 | request, | 24 | interface JoinLinesParams { |
27 | ); | 25 | textDocument: lc.TextDocumentIdentifier; |
28 | await applySourceChange(change); | 26 | range: lc.Range; |
29 | } | 27 | } |
diff --git a/editors/code/src/commands/line_buffer.ts b/editors/code/src/commands/line_buffer.ts deleted file mode 100644 index fb5b9f7f2..000000000 --- a/editors/code/src/commands/line_buffer.ts +++ /dev/null | |||
@@ -1,16 +0,0 @@ | |||
1 | export class LineBuffer { | ||
2 | private outBuffer: string = ''; | ||
3 | |||
4 | public processOutput(chunk: string, cb: (line: string) => void) { | ||
5 | this.outBuffer += chunk; | ||
6 | let eolIndex = this.outBuffer.indexOf('\n'); | ||
7 | while (eolIndex >= 0) { | ||
8 | // line includes the EOL | ||
9 | const line = this.outBuffer.slice(0, eolIndex + 1); | ||
10 | cb(line); | ||
11 | this.outBuffer = this.outBuffer.slice(eolIndex + 1); | ||
12 | |||
13 | eolIndex = this.outBuffer.indexOf('\n'); | ||
14 | } | ||
15 | } | ||
16 | } | ||
diff --git a/editors/code/src/commands/matching_brace.ts b/editors/code/src/commands/matching_brace.ts index 364208cc7..7c58bb7e7 100644 --- a/editors/code/src/commands/matching_brace.ts +++ b/editors/code/src/commands/matching_brace.ts | |||
@@ -1,34 +1,36 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as lc from 'vscode-languageclient'; | ||
2 | 3 | ||
3 | import { Position, TextDocumentIdentifier } from 'vscode-languageclient'; | 4 | import { Ctx, Cmd } from '../ctx'; |
4 | import { Server } from '../server'; | ||
5 | 5 | ||
6 | interface FindMatchingBraceParams { | 6 | export function matchingBrace(ctx: Ctx): Cmd { |
7 | textDocument: TextDocumentIdentifier; | 7 | return async () => { |
8 | offsets: Position[]; | 8 | const editor = ctx.activeRustEditor; |
9 | } | 9 | const client = ctx.client; |
10 | if (!editor || !client) return; | ||
10 | 11 | ||
11 | export async function handle() { | 12 | const request: FindMatchingBraceParams = { |
12 | const editor = vscode.window.activeTextEditor; | 13 | textDocument: { uri: editor.document.uri.toString() }, |
13 | if (editor == null || editor.document.languageId !== 'rust') { | 14 | offsets: editor.selections.map(s => |
14 | return; | 15 | client.code2ProtocolConverter.asPosition(s.active), |
15 | } | 16 | ), |
16 | const request: FindMatchingBraceParams = { | 17 | }; |
17 | textDocument: { uri: editor.document.uri.toString() }, | 18 | const response = await client.sendRequest<lc.Position[]>( |
18 | offsets: editor.selections.map(s => { | 19 | 'rust-analyzer/findMatchingBrace', |
19 | return Server.client.code2ProtocolConverter.asPosition(s.active); | 20 | request, |
20 | }), | ||
21 | }; | ||
22 | const response = await Server.client.sendRequest<Position[]>( | ||
23 | 'rust-analyzer/findMatchingBrace', | ||
24 | request, | ||
25 | ); | ||
26 | editor.selections = editor.selections.map((sel, idx) => { | ||
27 | const active = Server.client.protocol2CodeConverter.asPosition( | ||
28 | response[idx], | ||
29 | ); | 21 | ); |
30 | const anchor = sel.isEmpty ? active : sel.anchor; | 22 | editor.selections = editor.selections.map((sel, idx) => { |
31 | return new vscode.Selection(anchor, active); | 23 | const active = client.protocol2CodeConverter.asPosition( |
32 | }); | 24 | response[idx], |
33 | editor.revealRange(editor.selection); | 25 | ); |
26 | const anchor = sel.isEmpty ? active : sel.anchor; | ||
27 | return new vscode.Selection(anchor, active); | ||
28 | }); | ||
29 | editor.revealRange(editor.selection); | ||
30 | }; | ||
31 | } | ||
32 | |||
33 | interface FindMatchingBraceParams { | ||
34 | textDocument: lc.TextDocumentIdentifier; | ||
35 | offsets: lc.Position[]; | ||
34 | } | 36 | } |
diff --git a/editors/code/src/commands/on_enter.ts b/editors/code/src/commands/on_enter.ts index 772c64b3c..6f61883cd 100644 --- a/editors/code/src/commands/on_enter.ts +++ b/editors/code/src/commands/on_enter.ts | |||
@@ -1,33 +1,28 @@ | |||
1 | import * as vscode from 'vscode'; | ||
2 | import * as lc from 'vscode-languageclient'; | 1 | import * as lc from 'vscode-languageclient'; |
3 | import { Server } from '../server'; | ||
4 | import { | ||
5 | handle as applySourceChange, | ||
6 | SourceChange, | ||
7 | } from './apply_source_change'; | ||
8 | 2 | ||
9 | export async function handle(event: { text: string }): Promise<boolean> { | 3 | import { applySourceChange, SourceChange } from '../source_change'; |
10 | const editor = vscode.window.activeTextEditor; | 4 | import { Cmd, Ctx } from '../ctx'; |
11 | if ( | 5 | |
12 | editor == null || | 6 | export function onEnter(ctx: Ctx): Cmd { |
13 | editor.document.languageId !== 'rust' || | 7 | return async (event: { text: string }) => { |
14 | event.text !== '\n' | 8 | const editor = ctx.activeRustEditor; |
15 | ) { | 9 | const client = ctx.client; |
16 | return false; | 10 | if (!editor || event.text !== '\n') return false; |
17 | } | 11 | if (!client) return false; |
18 | const request: lc.TextDocumentPositionParams = { | 12 | |
19 | textDocument: { uri: editor.document.uri.toString() }, | 13 | const request: lc.TextDocumentPositionParams = { |
20 | position: Server.client.code2ProtocolConverter.asPosition( | 14 | textDocument: { uri: editor.document.uri.toString() }, |
21 | editor.selection.active, | 15 | position: client.code2ProtocolConverter.asPosition( |
22 | ), | 16 | editor.selection.active, |
17 | ), | ||
18 | }; | ||
19 | const change = await client.sendRequest<undefined | SourceChange>( | ||
20 | 'rust-analyzer/onEnter', | ||
21 | request, | ||
22 | ); | ||
23 | if (!change) return false; | ||
24 | |||
25 | await applySourceChange(ctx, change); | ||
26 | return true; | ||
23 | }; | 27 | }; |
24 | const change = await Server.client.sendRequest<undefined | SourceChange>( | ||
25 | 'rust-analyzer/onEnter', | ||
26 | request, | ||
27 | ); | ||
28 | if (!change) { | ||
29 | return false; | ||
30 | } | ||
31 | await applySourceChange(change); | ||
32 | return true; | ||
33 | } | 28 | } |
diff --git a/editors/code/src/commands/parent_module.ts b/editors/code/src/commands/parent_module.ts index ad49e1bdb..bf40b4021 100644 --- a/editors/code/src/commands/parent_module.ts +++ b/editors/code/src/commands/parent_module.ts | |||
@@ -1,32 +1,33 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | |||
3 | import * as lc from 'vscode-languageclient'; | 2 | import * as lc from 'vscode-languageclient'; |
4 | import { Server } from '../server'; | ||
5 | 3 | ||
6 | export async function handle() { | 4 | import { Ctx, Cmd } from '../ctx'; |
7 | const editor = vscode.window.activeTextEditor; | 5 | |
8 | if (editor == null || editor.document.languageId !== 'rust') { | 6 | export function parentModule(ctx: Ctx): Cmd { |
9 | return; | 7 | return async () => { |
10 | } | 8 | const editor = ctx.activeRustEditor; |
11 | const request: lc.TextDocumentPositionParams = { | 9 | const client = ctx.client; |
12 | textDocument: { uri: editor.document.uri.toString() }, | 10 | if (!editor || !client) return; |
13 | position: Server.client.code2ProtocolConverter.asPosition( | ||
14 | editor.selection.active, | ||
15 | ), | ||
16 | }; | ||
17 | const response = await Server.client.sendRequest<lc.Location[]>( | ||
18 | 'rust-analyzer/parentModule', | ||
19 | request, | ||
20 | ); | ||
21 | const loc = response[0]; | ||
22 | if (loc == null) { | ||
23 | return; | ||
24 | } | ||
25 | const uri = Server.client.protocol2CodeConverter.asUri(loc.uri); | ||
26 | const range = Server.client.protocol2CodeConverter.asRange(loc.range); | ||
27 | 11 | ||
28 | const doc = await vscode.workspace.openTextDocument(uri); | 12 | const request: lc.TextDocumentPositionParams = { |
29 | const e = await vscode.window.showTextDocument(doc); | 13 | textDocument: { uri: editor.document.uri.toString() }, |
30 | e.selection = new vscode.Selection(range.start, range.start); | 14 | position: client.code2ProtocolConverter.asPosition( |
31 | e.revealRange(range, vscode.TextEditorRevealType.InCenter); | 15 | editor.selection.active, |
16 | ), | ||
17 | }; | ||
18 | const response = await client.sendRequest<lc.Location[]>( | ||
19 | 'rust-analyzer/parentModule', | ||
20 | request, | ||
21 | ); | ||
22 | const loc = response[0]; | ||
23 | if (loc == null) return; | ||
24 | |||
25 | const uri = client.protocol2CodeConverter.asUri(loc.uri); | ||
26 | const range = client.protocol2CodeConverter.asRange(loc.range); | ||
27 | |||
28 | const doc = await vscode.workspace.openTextDocument(uri); | ||
29 | const e = await vscode.window.showTextDocument(doc); | ||
30 | e.selection = new vscode.Selection(range.start, range.start); | ||
31 | e.revealRange(range, vscode.TextEditorRevealType.InCenter); | ||
32 | }; | ||
32 | } | 33 | } |
diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts index cf980e257..7919997ce 100644 --- a/editors/code/src/commands/runnables.ts +++ b/editors/code/src/commands/runnables.ts | |||
@@ -1,11 +1,68 @@ | |||
1 | import * as child_process from 'child_process'; | ||
2 | |||
3 | import * as util from 'util'; | ||
4 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
5 | import * as lc from 'vscode-languageclient'; | 2 | import * as lc from 'vscode-languageclient'; |
6 | 3 | ||
7 | import { Server } from '../server'; | 4 | import { Ctx, Cmd } from '../ctx'; |
8 | import { CargoWatchProvider, registerCargoWatchProvider } from './cargo_watch'; | 5 | |
6 | export function run(ctx: Ctx): Cmd { | ||
7 | let prevRunnable: RunnableQuickPick | undefined; | ||
8 | |||
9 | return async () => { | ||
10 | const editor = ctx.activeRustEditor; | ||
11 | const client = ctx.client; | ||
12 | if (!editor || !client) return; | ||
13 | |||
14 | const textDocument: lc.TextDocumentIdentifier = { | ||
15 | uri: editor.document.uri.toString(), | ||
16 | }; | ||
17 | const params: RunnablesParams = { | ||
18 | textDocument, | ||
19 | position: client.code2ProtocolConverter.asPosition( | ||
20 | editor.selection.active, | ||
21 | ), | ||
22 | }; | ||
23 | const runnables = await client.sendRequest<Runnable[]>( | ||
24 | 'rust-analyzer/runnables', | ||
25 | params, | ||
26 | ); | ||
27 | const items: RunnableQuickPick[] = []; | ||
28 | if (prevRunnable) { | ||
29 | items.push(prevRunnable); | ||
30 | } | ||
31 | for (const r of runnables) { | ||
32 | if ( | ||
33 | prevRunnable && | ||
34 | JSON.stringify(prevRunnable.runnable) === JSON.stringify(r) | ||
35 | ) { | ||
36 | continue; | ||
37 | } | ||
38 | items.push(new RunnableQuickPick(r)); | ||
39 | } | ||
40 | const item = await vscode.window.showQuickPick(items); | ||
41 | if (!item) return; | ||
42 | |||
43 | item.detail = 'rerun'; | ||
44 | prevRunnable = item; | ||
45 | const task = createTask(item.runnable); | ||
46 | return await vscode.tasks.executeTask(task); | ||
47 | }; | ||
48 | } | ||
49 | |||
50 | export function runSingle(ctx: Ctx): Cmd { | ||
51 | return async (runnable: Runnable) => { | ||
52 | const editor = ctx.activeRustEditor; | ||
53 | if (!editor) return; | ||
54 | |||
55 | const task = createTask(runnable); | ||
56 | task.group = vscode.TaskGroup.Build; | ||
57 | task.presentationOptions = { | ||
58 | reveal: vscode.TaskRevealKind.Always, | ||
59 | panel: vscode.TaskPanelKind.Dedicated, | ||
60 | clear: true, | ||
61 | }; | ||
62 | |||
63 | return vscode.tasks.executeTask(task); | ||
64 | }; | ||
65 | } | ||
9 | 66 | ||
10 | interface RunnablesParams { | 67 | interface RunnablesParams { |
11 | textDocument: lc.TextDocumentIdentifier; | 68 | textDocument: lc.TextDocumentIdentifier; |
@@ -71,150 +128,3 @@ function createTask(spec: Runnable): vscode.Task { | |||
71 | t.presentationOptions.clear = true; | 128 | t.presentationOptions.clear = true; |
72 | return t; | 129 | return t; |
73 | } | 130 | } |
74 | |||
75 | let prevRunnable: RunnableQuickPick | undefined; | ||
76 | export async function handle(): Promise<vscode.TaskExecution | undefined> { | ||
77 | const editor = vscode.window.activeTextEditor; | ||
78 | if (editor == null || editor.document.languageId !== 'rust') { | ||
79 | return; | ||
80 | } | ||
81 | const textDocument: lc.TextDocumentIdentifier = { | ||
82 | uri: editor.document.uri.toString(), | ||
83 | }; | ||
84 | const params: RunnablesParams = { | ||
85 | textDocument, | ||
86 | position: Server.client.code2ProtocolConverter.asPosition( | ||
87 | editor.selection.active, | ||
88 | ), | ||
89 | }; | ||
90 | const runnables = await Server.client.sendRequest<Runnable[]>( | ||
91 | 'rust-analyzer/runnables', | ||
92 | params, | ||
93 | ); | ||
94 | const items: RunnableQuickPick[] = []; | ||
95 | if (prevRunnable) { | ||
96 | items.push(prevRunnable); | ||
97 | } | ||
98 | for (const r of runnables) { | ||
99 | if ( | ||
100 | prevRunnable && | ||
101 | JSON.stringify(prevRunnable.runnable) === JSON.stringify(r) | ||
102 | ) { | ||
103 | continue; | ||
104 | } | ||
105 | items.push(new RunnableQuickPick(r)); | ||
106 | } | ||
107 | const item = await vscode.window.showQuickPick(items); | ||
108 | if (!item) { | ||
109 | return; | ||
110 | } | ||
111 | |||
112 | item.detail = 'rerun'; | ||
113 | prevRunnable = item; | ||
114 | const task = createTask(item.runnable); | ||
115 | return await vscode.tasks.executeTask(task); | ||
116 | } | ||
117 | |||
118 | export async function handleSingle(runnable: Runnable) { | ||
119 | const editor = vscode.window.activeTextEditor; | ||
120 | if (editor == null || editor.document.languageId !== 'rust') { | ||
121 | return; | ||
122 | } | ||
123 | |||
124 | const task = createTask(runnable); | ||
125 | task.group = vscode.TaskGroup.Build; | ||
126 | task.presentationOptions = { | ||
127 | reveal: vscode.TaskRevealKind.Always, | ||
128 | panel: vscode.TaskPanelKind.Dedicated, | ||
129 | clear: true, | ||
130 | }; | ||
131 | |||
132 | return vscode.tasks.executeTask(task); | ||
133 | } | ||
134 | |||
135 | /** | ||
136 | * Interactively asks the user whether we should run `cargo check` in order to | ||
137 | * provide inline diagnostics; the user is met with a series of dialog boxes | ||
138 | * that, when accepted, allow us to `cargo install cargo-watch` and then run it. | ||
139 | */ | ||
140 | export async function interactivelyStartCargoWatch( | ||
141 | context: vscode.ExtensionContext, | ||
142 | ): Promise<CargoWatchProvider | undefined> { | ||
143 | if (Server.config.cargoWatchOptions.enableOnStartup === 'disabled') { | ||
144 | return; | ||
145 | } | ||
146 | |||
147 | if (Server.config.cargoWatchOptions.enableOnStartup === 'ask') { | ||
148 | const watch = await vscode.window.showInformationMessage( | ||
149 | 'Start watching changes with cargo? (Executes `cargo watch`, provides inline diagnostics)', | ||
150 | 'yes', | ||
151 | 'no', | ||
152 | ); | ||
153 | if (watch !== 'yes') { | ||
154 | return; | ||
155 | } | ||
156 | } | ||
157 | |||
158 | return startCargoWatch(context); | ||
159 | } | ||
160 | |||
161 | export async function startCargoWatch( | ||
162 | context: vscode.ExtensionContext, | ||
163 | ): Promise<CargoWatchProvider | undefined> { | ||
164 | const execPromise = util.promisify(child_process.exec); | ||
165 | |||
166 | const { stderr, code = 0 } = await execPromise( | ||
167 | 'cargo watch --version', | ||
168 | ).catch(e => e); | ||
169 | |||
170 | if (stderr.includes('no such subcommand: `watch`')) { | ||
171 | const msg = | ||
172 | 'The `cargo-watch` subcommand is not installed. Install? (takes ~1-2 minutes)'; | ||
173 | const install = await vscode.window.showInformationMessage( | ||
174 | msg, | ||
175 | 'yes', | ||
176 | 'no', | ||
177 | ); | ||
178 | if (install !== 'yes') { | ||
179 | return; | ||
180 | } | ||
181 | |||
182 | const label = 'install-cargo-watch'; | ||
183 | const taskFinished = new Promise((resolve, _reject) => { | ||
184 | const disposable = vscode.tasks.onDidEndTask(({ execution }) => { | ||
185 | if (execution.task.name === label) { | ||
186 | disposable.dispose(); | ||
187 | resolve(); | ||
188 | } | ||
189 | }); | ||
190 | }); | ||
191 | |||
192 | vscode.tasks.executeTask( | ||
193 | createTask({ | ||
194 | label, | ||
195 | bin: 'cargo', | ||
196 | args: ['install', 'cargo-watch'], | ||
197 | env: {}, | ||
198 | }), | ||
199 | ); | ||
200 | await taskFinished; | ||
201 | const output = await execPromise('cargo watch --version').catch(e => e); | ||
202 | if (output.stderr !== '') { | ||
203 | vscode.window.showErrorMessage( | ||
204 | `Couldn't install \`cargo-\`watch: ${output.stderr}`, | ||
205 | ); | ||
206 | return; | ||
207 | } | ||
208 | } else if (code !== 0) { | ||
209 | vscode.window.showErrorMessage( | ||
210 | `\`cargo watch\` failed with ${code}: ${stderr}`, | ||
211 | ); | ||
212 | return; | ||
213 | } | ||
214 | |||
215 | const provider = await registerCargoWatchProvider(context.subscriptions); | ||
216 | if (provider) { | ||
217 | provider.start(); | ||
218 | } | ||
219 | return provider; | ||
220 | } | ||
diff --git a/editors/code/src/commands/syntaxTree.ts b/editors/code/src/commands/syntaxTree.ts deleted file mode 100644 index 89a80550c..000000000 --- a/editors/code/src/commands/syntaxTree.ts +++ /dev/null | |||
@@ -1,76 +0,0 @@ | |||
1 | import * as vscode from 'vscode'; | ||
2 | import { Range, TextDocumentIdentifier } from 'vscode-languageclient'; | ||
3 | |||
4 | import { Server } from '../server'; | ||
5 | |||
6 | export const syntaxTreeUri = vscode.Uri.parse('rust-analyzer://syntaxtree'); | ||
7 | |||
8 | export class SyntaxTreeContentProvider | ||
9 | implements vscode.TextDocumentContentProvider { | ||
10 | public eventEmitter = new vscode.EventEmitter<vscode.Uri>(); | ||
11 | public syntaxTree: string = 'Not available'; | ||
12 | |||
13 | public provideTextDocumentContent( | ||
14 | uri: vscode.Uri, | ||
15 | ): vscode.ProviderResult<string> { | ||
16 | const editor = vscode.window.activeTextEditor; | ||
17 | if (editor == null) { | ||
18 | return ''; | ||
19 | } | ||
20 | |||
21 | let range: Range | undefined; | ||
22 | |||
23 | // When the range based query is enabled we take the range of the selection | ||
24 | if (uri.query === 'range=true') { | ||
25 | range = editor.selection.isEmpty | ||
26 | ? undefined | ||
27 | : Server.client.code2ProtocolConverter.asRange( | ||
28 | editor.selection, | ||
29 | ); | ||
30 | } | ||
31 | |||
32 | const request: SyntaxTreeParams = { | ||
33 | textDocument: { uri: editor.document.uri.toString() }, | ||
34 | range, | ||
35 | }; | ||
36 | return Server.client.sendRequest<SyntaxTreeResult>( | ||
37 | 'rust-analyzer/syntaxTree', | ||
38 | request, | ||
39 | ); | ||
40 | } | ||
41 | |||
42 | get onDidChange(): vscode.Event<vscode.Uri> { | ||
43 | return this.eventEmitter.event; | ||
44 | } | ||
45 | } | ||
46 | |||
47 | interface SyntaxTreeParams { | ||
48 | textDocument: TextDocumentIdentifier; | ||
49 | range?: Range; | ||
50 | } | ||
51 | |||
52 | type SyntaxTreeResult = string; | ||
53 | |||
54 | // Opens the virtual file that will show the syntax tree | ||
55 | // | ||
56 | // The contents of the file come from the `TextDocumentContentProvider` | ||
57 | export function createHandle(provider: SyntaxTreeContentProvider) { | ||
58 | return async () => { | ||
59 | const editor = vscode.window.activeTextEditor; | ||
60 | const rangeEnabled = !!(editor && !editor.selection.isEmpty); | ||
61 | |||
62 | const uri = rangeEnabled | ||
63 | ? vscode.Uri.parse(`${syntaxTreeUri.toString()}?range=true`) | ||
64 | : syntaxTreeUri; | ||
65 | |||
66 | const document = await vscode.workspace.openTextDocument(uri); | ||
67 | |||
68 | provider.eventEmitter.fire(uri); | ||
69 | |||
70 | return vscode.window.showTextDocument( | ||
71 | document, | ||
72 | vscode.ViewColumn.Two, | ||
73 | true, | ||
74 | ); | ||
75 | }; | ||
76 | } | ||
diff --git a/editors/code/src/commands/syntax_tree.ts b/editors/code/src/commands/syntax_tree.ts new file mode 100644 index 000000000..02ea9f166 --- /dev/null +++ b/editors/code/src/commands/syntax_tree.ts | |||
@@ -0,0 +1,104 @@ | |||
1 | import * as vscode from 'vscode'; | ||
2 | import * as lc from 'vscode-languageclient'; | ||
3 | |||
4 | import { Ctx, Cmd } from '../ctx'; | ||
5 | |||
6 | // Opens the virtual file that will show the syntax tree | ||
7 | // | ||
8 | // The contents of the file come from the `TextDocumentContentProvider` | ||
9 | export function syntaxTree(ctx: Ctx): Cmd { | ||
10 | const tdcp = new TextDocumentContentProvider(ctx); | ||
11 | |||
12 | ctx.pushCleanup( | ||
13 | vscode.workspace.registerTextDocumentContentProvider( | ||
14 | 'rust-analyzer', | ||
15 | tdcp, | ||
16 | ), | ||
17 | ); | ||
18 | |||
19 | vscode.workspace.onDidChangeTextDocument( | ||
20 | (event: vscode.TextDocumentChangeEvent) => { | ||
21 | const doc = event.document; | ||
22 | if (doc.languageId !== 'rust') return; | ||
23 | afterLs(() => tdcp.eventEmitter.fire(tdcp.uri)); | ||
24 | }, | ||
25 | ctx.subscriptions, | ||
26 | ); | ||
27 | |||
28 | vscode.window.onDidChangeActiveTextEditor( | ||
29 | (editor: vscode.TextEditor | undefined) => { | ||
30 | if (!editor || editor.document.languageId !== 'rust') return; | ||
31 | tdcp.eventEmitter.fire(tdcp.uri); | ||
32 | }, | ||
33 | ctx.subscriptions, | ||
34 | ); | ||
35 | |||
36 | return async () => { | ||
37 | const editor = vscode.window.activeTextEditor; | ||
38 | const rangeEnabled = !!(editor && !editor.selection.isEmpty); | ||
39 | |||
40 | const uri = rangeEnabled | ||
41 | ? vscode.Uri.parse(`${tdcp.uri.toString()}?range=true`) | ||
42 | : tdcp.uri; | ||
43 | |||
44 | const document = await vscode.workspace.openTextDocument(uri); | ||
45 | |||
46 | tdcp.eventEmitter.fire(uri); | ||
47 | |||
48 | return vscode.window.showTextDocument( | ||
49 | document, | ||
50 | vscode.ViewColumn.Two, | ||
51 | true, | ||
52 | ); | ||
53 | }; | ||
54 | } | ||
55 | |||
56 | // We need to order this after LS updates, but there's no API for that. | ||
57 | // Hence, good old setTimeout. | ||
58 | function afterLs(f: () => any) { | ||
59 | setTimeout(f, 10); | ||
60 | } | ||
61 | |||
62 | interface SyntaxTreeParams { | ||
63 | textDocument: lc.TextDocumentIdentifier; | ||
64 | range?: lc.Range; | ||
65 | } | ||
66 | |||
67 | class TextDocumentContentProvider | ||
68 | implements vscode.TextDocumentContentProvider { | ||
69 | private ctx: Ctx; | ||
70 | uri = vscode.Uri.parse('rust-analyzer://syntaxtree'); | ||
71 | eventEmitter = new vscode.EventEmitter<vscode.Uri>(); | ||
72 | |||
73 | constructor(ctx: Ctx) { | ||
74 | this.ctx = ctx; | ||
75 | } | ||
76 | |||
77 | provideTextDocumentContent(uri: vscode.Uri): vscode.ProviderResult<string> { | ||
78 | const editor = vscode.window.activeTextEditor; | ||
79 | const client = this.ctx.client; | ||
80 | if (!editor || !client) return ''; | ||
81 | |||
82 | let range: lc.Range | undefined; | ||
83 | |||
84 | // When the range based query is enabled we take the range of the selection | ||
85 | if (uri.query === 'range=true') { | ||
86 | range = editor.selection.isEmpty | ||
87 | ? undefined | ||
88 | : client.code2ProtocolConverter.asRange(editor.selection); | ||
89 | } | ||
90 | |||
91 | const request: SyntaxTreeParams = { | ||
92 | textDocument: { uri: editor.document.uri.toString() }, | ||
93 | range, | ||
94 | }; | ||
95 | return client.sendRequest<string>( | ||
96 | 'rust-analyzer/syntaxTree', | ||
97 | request, | ||
98 | ); | ||
99 | } | ||
100 | |||
101 | get onDidChange(): vscode.Event<vscode.Uri> { | ||
102 | return this.eventEmitter.event; | ||
103 | } | ||
104 | } | ||
diff --git a/editors/code/src/commands/watch_status.ts b/editors/code/src/commands/watch_status.ts deleted file mode 100644 index 8d64394c7..000000000 --- a/editors/code/src/commands/watch_status.ts +++ /dev/null | |||
@@ -1,63 +0,0 @@ | |||
1 | import * as vscode from 'vscode'; | ||
2 | |||
3 | const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; | ||
4 | |||
5 | export class StatusDisplay implements vscode.Disposable { | ||
6 | public packageName?: string; | ||
7 | |||
8 | private i = 0; | ||
9 | private statusBarItem: vscode.StatusBarItem; | ||
10 | private command: string; | ||
11 | private timer?: NodeJS.Timeout; | ||
12 | |||
13 | constructor(command: string) { | ||
14 | this.statusBarItem = vscode.window.createStatusBarItem( | ||
15 | vscode.StatusBarAlignment.Left, | ||
16 | 10, | ||
17 | ); | ||
18 | this.command = command; | ||
19 | this.statusBarItem.hide(); | ||
20 | } | ||
21 | |||
22 | public show() { | ||
23 | this.packageName = undefined; | ||
24 | |||
25 | this.timer = | ||
26 | this.timer || | ||
27 | setInterval(() => { | ||
28 | if (this.packageName) { | ||
29 | this.statusBarItem!.text = `cargo ${this.command} [${ | ||
30 | this.packageName | ||
31 | }] ${this.frame()}`; | ||
32 | } else { | ||
33 | this.statusBarItem!.text = `cargo ${ | ||
34 | this.command | ||
35 | } ${this.frame()}`; | ||
36 | } | ||
37 | }, 300); | ||
38 | |||
39 | this.statusBarItem.show(); | ||
40 | } | ||
41 | |||
42 | public hide() { | ||
43 | if (this.timer) { | ||
44 | clearInterval(this.timer); | ||
45 | this.timer = undefined; | ||
46 | } | ||
47 | |||
48 | this.statusBarItem.hide(); | ||
49 | } | ||
50 | |||
51 | public dispose() { | ||
52 | if (this.timer) { | ||
53 | clearInterval(this.timer); | ||
54 | this.timer = undefined; | ||
55 | } | ||
56 | |||
57 | this.statusBarItem.dispose(); | ||
58 | } | ||
59 | |||
60 | private frame() { | ||
61 | return spinnerFrames[(this.i = ++this.i % spinnerFrames.length)]; | ||
62 | } | ||
63 | } | ||