Skip to content

Commit e919afa

Browse files
authored
playground: add quick fixes (#606)
1 parent abad683 commit e919afa

File tree

3 files changed

+141
-3
lines changed

3 files changed

+141
-3
lines changed

crates/squawk_wasm/src/lib.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,22 @@ struct LintError {
6464
range_end: usize,
6565
// used for the linter tab
6666
messages: Vec<String>,
67+
fix: Option<Fix>,
68+
}
69+
70+
#[derive(Serialize)]
71+
struct Fix {
72+
title: String,
73+
edits: Vec<TextEdit>,
74+
}
75+
76+
#[derive(Serialize)]
77+
struct TextEdit {
78+
start_line_number: u32,
79+
start_column: u32,
80+
end_line_number: u32,
81+
end_column: u32,
82+
text: String,
6783
}
6884

6985
#[wasm_bindgen]
@@ -97,6 +113,7 @@ pub fn lint(text: String) -> Result<JsValue, Error> {
97113
range_start: range_start.into(),
98114
range_end: range_end.into(),
99115
messages: vec![],
116+
fix: None,
100117
}
101118
});
102119

@@ -116,6 +133,36 @@ pub fn lint(text: String) -> Result<JsValue, Error> {
116133
None => vec![],
117134
};
118135

136+
let fix = x.fix.map(|fix| {
137+
let edits = fix
138+
.edits
139+
.into_iter()
140+
.filter_map(|edit| {
141+
let start_pos = line_index.line_col(edit.text_range.start());
142+
let end_pos = line_index.line_col(edit.text_range.end());
143+
let start_wide = line_index
144+
.to_wide(line_index::WideEncoding::Utf16, start_pos)
145+
.unwrap();
146+
let end_wide = line_index
147+
.to_wide(line_index::WideEncoding::Utf16, end_pos)
148+
.unwrap();
149+
150+
Some(TextEdit {
151+
start_line_number: start_wide.line,
152+
start_column: start_wide.col,
153+
end_line_number: end_wide.line,
154+
end_column: end_wide.col,
155+
text: edit.text.unwrap_or_default(),
156+
})
157+
})
158+
.collect();
159+
160+
Fix {
161+
title: fix.title,
162+
edits,
163+
}
164+
});
165+
119166
LintError {
120167
code: x.code.to_string(),
121168
range_start: x.text_range.start().into(),
@@ -128,6 +175,7 @@ pub fn lint(text: String) -> Result<JsValue, Error> {
128175
start_column: start.col,
129176
end_line_number: end.line,
130177
end_column: end.col,
178+
fix,
131179
}
132180
});
133181

