Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions crates/red_knot_wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,14 +247,14 @@ impl Diagnostic {
Severity::from(self.inner.severity())
}

#[wasm_bindgen]
#[wasm_bindgen(js_name = "textRange")]
pub fn text_range(&self) -> Option<TextRange> {
self.inner
.span()
.and_then(|span| Some(TextRange::from(span.range()?)))
}

#[wasm_bindgen]
#[wasm_bindgen(js_name = "toRange")]
pub fn to_range(&self, workspace: &Workspace) -> Option<Range> {
self.inner.span().and_then(|span| {
let line_index = line_index(workspace.db.upcast(), span.file());
Expand Down Expand Up @@ -287,22 +287,25 @@ pub struct Range {
pub end: Position,
}

#[wasm_bindgen]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct Position {
/// One indexed line number
pub line: usize,

/// One indexed column number (the nth character on the line)
pub column: usize,
}

impl From<SourceLocation> for Position {
fn from(location: SourceLocation) -> Self {
Self {
line: location.row.to_zero_indexed(),
character: location.column.to_zero_indexed(),
line: location.row.get(),
column: location.column.get(),
}
}
}

#[wasm_bindgen]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct Position {
pub line: usize,
pub character: usize,
}

#[wasm_bindgen]
#[derive(Copy, Clone, Hash, PartialEq, Eq)]
pub enum Severity {
Expand Down
5 changes: 1 addition & 4 deletions crates/red_knot_wasm/tests/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ fn check() {
assert_eq!(diagnostic.id(), "lint:unresolved-import");
assert_eq!(
diagnostic.to_range(&workspace).unwrap().start,
Position {
line: 0,
character: 7
}
Position { line: 1, column: 8 }
);
assert_eq!(diagnostic.message(), "Cannot resolve import `random22`");
}
48 changes: 37 additions & 11 deletions playground/knot/src/Editor/Chrome.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
lazy,
use,
useCallback,
useDeferredValue,
Expand All @@ -16,15 +17,16 @@ import type { Diagnostic, Workspace } from "red_knot_wasm";
import { Panel, PanelGroup } from "react-resizable-panels";
import { Files } from "./Files";
import SecondarySideBar from "./SecondarySideBar";
import Editor from "./Editor";
import SecondaryPanel, {
SecondaryPanelResult,
SecondaryTool,
} from "./SecondaryPanel";
import Diagnostics from "./Diagnostics";
import { editor } from "monaco-editor";
import IStandaloneCodeEditor = editor.IStandaloneCodeEditor;
import { FileId, ReadonlyFiles } from "../Playground";
import type { editor } from "monaco-editor";
import type { Monaco } from "@monaco-editor/react";

const Editor = lazy(() => import("./Editor"));

interface CheckResult {
diagnostics: Diagnostic[];
Expand Down Expand Up @@ -66,11 +68,14 @@ export default function Chrome({
null,
);

const editorRef = useRef<IStandaloneCodeEditor | null>(null);
const editorRef = useRef<{
editor: editor.IStandaloneCodeEditor;
monaco: Monaco;
} | null>(null);

const handleFileRenamed = (file: FileId, newName: string) => {
onFileRenamed(workspace, file, newName);
editorRef.current?.focus();
editorRef.current?.editor.focus();
};

const handleSecondaryToolSelected = useCallback(
Expand All @@ -86,12 +91,15 @@ export default function Chrome({
[],
);

const handleEditorMount = useCallback((editor: IStandaloneCodeEditor) => {
editorRef.current = editor;
}, []);
const handleEditorMount = useCallback(
(editor: editor.IStandaloneCodeEditor, monaco: Monaco) => {
editorRef.current = { editor, monaco };
},
[],
);

const handleGoTo = useCallback((line: number, column: number) => {
const editor = editorRef.current;
const editor = editorRef.current?.editor;

if (editor == null) {
return;
Expand All @@ -107,6 +115,25 @@ export default function Chrome({
editor.setSelection(range);
}, []);

const handleRemoved = useCallback(
async (id: FileId) => {
const name = files.index.find((file) => file.id === id)?.name;

if (name != null && editorRef.current != null) {
// Remove the file from the monaco state to avoid that monaco "restores" the old content.
// An alternative is to use a `key` on the `Editor` but that means we lose focus and selection
// range when changing between tabs.
const monaco = await import("monaco-editor");
editorRef.current.monaco.editor
.getModel(monaco.Uri.file(name))
?.dispose();
}

onFileRemoved(workspace, id);
},
[workspace, files.index, onFileRemoved],
);

const checkResult = useCheckResult(files, workspace, secondaryTool);

return (
Expand All @@ -120,7 +147,7 @@ export default function Chrome({
onAdd={(name) => onFileAdded(workspace, name)}
onRename={handleFileRenamed}
onSelected={onFileSelected}
onRemove={(id) => onFileRemoved(workspace, id)}
onRemove={handleRemoved}
/>
<PanelGroup direction="horizontal" autoSaveId="main">
<Panel
Expand All @@ -132,7 +159,6 @@ export default function Chrome({
<PanelGroup id="vertical" direction="vertical">
<Panel minSize={10} className="my-2" order={0}>
<Editor
key={selectedFileName}
theme={theme}
visible={true}
fileName={selectedFileName}
Expand Down
10 changes: 5 additions & 5 deletions playground/knot/src/Editor/Diagnostics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function Diagnostics({
const diagnostics = useMemo(() => {
const sorted = [...unsorted];
sorted.sort((a, b) => {
return (a.text_range()?.start ?? 0) - (b.text_range()?.start ?? 0);
return (a.textRange()?.start ?? 0) - (b.textRange()?.start ?? 0);
});

return sorted;
Expand Down Expand Up @@ -73,15 +73,15 @@ function Items({
return (
<ul className="space-y-0.5 grow overflow-y-scroll">
{diagnostics.map((diagnostic, index) => {
const position = diagnostic.to_range(workspace);
const position = diagnostic.toRange(workspace);
const start = position?.start;
const id = diagnostic.id();

const startLine = (start?.line ?? 0) + 1;
const startColumn = (start?.character ?? 0) + 1;
const startLine = start?.line ?? 1;
const startColumn = start?.column ?? 1;

return (
<li key={`${diagnostic.text_range()?.start ?? 0}-${id ?? index}`}>
<li key={`${startLine}:${startColumn}-${id ?? index}`}>
<button
onClick={() => onGoTo(startLine, startColumn)}
className="w-full text-start cursor-pointer select-text"
Expand Down
14 changes: 7 additions & 7 deletions playground/knot/src/Editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type Props = {
theme: Theme;
workspace: Workspace;
onChange(content: string): void;
onMount(editor: IStandaloneCodeEditor): void;
onMount(editor: IStandaloneCodeEditor, monaco: Monaco): void;
};

type MonacoEditorState = {
Expand Down Expand Up @@ -63,7 +63,7 @@ export default function Editor({
monaco: instance,
};

onMount(editor);
onMount(editor, instance);
},

[onMount, workspace, diagnostics],
Expand Down Expand Up @@ -120,14 +120,14 @@ function updateMarkers(
}
};

const range = diagnostic.to_range(workspace);
const range = diagnostic.toRange(workspace);

return {
code: diagnostic.id(),
startLineNumber: (range?.start?.line ?? 0) + 1,
startColumn: (range?.start?.character ?? 0) + 1,
endLineNumber: (range?.end?.line ?? 0) + 1,
endColumn: (range?.end?.character ?? 0) + 1,
startLineNumber: range?.start?.line ?? 0,
startColumn: range?.start?.column ?? 0,
endLineNumber: range?.end?.line ?? 0,
endColumn: range?.end?.column ?? 0,
message: diagnostic.message(),
severity: mapSeverity(diagnostic.severity()),
tags: [],
Expand Down
1 change: 1 addition & 0 deletions playground/knot/src/Editor/Files.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ function FileEntry({ name, onClicked, onRenamed, selected }: FileEntryProps) {
switch (event.key) {
case "Enter":
event.currentTarget.blur();
event.preventDefault();
return;
case "Escape":
setNewName(null);
Expand Down
Loading