diff options
Diffstat (limited to 'editors/code/src')
-rw-r--r-- | editors/code/src/client.ts | 27 | ||||
-rw-r--r-- | editors/code/src/commands/runnables.ts | 4 | ||||
-rw-r--r-- | editors/code/src/commands/syntax_tree.ts | 111 | ||||
-rw-r--r-- | editors/code/src/config.ts | 32 | ||||
-rw-r--r-- | editors/code/src/ctx.ts | 3 | ||||
-rw-r--r-- | editors/code/src/highlighting.ts | 255 | ||||
-rw-r--r-- | editors/code/src/main.ts | 4 | ||||
-rw-r--r-- | editors/code/src/status_display.ts | 2 |
8 files changed, 105 insertions, 333 deletions
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index f909f8db2..3b1d00bca 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts | |||
@@ -5,31 +5,6 @@ import { Config } from './config'; | |||
5 | import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.proposed'; | 5 | import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.proposed'; |
6 | import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed'; | 6 | import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed'; |
7 | 7 | ||
8 | export function configToServerOptions(config: Config) { | ||
9 | return { | ||
10 | publishDecorations: !config.highlightingSemanticTokens, | ||
11 | lruCapacity: config.lruCapacity, | ||
12 | |||
13 | inlayHintsType: config.inlayHints.typeHints, | ||
14 | inlayHintsParameter: config.inlayHints.parameterHints, | ||
15 | inlayHintsChaining: config.inlayHints.chainingHints, | ||
16 | inlayHintsMaxLength: config.inlayHints.maxLength, | ||
17 | |||
18 | cargoWatchEnable: config.cargoWatchOptions.enable, | ||
19 | cargoWatchArgs: config.cargoWatchOptions.arguments, | ||
20 | cargoWatchCommand: config.cargoWatchOptions.command, | ||
21 | cargoWatchAllTargets: config.cargoWatchOptions.allTargets, | ||
22 | |||
23 | excludeGlobs: config.excludeGlobs, | ||
24 | useClientWatching: config.useClientWatching, | ||
25 | featureFlags: config.featureFlags, | ||
26 | withSysroot: config.withSysroot, | ||
27 | cargoFeatures: config.cargoFeatures, | ||
28 | rustfmtArgs: config.rustfmtArgs, | ||
29 | vscodeLldb: vscode.extensions.getExtension("vadimcn.vscode-lldb") != null, | ||
30 | }; | ||
31 | } | ||
32 | |||
33 | export async function createClient(config: Config, serverPath: string, cwd: string): Promise<lc.LanguageClient> { | 8 | export async function createClient(config: Config, serverPath: string, cwd: string): Promise<lc.LanguageClient> { |
34 | // '.' Is the fallback if no folder is open | 9 | // '.' Is the fallback if no folder is open |
35 | // TODO?: Workspace folders support Uri's (eg: file://test.txt). | 10 | // TODO?: Workspace folders support Uri's (eg: file://test.txt). |
@@ -49,7 +24,7 @@ export async function createClient(config: Config, serverPath: string, cwd: stri | |||
49 | 24 | ||
50 | const clientOptions: lc.LanguageClientOptions = { | 25 | const clientOptions: lc.LanguageClientOptions = { |
51 | documentSelector: [{ scheme: 'file', language: 'rust' }], | 26 | documentSelector: [{ scheme: 'file', language: 'rust' }], |
52 | initializationOptions: configToServerOptions(config), | 27 | initializationOptions: vscode.workspace.getConfiguration("rust-analyzer"), |
53 | traceOutputChannel, | 28 | traceOutputChannel, |
54 | middleware: { | 29 | middleware: { |
55 | // Workaround for https://github.com/microsoft/vscode-languageserver-node/issues/576 | 30 | // Workaround for https://github.com/microsoft/vscode-languageserver-node/issues/576 |
diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts index 357155163..2635a1440 100644 --- a/editors/code/src/commands/runnables.ts +++ b/editors/code/src/commands/runnables.ts | |||
@@ -66,6 +66,10 @@ export function debugSingle(ctx: Ctx): Cmd { | |||
66 | return async (config: ra.Runnable) => { | 66 | return async (config: ra.Runnable) => { |
67 | const editor = ctx.activeRustEditor; | 67 | const editor = ctx.activeRustEditor; |
68 | if (!editor) return; | 68 | if (!editor) return; |
69 | if (!vscode.extensions.getExtension("vadimcn.vscode-lldb")) { | ||
70 | vscode.window.showErrorMessage("Install `vadimcn.vscode-lldb` extension for debugging"); | ||
71 | return; | ||
72 | } | ||
69 | 73 | ||
70 | const debugConfig = { | 74 | const debugConfig = { |
71 | type: "lldb", | 75 | type: "lldb", |
diff --git a/editors/code/src/commands/syntax_tree.ts b/editors/code/src/commands/syntax_tree.ts index 996c7a716..b7a397414 100644 --- a/editors/code/src/commands/syntax_tree.ts +++ b/editors/code/src/commands/syntax_tree.ts | |||
@@ -15,6 +15,9 @@ export function syntaxTree(ctx: Ctx): Cmd { | |||
15 | void new AstInspector(ctx); | 15 | void new AstInspector(ctx); |
16 | 16 | ||
17 | ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider(AST_FILE_SCHEME, tdcp)); | 17 | ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider(AST_FILE_SCHEME, tdcp)); |
18 | ctx.pushCleanup(vscode.languages.setLanguageConfiguration("ra_syntax_tree", { | ||
19 | brackets: [["[", ")"]], | ||
20 | })); | ||
18 | 21 | ||
19 | return async () => { | 22 | return async () => { |
20 | const editor = vscode.window.activeTextEditor; | 23 | const editor = vscode.window.activeTextEditor; |
@@ -36,7 +39,7 @@ export function syntaxTree(ctx: Ctx): Cmd { | |||
36 | } | 39 | } |
37 | 40 | ||
38 | class TextDocumentContentProvider implements vscode.TextDocumentContentProvider { | 41 | class TextDocumentContentProvider implements vscode.TextDocumentContentProvider { |
39 | readonly uri = vscode.Uri.parse('rust-analyzer://syntaxtree'); | 42 | readonly uri = vscode.Uri.parse('rust-analyzer://syntaxtree/tree.rast'); |
40 | readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>(); | 43 | readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>(); |
41 | 44 | ||
42 | 45 | ||
@@ -79,16 +82,41 @@ class TextDocumentContentProvider implements vscode.TextDocumentContentProvider | |||
79 | 82 | ||
80 | // FIXME: consider implementing this via the Tree View API? | 83 | // FIXME: consider implementing this via the Tree View API? |
81 | // https://code.visualstudio.com/api/extension-guides/tree-view | 84 | // https://code.visualstudio.com/api/extension-guides/tree-view |
82 | class AstInspector implements vscode.HoverProvider, Disposable { | 85 | class AstInspector implements vscode.HoverProvider, vscode.DefinitionProvider, Disposable { |
83 | private static readonly astDecorationType = vscode.window.createTextEditorDecorationType({ | 86 | private readonly astDecorationType = vscode.window.createTextEditorDecorationType({ |
84 | fontStyle: "normal", | 87 | borderColor: new vscode.ThemeColor('rust_analyzer.syntaxTreeBorder'), |
85 | border: "#ffffff 1px solid", | 88 | borderStyle: "solid", |
89 | borderWidth: "2px", | ||
90 | |||
86 | }); | 91 | }); |
87 | private rustEditor: undefined | RustEditor; | 92 | private rustEditor: undefined | RustEditor; |
88 | 93 | ||
94 | // Lazy rust token range -> syntax tree file range. | ||
95 | private readonly rust2Ast = new Lazy(() => { | ||
96 | const astEditor = this.findAstTextEditor(); | ||
97 | if (!this.rustEditor || !astEditor) return undefined; | ||
98 | |||
99 | const buf: [vscode.Range, vscode.Range][] = []; | ||
100 | for (let i = 0; i < astEditor.document.lineCount; ++i) { | ||
101 | const astLine = astEditor.document.lineAt(i); | ||
102 | |||
103 | // Heuristically look for nodes with quoted text (which are token nodes) | ||
104 | const isTokenNode = astLine.text.lastIndexOf('"') >= 0; | ||
105 | if (!isTokenNode) continue; | ||
106 | |||
107 | const rustRange = this.parseRustTextRange(this.rustEditor.document, astLine.text); | ||
108 | if (!rustRange) continue; | ||
109 | |||
110 | buf.push([rustRange, this.findAstNodeRange(astLine)]); | ||
111 | } | ||
112 | return buf; | ||
113 | }); | ||
114 | |||
89 | constructor(ctx: Ctx) { | 115 | constructor(ctx: Ctx) { |
90 | ctx.pushCleanup(vscode.languages.registerHoverProvider({ scheme: AST_FILE_SCHEME }, this)); | 116 | ctx.pushCleanup(vscode.languages.registerHoverProvider({ scheme: AST_FILE_SCHEME }, this)); |
117 | ctx.pushCleanup(vscode.languages.registerDefinitionProvider({ language: "rust" }, this)); | ||
91 | vscode.workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, ctx.subscriptions); | 118 | vscode.workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, ctx.subscriptions); |
119 | vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions); | ||
92 | vscode.window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, ctx.subscriptions); | 120 | vscode.window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, ctx.subscriptions); |
93 | 121 | ||
94 | ctx.pushCleanup(this); | 122 | ctx.pushCleanup(this); |
@@ -97,6 +125,12 @@ class AstInspector implements vscode.HoverProvider, Disposable { | |||
97 | this.setRustEditor(undefined); | 125 | this.setRustEditor(undefined); |
98 | } | 126 | } |
99 | 127 | ||
128 | private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) { | ||
129 | if (this.rustEditor && event.document.uri.toString() === this.rustEditor.document.uri.toString()) { | ||
130 | this.rust2Ast.reset(); | ||
131 | } | ||
132 | } | ||
133 | |||
100 | private onDidCloseTextDocument(doc: vscode.TextDocument) { | 134 | private onDidCloseTextDocument(doc: vscode.TextDocument) { |
101 | if (this.rustEditor && doc.uri.toString() === this.rustEditor.document.uri.toString()) { | 135 | if (this.rustEditor && doc.uri.toString() === this.rustEditor.document.uri.toString()) { |
102 | this.setRustEditor(undefined); | 136 | this.setRustEditor(undefined); |
@@ -104,38 +138,67 @@ class AstInspector implements vscode.HoverProvider, Disposable { | |||
104 | } | 138 | } |
105 | 139 | ||
106 | private onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]) { | 140 | private onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]) { |
107 | if (editors.every(suspect => suspect.document.uri.scheme !== AST_FILE_SCHEME)) { | 141 | if (!this.findAstTextEditor()) { |
108 | this.setRustEditor(undefined); | 142 | this.setRustEditor(undefined); |
109 | return; | 143 | return; |
110 | } | 144 | } |
111 | this.setRustEditor(editors.find(isRustEditor)); | 145 | this.setRustEditor(editors.find(isRustEditor)); |
112 | } | 146 | } |
113 | 147 | ||
148 | private findAstTextEditor(): undefined | vscode.TextEditor { | ||
149 | return vscode.window.visibleTextEditors.find(it => it.document.uri.scheme === AST_FILE_SCHEME); | ||
150 | } | ||
151 | |||
114 | private setRustEditor(newRustEditor: undefined | RustEditor) { | 152 | private setRustEditor(newRustEditor: undefined | RustEditor) { |
115 | if (newRustEditor !== this.rustEditor) { | 153 | if (this.rustEditor && this.rustEditor !== newRustEditor) { |
116 | this.rustEditor?.setDecorations(AstInspector.astDecorationType, []); | 154 | this.rustEditor.setDecorations(this.astDecorationType, []); |
155 | this.rust2Ast.reset(); | ||
117 | } | 156 | } |
118 | this.rustEditor = newRustEditor; | 157 | this.rustEditor = newRustEditor; |
119 | } | 158 | } |
120 | 159 | ||
160 | // additional positional params are omitted | ||
161 | provideDefinition(doc: vscode.TextDocument, pos: vscode.Position): vscode.ProviderResult<vscode.DefinitionLink[]> { | ||
162 | if (!this.rustEditor || doc.uri.toString() !== this.rustEditor.document.uri.toString()) return; | ||
163 | |||
164 | const astEditor = this.findAstTextEditor(); | ||
165 | if (!astEditor) return; | ||
166 | |||
167 | const rust2AstRanges = this.rust2Ast.get()?.find(([rustRange, _]) => rustRange.contains(pos)); | ||
168 | if (!rust2AstRanges) return; | ||
169 | |||
170 | const [rustFileRange, astFileRange] = rust2AstRanges; | ||
171 | |||
172 | astEditor.revealRange(astFileRange); | ||
173 | astEditor.selection = new vscode.Selection(astFileRange.start, astFileRange.end); | ||
174 | |||
175 | return [{ | ||
176 | targetRange: astFileRange, | ||
177 | targetUri: astEditor.document.uri, | ||
178 | originSelectionRange: rustFileRange, | ||
179 | targetSelectionRange: astFileRange, | ||
180 | }]; | ||
181 | } | ||
182 | |||
183 | // additional positional params are omitted | ||
121 | provideHover(doc: vscode.TextDocument, hoverPosition: vscode.Position): vscode.ProviderResult<vscode.Hover> { | 184 | provideHover(doc: vscode.TextDocument, hoverPosition: vscode.Position): vscode.ProviderResult<vscode.Hover> { |
122 | if (!this.rustEditor) return; | 185 | if (!this.rustEditor) return; |
123 | 186 | ||
124 | const astTextLine = doc.lineAt(hoverPosition.line); | 187 | const astFileLine = doc.lineAt(hoverPosition.line); |
125 | 188 | ||
126 | const rustTextRange = this.parseRustTextRange(this.rustEditor.document, astTextLine.text); | 189 | const rustFileRange = this.parseRustTextRange(this.rustEditor.document, astFileLine.text); |
127 | if (!rustTextRange) return; | 190 | if (!rustFileRange) return; |
128 | 191 | ||
129 | this.rustEditor.setDecorations(AstInspector.astDecorationType, [rustTextRange]); | 192 | this.rustEditor.setDecorations(this.astDecorationType, [rustFileRange]); |
130 | this.rustEditor.revealRange(rustTextRange); | 193 | this.rustEditor.revealRange(rustFileRange); |
131 | 194 | ||
132 | const rustSourceCode = this.rustEditor.document.getText(rustTextRange); | 195 | const rustSourceCode = this.rustEditor.document.getText(rustFileRange); |
133 | const astTextRange = this.findAstRange(astTextLine); | 196 | const astFileRange = this.findAstNodeRange(astFileLine); |
134 | 197 | ||
135 | return new vscode.Hover(["```rust\n" + rustSourceCode + "\n```"], astTextRange); | 198 | return new vscode.Hover(["```rust\n" + rustSourceCode + "\n```"], astFileRange); |
136 | } | 199 | } |
137 | 200 | ||
138 | private findAstRange(astLine: vscode.TextLine) { | 201 | private findAstNodeRange(astLine: vscode.TextLine) { |
139 | const lineOffset = astLine.range.start; | 202 | const lineOffset = astLine.range.start; |
140 | const begin = lineOffset.translate(undefined, astLine.firstNonWhitespaceCharacterIndex); | 203 | const begin = lineOffset.translate(undefined, astLine.firstNonWhitespaceCharacterIndex); |
141 | const end = lineOffset.translate(undefined, astLine.text.trimEnd().length); | 204 | const end = lineOffset.translate(undefined, astLine.text.trimEnd().length); |
@@ -151,3 +214,17 @@ class AstInspector implements vscode.HoverProvider, Disposable { | |||
151 | return new vscode.Range(begin, end); | 214 | return new vscode.Range(begin, end); |
152 | } | 215 | } |
153 | } | 216 | } |
217 | |||
218 | class Lazy<T> { | ||
219 | val: undefined | T; | ||
220 | |||
221 | constructor(private readonly compute: () => undefined | T) { } | ||
222 | |||
223 | get() { | ||
224 | return this.val ?? (this.val = this.compute()); | ||
225 | } | ||
226 | |||
227 | reset() { | ||
228 | this.val = undefined; | ||
229 | } | ||
230 | } | ||
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 501997fef..1f45f1de0 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts | |||
@@ -11,9 +11,8 @@ export class Config { | |||
11 | private readonly rootSection = "rust-analyzer"; | 11 | private readonly rootSection = "rust-analyzer"; |
12 | private readonly requiresReloadOpts = [ | 12 | private readonly requiresReloadOpts = [ |
13 | "serverPath", | 13 | "serverPath", |
14 | "cargoFeatures", | 14 | "cargo", |
15 | "excludeGlobs", | 15 | "files", |
16 | "useClientWatching", | ||
17 | "highlighting", | 16 | "highlighting", |
18 | "updates.channel", | 17 | "updates.channel", |
19 | ] | 18 | ] |
@@ -71,19 +70,8 @@ export class Config { | |||
71 | get channel() { return this.cfg.get<UpdatesChannel>("updates.channel")!; } | 70 | get channel() { return this.cfg.get<UpdatesChannel>("updates.channel")!; } |
72 | get askBeforeDownload() { return this.cfg.get<boolean>("updates.askBeforeDownload")!; } | 71 | get askBeforeDownload() { return this.cfg.get<boolean>("updates.askBeforeDownload")!; } |
73 | get highlightingSemanticTokens() { return this.cfg.get<boolean>("highlighting.semanticTokens")!; } | 72 | get highlightingSemanticTokens() { return this.cfg.get<boolean>("highlighting.semanticTokens")!; } |
74 | get highlightingOn() { return this.cfg.get<boolean>("highlightingOn")!; } | ||
75 | get rainbowHighlightingOn() { return this.cfg.get<boolean>("rainbowHighlightingOn")!; } | ||
76 | get lruCapacity() { return this.cfg.get<null | number>("lruCapacity")!; } | ||
77 | get excludeGlobs() { return this.cfg.get<string[]>("excludeGlobs")!; } | ||
78 | get useClientWatching() { return this.cfg.get<boolean>("useClientWatching")!; } | ||
79 | get featureFlags() { return this.cfg.get<Record<string, boolean>>("featureFlags")!; } | ||
80 | get rustfmtArgs() { return this.cfg.get<string[]>("rustfmtArgs")!; } | ||
81 | get loadOutDirsFromCheck() { return this.cfg.get<boolean>("loadOutDirsFromCheck")!; } | ||
82 | get traceExtension() { return this.cfg.get<boolean>("trace.extension")!; } | 73 | get traceExtension() { return this.cfg.get<boolean>("trace.extension")!; } |
83 | 74 | ||
84 | // for internal use | ||
85 | get withSysroot() { return this.cfg.get<boolean>("withSysroot", true)!; } | ||
86 | |||
87 | get inlayHints() { | 75 | get inlayHints() { |
88 | return { | 76 | return { |
89 | typeHints: this.cfg.get<boolean>("inlayHints.typeHints")!, | 77 | typeHints: this.cfg.get<boolean>("inlayHints.typeHints")!, |
@@ -93,21 +81,9 @@ export class Config { | |||
93 | }; | 81 | }; |
94 | } | 82 | } |
95 | 83 | ||
96 | get cargoWatchOptions() { | 84 | get checkOnSave() { |
97 | return { | ||
98 | enable: this.cfg.get<boolean>("cargo-watch.enable")!, | ||
99 | arguments: this.cfg.get<string[]>("cargo-watch.arguments")!, | ||
100 | allTargets: this.cfg.get<boolean>("cargo-watch.allTargets")!, | ||
101 | command: this.cfg.get<string>("cargo-watch.command")!, | ||
102 | }; | ||
103 | } | ||
104 | |||
105 | get cargoFeatures() { | ||
106 | return { | 85 | return { |
107 | noDefaultFeatures: this.cfg.get<boolean>("cargoFeatures.noDefaultFeatures")!, | 86 | command: this.cfg.get<string>("checkOnSave.command")!, |
108 | allFeatures: this.cfg.get<boolean>("cargoFeatures.allFeatures")!, | ||
109 | features: this.cfg.get<string[]>("cargoFeatures.features")!, | ||
110 | loadOutDirsFromCheck: this.cfg.get<boolean>("cargoFeatures.loadOutDirsFromCheck")!, | ||
111 | }; | 87 | }; |
112 | } | 88 | } |
113 | } | 89 | } |
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 86b5f3629..bd1c3de07 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts | |||
@@ -2,7 +2,7 @@ import * as vscode from 'vscode'; | |||
2 | import * as lc from 'vscode-languageclient'; | 2 | import * as lc from 'vscode-languageclient'; |
3 | 3 | ||
4 | import { Config } from './config'; | 4 | import { Config } from './config'; |
5 | import { createClient, configToServerOptions } from './client'; | 5 | import { createClient } from './client'; |
6 | import { isRustEditor, RustEditor } from './util'; | 6 | import { isRustEditor, RustEditor } from './util'; |
7 | 7 | ||
8 | export class Ctx { | 8 | export class Ctx { |
@@ -25,7 +25,6 @@ export class Ctx { | |||
25 | const res = new Ctx(config, extCtx, client, serverPath); | 25 | const res = new Ctx(config, extCtx, client, serverPath); |
26 | res.pushCleanup(client.start()); | 26 | res.pushCleanup(client.start()); |
27 | await client.onReady(); | 27 | await client.onReady(); |
28 | client.onRequest('workspace/configuration', _ => [configToServerOptions(config)]); | ||
29 | return res; | 28 | return res; |
30 | } | 29 | } |
31 | 30 | ||
diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts deleted file mode 100644 index ea2dfc0e3..000000000 --- a/editors/code/src/highlighting.ts +++ /dev/null | |||
@@ -1,255 +0,0 @@ | |||
1 | import * as vscode from 'vscode'; | ||
2 | import * as ra from './rust-analyzer-api'; | ||
3 | |||
4 | import { ColorTheme, TextMateRuleSettings } from './color_theme'; | ||
5 | |||
6 | import { Ctx } from './ctx'; | ||
7 | import { sendRequestWithRetry, isRustDocument } from './util'; | ||
8 | |||
9 | export function activateHighlighting(ctx: Ctx) { | ||
10 | const highlighter = new Highlighter(ctx); | ||
11 | |||
12 | ctx.client.onNotification(ra.publishDecorations, params => { | ||
13 | if (!ctx.config.highlightingOn) return; | ||
14 | |||
15 | const targetEditor = vscode.window.visibleTextEditors.find( | ||
16 | editor => { | ||
17 | const unescapedUri = unescape( | ||
18 | editor.document.uri.toString(), | ||
19 | ); | ||
20 | // Unescaped URI looks like: | ||
21 | // file:///c:/Workspace/ra-test/src/main.rs | ||
22 | return unescapedUri === params.uri; | ||
23 | }, | ||
24 | ); | ||
25 | if (!targetEditor) return; | ||
26 | |||
27 | highlighter.setHighlights(targetEditor, params.decorations); | ||
28 | }); | ||
29 | |||
30 | |||
31 | vscode.workspace.onDidChangeConfiguration( | ||
32 | _ => highlighter.removeHighlights(), | ||
33 | null, | ||
34 | ctx.subscriptions, | ||
35 | ); | ||
36 | |||
37 | vscode.window.onDidChangeActiveTextEditor( | ||
38 | async (editor: vscode.TextEditor | undefined) => { | ||
39 | if (!editor || !isRustDocument(editor.document)) return; | ||
40 | if (!ctx.config.highlightingOn) return; | ||
41 | const client = ctx.client; | ||
42 | if (!client) return; | ||
43 | |||
44 | const decorations = await sendRequestWithRetry( | ||
45 | client, | ||
46 | ra.decorationsRequest, | ||
47 | { uri: editor.document.uri.toString() }, | ||
48 | ); | ||
49 | highlighter.setHighlights(editor, decorations); | ||
50 | }, | ||
51 | null, | ||
52 | ctx.subscriptions, | ||
53 | ); | ||
54 | } | ||
55 | |||
56 | // Based on this HSL-based color generator: https://gist.github.com/bendc/76c48ce53299e6078a76 | ||
57 | function fancify(seed: string, shade: 'light' | 'dark') { | ||
58 | const random = randomU32Numbers(hashString(seed)); | ||
59 | const randomInt = (min: number, max: number) => { | ||
60 | return Math.abs(random()) % (max - min + 1) + min; | ||
61 | }; | ||
62 | |||
63 | const h = randomInt(0, 360); | ||
64 | const s = randomInt(42, 98); | ||
65 | const l = shade === 'light' ? randomInt(15, 40) : randomInt(40, 90); | ||
66 | return `hsl(${h},${s}%,${l}%)`; | ||
67 | } | ||
68 | |||
69 | class Highlighter { | ||
70 | private ctx: Ctx; | ||
71 | private decorations: Map< | ||
72 | string, | ||
73 | vscode.TextEditorDecorationType | ||
74 | > | null = null; | ||
75 | |||
76 | constructor(ctx: Ctx) { | ||
77 | this.ctx = ctx; | ||
78 | } | ||
79 | |||
80 | public removeHighlights() { | ||
81 | if (this.decorations == null) { | ||
82 | return; | ||
83 | } | ||
84 | |||
85 | // Decorations are removed when the object is disposed | ||
86 | for (const decoration of this.decorations.values()) { | ||
87 | decoration.dispose(); | ||
88 | } | ||
89 | |||
90 | this.decorations = null; | ||
91 | } | ||
92 | |||
93 | public setHighlights(editor: vscode.TextEditor, highlights: ra.Decoration[]) { | ||
94 | const client = this.ctx.client; | ||
95 | if (!client) return; | ||
96 | // Initialize decorations if necessary | ||
97 | // | ||
98 | // Note: decoration objects need to be kept around so we can dispose them | ||
99 | // if the user disables syntax highlighting | ||
100 | if (this.decorations == null) { | ||
101 | this.decorations = initDecorations(); | ||
102 | } | ||
103 | |||
104 | const byTag: Map<string, vscode.Range[]> = new Map(); | ||
105 | const colorfulIdents: Map< | ||
106 | string, | ||
107 | [vscode.Range[], boolean] | ||
108 | > = new Map(); | ||
109 | const rainbowTime = this.ctx.config.rainbowHighlightingOn; | ||
110 | |||
111 | for (const tag of this.decorations.keys()) { | ||
112 | byTag.set(tag, []); | ||
113 | } | ||
114 | |||
115 | for (const d of highlights) { | ||
116 | if (!byTag.get(d.tag)) { | ||
117 | continue; | ||
118 | } | ||
119 | |||
120 | if (rainbowTime && d.bindingHash) { | ||
121 | if (!colorfulIdents.has(d.bindingHash)) { | ||
122 | const mut = d.tag.endsWith('.mut'); | ||
123 | colorfulIdents.set(d.bindingHash, [[], mut]); | ||
124 | } | ||
125 | colorfulIdents | ||
126 | .get(d.bindingHash)![0] | ||
127 | .push( | ||
128 | client.protocol2CodeConverter.asRange(d.range), | ||
129 | ); | ||
130 | } else { | ||
131 | byTag | ||
132 | .get(d.tag)! | ||
133 | .push( | ||
134 | client.protocol2CodeConverter.asRange(d.range), | ||
135 | ); | ||
136 | } | ||
137 | } | ||
138 | |||
139 | for (const tag of byTag.keys()) { | ||
140 | const dec = this.decorations.get( | ||
141 | tag, | ||
142 | ) as vscode.TextEditorDecorationType; | ||
143 | const ranges = byTag.get(tag)!; | ||
144 | editor.setDecorations(dec, ranges); | ||
145 | } | ||
146 | |||
147 | for (const [hash, [ranges, mut]] of colorfulIdents.entries()) { | ||
148 | const textDecoration = mut ? 'underline' : undefined; | ||
149 | const dec = vscode.window.createTextEditorDecorationType({ | ||
150 | light: { color: fancify(hash, 'light'), textDecoration }, | ||
151 | dark: { color: fancify(hash, 'dark'), textDecoration }, | ||
152 | }); | ||
153 | editor.setDecorations(dec, ranges); | ||
154 | } | ||
155 | } | ||
156 | } | ||
157 | |||
158 | function initDecorations(): Map<string, vscode.TextEditorDecorationType> { | ||
159 | const theme = ColorTheme.load(); | ||
160 | const res = new Map(); | ||
161 | TAG_TO_SCOPES.forEach((scopes, tag) => { | ||
162 | // We are going to axe this soon, so don't try to detect unknown tags. | ||
163 | // Users should switch to the new semantic tokens implementation. | ||
164 | if (!scopes) return; | ||
165 | const rule = theme.lookup(scopes); | ||
166 | const decor = createDecorationFromTextmate(rule); | ||
167 | res.set(tag, decor); | ||
168 | }); | ||
169 | return res; | ||
170 | } | ||
171 | |||
172 | function createDecorationFromTextmate( | ||
173 | themeStyle: TextMateRuleSettings, | ||
174 | ): vscode.TextEditorDecorationType { | ||
175 | const decorationOptions: vscode.DecorationRenderOptions = {}; | ||
176 | decorationOptions.rangeBehavior = vscode.DecorationRangeBehavior.OpenOpen; | ||
177 | |||
178 | if (themeStyle.foreground) { | ||
179 | decorationOptions.color = themeStyle.foreground; | ||
180 | } | ||
181 | |||
182 | if (themeStyle.background) { | ||
183 | decorationOptions.backgroundColor = themeStyle.background; | ||
184 | } | ||
185 | |||
186 | if (themeStyle.fontStyle) { | ||
187 | const parts: string[] = themeStyle.fontStyle.split(' '); | ||
188 | parts.forEach(part => { | ||
189 | switch (part) { | ||
190 | case 'italic': | ||
191 | decorationOptions.fontStyle = 'italic'; | ||
192 | break; | ||
193 | case 'bold': | ||
194 | decorationOptions.fontWeight = 'bold'; | ||
195 | break; | ||
196 | case 'underline': | ||
197 | decorationOptions.textDecoration = 'underline'; | ||
198 | break; | ||
199 | default: | ||
200 | break; | ||
201 | } | ||
202 | }); | ||
203 | } | ||
204 | return vscode.window.createTextEditorDecorationType(decorationOptions); | ||
205 | } | ||
206 | |||
207 | // sync with tags from `syntax_highlighting.rs`. | ||
208 | const TAG_TO_SCOPES = new Map<string, string[]>([ | ||
209 | ["field", ["entity.name.field"]], | ||
210 | ["function", ["entity.name.function"]], | ||
211 | ["module", ["entity.name.module"]], | ||
212 | ["constant", ["entity.name.constant"]], | ||
213 | ["macro", ["entity.name.macro"]], | ||
214 | |||
215 | ["variable", ["variable"]], | ||
216 | ["variable.mutable", ["variable", "meta.mutable"]], | ||
217 | |||
218 | ["type", ["entity.name.type"]], | ||
219 | ["type.builtin", ["entity.name.type", "support.type.primitive"]], | ||
220 | ["type.self", ["entity.name.type.parameter.self"]], | ||
221 | ["type.param", ["entity.name.type.parameter", "entity.name.type.param.rust"]], | ||
222 | ["type.lifetime", ["entity.name.type.lifetime", "entity.name.lifetime.rust"]], | ||
223 | |||
224 | ["literal.byte", ["constant.character.byte"]], | ||
225 | ["literal.char", ["constant.character.rust"]], | ||
226 | ["numeric_literal", ["constant.numeric"]], | ||
227 | |||
228 | ["comment", ["comment"]], | ||
229 | ["string_literal", ["string.quoted"]], | ||
230 | ["attribute", ["meta.attribute.rust"]], | ||
231 | |||
232 | ["keyword", ["keyword"]], | ||
233 | ["keyword.unsafe", ["keyword.other.unsafe"]], | ||
234 | ["keyword.control", ["keyword.control"]], | ||
235 | ]); | ||
236 | |||
237 | function randomU32Numbers(seed: number) { | ||
238 | let random = seed | 0; | ||
239 | return () => { | ||
240 | random ^= random << 13; | ||
241 | random ^= random >> 17; | ||
242 | random ^= random << 5; | ||
243 | random |= 0; | ||
244 | return random; | ||
245 | }; | ||
246 | } | ||
247 | |||
248 | function hashString(str: string): number { | ||
249 | let res = 0; | ||
250 | for (let i = 0; i < str.length; ++i) { | ||
251 | const c = str.codePointAt(i)!; | ||
252 | res = (res * 31 + c) & ~0; | ||
253 | } | ||
254 | return res; | ||
255 | } | ||
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 7ba16120c..4f3b89f44 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts | |||
@@ -7,7 +7,6 @@ import * as commands from './commands'; | |||
7 | import { activateInlayHints } from './inlay_hints'; | 7 | import { activateInlayHints } from './inlay_hints'; |
8 | import { activateStatusDisplay } from './status_display'; | 8 | import { activateStatusDisplay } from './status_display'; |
9 | import { Ctx } from './ctx'; | 9 | import { Ctx } from './ctx'; |
10 | import { activateHighlighting } from './highlighting'; | ||
11 | import { Config, NIGHTLY_TAG } from './config'; | 10 | import { Config, NIGHTLY_TAG } from './config'; |
12 | import { log, assert } from './util'; | 11 | import { log, assert } from './util'; |
13 | import { PersistentState } from './persistent_state'; | 12 | import { PersistentState } from './persistent_state'; |
@@ -97,9 +96,6 @@ export async function activate(context: vscode.ExtensionContext) { | |||
97 | 96 | ||
98 | activateStatusDisplay(ctx); | 97 | activateStatusDisplay(ctx); |
99 | 98 | ||
100 | if (!ctx.config.highlightingSemanticTokens) { | ||
101 | activateHighlighting(ctx); | ||
102 | } | ||
103 | activateInlayHints(ctx); | 99 | activateInlayHints(ctx); |
104 | 100 | ||
105 | vscode.workspace.onDidChangeConfiguration( | 101 | vscode.workspace.onDidChangeConfiguration( |
diff --git a/editors/code/src/status_display.ts b/editors/code/src/status_display.ts index 0f5f6ef99..f9cadc8a2 100644 --- a/editors/code/src/status_display.ts +++ b/editors/code/src/status_display.ts | |||
@@ -7,7 +7,7 @@ import { Ctx } from './ctx'; | |||
7 | const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; | 7 | const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; |
8 | 8 | ||
9 | export function activateStatusDisplay(ctx: Ctx) { | 9 | export function activateStatusDisplay(ctx: Ctx) { |
10 | const statusDisplay = new StatusDisplay(ctx.config.cargoWatchOptions.command); | 10 | const statusDisplay = new StatusDisplay(ctx.config.checkOnSave.command); |
11 | ctx.pushCleanup(statusDisplay); | 11 | ctx.pushCleanup(statusDisplay); |
12 | const client = ctx.client; | 12 | const client = ctx.client; |
13 | if (client != null) { | 13 | if (client != null) { |