playground/src/App.tsx

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useState, useEffect, useLayoutEffect, useRef } from "react"
22
import * as monaco from "monaco-editor"
3-
import { LintError, useDumpCst, useDumpTokens, useErrors } from "./squawk"
3+
import { LintError, Fix, useDumpCst, useDumpTokens, useErrors } from "./squawk"
44
import {
55
compress,
66
compressToEncodedURIComponent,
@@ -231,14 +231,16 @@ function Editor({
231231
onChange?: (_: string) => void
232232
onSave?: (_: string) => void
233233
settings: monaco.editor.IStandaloneEditorConstructionOptions
234-
markers?: monaco.editor.IMarkerData[]
234+
markers?: Marker[]
235235
}) {
236236
const onChangeRef = useRef<((_: string) => void) | undefined>(null)
237237
const onSaveRef = useRef<((_: string) => void) | undefined>(null)
238238
const divRef = useRef<HTMLDivElement>(null)
239239
const autoFocusRef = useRef(autoFocus)
240240
const settingsInitial = useRef(settings)
241241
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>(null)
242+
const fixesRef = useRef<Map<string, Fix>>(new Map())
243+
242244
// TODO: replace with useEventEffect
243245
useEffect(() => {
244246
onChangeRef.current = onChange
@@ -251,6 +253,16 @@ function Editor({
251253
if (markers == null) {
252254
return
253255
}
256+
257+
const fixesMap = new Map<string, Fix>()
258+
for (const marker of markers) {
259+
if (marker.fix) {
260+
const key = createMarkerKey(marker)
261+
fixesMap.set(key, marker.fix)
262+
}
263+
}
264+
fixesRef.current = fixesMap
265+
254266
const model = editorRef.current?.getModel()
255267
if (model != null) {
256268
monaco.editor.setModelMarkers(model, "squawk", markers)
@@ -269,7 +281,7 @@ function Editor({
269281
onSaveRef.current?.(editor.getValue())
270282
})
271283
monaco.languages.register({ id: "rast" })
272-
monaco.languages.setMonarchTokensProvider("rast", {
284+
const tokenProvider = monaco.languages.setMonarchTokensProvider("rast", {
273285
tokenizer: {
274286
// via: https://github.com/rust-lang/rust-analyzer/blob/9691da7707ea7c50922fe1647b1c2af47934b9fa/editors/code/ra_syntax_tree.tmGrammar.json#L16C17-L16C17
275287
root: [
@@ -284,6 +296,55 @@ function Editor({
284296
],
285297
},
286298
})
299+
300+
const codeActionProvider = monaco.languages.registerCodeActionProvider(
301+
"pgsql",
302+
{
303+
provideCodeActions: (model, _range, context) => {
304+
const actions: monaco.languages.CodeAction[] = []
305+
for (const marker of context.markers) {
306+
if (marker.source === "squawk") {
307+
const key = createMarkerKey(marker)
308+
const fix = fixesRef.current.get(key)
309+
if (fix) {
310+
const edits = fix.edits.map(
311+
(edit): monaco.languages.IWorkspaceTextEdit => {
312+
return {
313+
resource: model.uri,
314+
versionId: model.getVersionId(),
315+
textEdit: {
316+
range: new monaco.Range(
317+
edit.start_line_number + 1,
318+
edit.start_column + 1,
319+
edit.end_line_number + 1,
320+
edit.end_column + 1,
321+
),
322+
text: edit.text,
323+
},
324+
}
325+
},
326+
)
327+
actions.push({
328+
title: fix.title,
329+
diagnostics: [marker],
330+
kind: "quickfix",
331+
edit: {
332+
edits,
333+
},
334+
isPreferred: true,
335+
})
336+
}
337+
}
338+
}
339+
340+
return {
341+
actions,
342+
dispose: () => {},
343+
}
344+
},
345+
},
346+
)
347+
287348
editor.onDidChangeModelContent(() => {
288349
onChangeRef.current?.(editor.getValue())
289350
})
@@ -293,7 +354,9 @@ function Editor({
293354
editorRef.current = editor
294355
return () => {
295356
editorRef.current = null
357+
codeActionProvider.dispose()
296358
editor?.dispose()
359+
tokenProvider.dispose()
297360
}
298361
}, [])
299362
useEffect(() => {
@@ -330,6 +393,18 @@ type Marker = monaco.editor.IMarkerData & {
330393
range_start: number
331394
range_end: number
332395
messages: string[]
396+
fix?: Fix
397+
}
398+
399+
function createMarkerKey(marker: {
400+
startLineNumber: number
401+
startColumn: number
402+
endLineNumber: number
403+
endColumn: number
404+
message: string
405+
}): string {
406+
// TODO: probably a better way to do this
407+
return `${marker.startLineNumber}:${marker.startColumn}:${marker.endLineNumber}:${marker.endColumn}:${marker.message}`
333408
}
334409

335410
function SyntaxTreePanel({ text }: { text: string }) {
@@ -372,6 +447,7 @@ function useMarkers(text: string): Array<Marker> {
372447
range_start: x.range_start,
373448
range_end: x.range_end,
374449
messages: x.messages,
450+
fix: x.fix,
375451
code: {
376452
value: x.code,
377453
target: monaco.Uri.parse(

playground/src/squawk.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@ import initWasm, {
55
lint as lint_,
66
} from "./pkg/squawk_wasm"
77

8+
export type TextEdit = {
9+
start_line_number: number
10+
start_column: number
11+
end_line_number: number
12+
end_column: number
13+
text: string
14+
}
15+
16+
export type Fix = {
17+
title: string
18+
edits: TextEdit[]
19+
}
20+
821
export type LintError = {
922
code: string
1023
message: string
@@ -16,6 +29,7 @@ export type LintError = {
1629
range_start: number
1730
range_end: number
1831
messages: string[]
32+
fix?: Fix
1933
}
2034

2135
function lintWithTypes(text: string): Array<LintError> {

0 commit comments

Comments
 (0)