From df1084e969a280b95775da3a580ceb9ad25f9700 Mon Sep 17 00:00:00 2001 From: Cubxity Date: Sat, 16 Sep 2023 21:23:30 +0200 Subject: [PATCH] feat: improve diagnostics and add preview state indicator --- package.json | 1 + pnpm-lock.yaml | 12 +++- src-tauri/src/ipc/commands/typst.rs | 35 +++++++---- src-tauri/src/ipc/model.rs | 13 +++- src/components/Editor.svelte | 60 ++++++++++++------- src/components/ExplorerNode.svelte | 13 ++-- src/components/ExplorerTree.svelte | 7 +-- src/components/ShellModal.svelte | 8 +-- src/components/SidePanel.svelte | 8 ++- src/components/StatusBar.svelte | 21 +++++++ src/components/icons/AddIcon.svelte | 4 -- src/components/icons/ArrowDropDownIcon.svelte | 1 - src/components/icons/ArrowRightIcon.svelte | 1 - src/components/icons/CloseIcon.svelte | 4 -- src/components/icons/FileIcon.svelte | 3 - src/components/icons/FolderIcon.svelte | 4 -- src/lib/ipc/typst.ts | 8 ++- src/lib/stores.ts | 11 ++++ 18 files changed, 138 insertions(+), 76 deletions(-) delete mode 100644 src/components/icons/AddIcon.svelte delete mode 100644 src/components/icons/ArrowDropDownIcon.svelte delete mode 100644 src/components/icons/ArrowRightIcon.svelte delete mode 100644 src/components/icons/CloseIcon.svelte delete mode 100644 src/components/icons/FileIcon.svelte delete mode 100644 src/components/icons/FolderIcon.svelte diff --git a/package.json b/package.json index 501b699..99b3061 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@tauri-apps/api": "^1.3.0", "clsx": "^1.2.1", "lodash": "^4.17.21", + "lucide-svelte": "^0.279.0", "monaco-editor": "^0.38.0", "vscode-oniguruma": "^1.7.0", "vscode-textmate": "^9.0.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 71643bb..cc0106a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,6 +10,9 @@ dependencies: lodash: specifier: ^4.17.21 version: 4.17.21 + lucide-svelte: + specifier: ^0.279.0 + version: 0.279.0(svelte@3.59.1) monaco-editor: specifier: ^0.38.0 version: 0.38.0 @@ -1874,6 +1877,14 @@ packages: yallist: 4.0.0 dev: true + /lucide-svelte@0.279.0(svelte@3.59.1): + resolution: {integrity: sha512-u9j8tMPxWsv5iXJvrUU/jpyML/k49flr7440UE8QM9V3u0OZt5+qaY5TMiPDTVRMdEELBg4d4ueW1+3Mo3VT4A==} + peerDependencies: + svelte: '>=3 <5' + dependencies: + svelte: 3.59.1 + dev: false + /magic-string@0.27.0: resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} engines: {node: '>=12'} @@ -2533,7 +2544,6 @@ packages: /svelte@3.59.1: resolution: {integrity: sha512-pKj8fEBmqf6mq3/NfrB9SLtcJcUvjYSWyePlfCqN9gujLB25RitWK8PvFzlwim6hD/We35KbPlRteuA6rnPGcQ==} engines: {node: '>= 8'} - dev: true /tailwindcss@3.3.2: resolution: {integrity: sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==} diff --git a/src-tauri/src/ipc/commands/typst.rs b/src-tauri/src/ipc/commands/typst.rs index 7ed19b1..01519f6 100644 --- a/src-tauri/src/ipc/commands/typst.rs +++ b/src-tauri/src/ipc/commands/typst.rs @@ -1,7 +1,9 @@ use super::{Error, Result}; use crate::ipc::commands::project; use crate::ipc::model::TypstRenderResponse; -use crate::ipc::{TypstCompileEvent, TypstDocument, TypstSourceError}; +use crate::ipc::{ + TypstCompileEvent, TypstDiagnosticSeverity, TypstDocument, TypstSourceDiagnostic, +}; use crate::project::ProjectManager; use base64::Engine; use log::debug; @@ -13,6 +15,7 @@ use std::path::PathBuf; use std::sync::Arc; use std::time::Instant; use tauri::Runtime; +use typst::diag::Severity; use typst::eval::Tracer; use typst::geom::Color; use typst::ide::{Completion, CompletionKind}; @@ -118,28 +121,36 @@ pub async fn typst_compile( width: width.to_pt(), height: height.to_pt(), }), - errors: None, + diagnostics: None, }, ); } - Err(errors) => { - debug!("compilation failed with {:?} errors", errors.len()); + Err(diagnostics) => { + debug!( + "compilation failed with {:?} diagnostics", + diagnostics.len() + ); let source = world.source(source_id); - let errors: Vec = match source { - Ok(source) => errors + let diagnostics: Vec = match source { + Ok(source) => diagnostics .iter() - .filter(|e| e.span.id() == Some(source_id)) - .filter_map(|e| { - let span = source.find(e.span)?; + .filter(|d| d.span.id() == Some(source_id)) + .filter_map(|d| { + let span = source.find(d.span)?; let range = span.range(); let start = content[..range.start].chars().count(); let size = content[range.start..range.end].chars().count(); - let message = e.message.to_string(); - Some(TypstSourceError { + let message = d.message.to_string(); + Some(TypstSourceDiagnostic { range: start..start + size, + severity: match d.severity { + Severity::Error => TypstDiagnosticSeverity::Error, + Severity::Warning => TypstDiagnosticSeverity::Warning, + }, message, + hints: d.hints.iter().map(|hint| hint.to_string()).collect(), }) }) .collect(), @@ -150,7 +161,7 @@ pub async fn typst_compile( "typst_compile", TypstCompileEvent { document: None, - errors: Some(errors), + diagnostics: Some(diagnostics), }, ); } diff --git a/src-tauri/src/ipc/model.rs b/src-tauri/src/ipc/model.rs index 83a6a60..a4b6518 100644 --- a/src-tauri/src/ipc/model.rs +++ b/src-tauri/src/ipc/model.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; #[derive(Serialize, Clone, Debug)] pub struct TypstCompileEvent { pub document: Option, - pub errors: Option>, + pub diagnostics: Option>, } #[derive(Serialize, Clone, Debug)] @@ -17,9 +17,18 @@ pub struct TypstDocument { } #[derive(Serialize, Clone, Debug)] -pub struct TypstSourceError { +#[serde(rename_all = "snake_case")] +pub enum TypstDiagnosticSeverity { + Error, + Warning, +} + +#[derive(Serialize, Clone, Debug)] +pub struct TypstSourceDiagnostic { pub range: Range, + pub severity: TypstDiagnosticSeverity, pub message: String, + pub hints: Vec, } #[derive(Serialize, Clone, Debug)] diff --git a/src/components/Editor.svelte b/src/components/Editor.svelte index e785daf..c96ee22 100644 --- a/src/components/Editor.svelte +++ b/src/components/Editor.svelte @@ -13,6 +13,7 @@ import IMarkerData = editorType.IMarkerData; import { paste } from "$lib/ipc/clipboard"; import { throttle } from "$lib/fn"; + import { PreviewState, shell } from "$lib/stores"; let divEl: HTMLDivElement; let editor: ICodeEditor; @@ -23,6 +24,8 @@ const handleCompile = async () => { const model = editor.getModel(); if (model) { + shell.setPreviewState(PreviewState.Compiling); + // Removing the preceding slash await compile(model.uri.path, model.getValue()); } @@ -44,9 +47,9 @@ // @ts-ignore self.MonacoEnvironment = { - getWorker: function(_moduleId: any, label: string) { + getWorker: function (_moduleId: any, label: string) { return new EditorWorker.default(); - } + }, }; editor = (await monacoImport).editor.create(divEl, { @@ -56,7 +59,7 @@ folding: true, quickSuggestions: false, wordWrap: "on", - unicodeHighlight: { ambiguousCharacters: false } + unicodeHighlight: { ambiguousCharacters: false }, }); editor.onDidChangeModel((e: IModelChangedEvent) => { @@ -79,24 +82,31 @@ // Returns an unlisten function return appWindow.listen("typst_compile", ({ event, payload }) => { - const { errors } = payload; + const { document, diagnostics } = payload; const model = editor.getModel(); if (model) { - const markers: IMarkerData[] = errors?.map(({ range, message }) => { - const start = model.getPositionAt(range.start); - const end = model.getPositionAt(range.end); - return { - startLineNumber: start.lineNumber, - startColumn: start.column, - endLineNumber: end.lineNumber, - endColumn: end.column, - message, - severity: monaco.MarkerSeverity.Error - }; - }) ?? []; + const markers: IMarkerData[] = + diagnostics?.map(({ range, severity, message, hints }) => { + const start = model.getPositionAt(range.start); + const end = model.getPositionAt(range.end); + return { + startLineNumber: start.lineNumber, + startColumn: start.column, + endLineNumber: end.lineNumber, + endColumn: end.column, + message: message + "\n" + hints.map((hint) => `hint: ${hint}`).join("\n"), + severity: + severity === "error" ? monaco.MarkerSeverity.Error : monaco.MarkerSeverity.Warning, + }; + }) ?? []; monaco.editor.setModelMarkers(model, "owner", markers); } + if (document) { + shell.setPreviewState(PreviewState.Idle); + } else { + shell.setPreviewState(PreviewState.CompileError); + } }); }); @@ -140,12 +150,16 @@ const range = editor.getSelection(); const model = editor.getModel(); if (range && model) { - model.pushEditOperations([], [ - { - range: range, - text: `\n#figure(\n image("${res.path}"),\n caption: []\n)\n` - } - ], () => null); + model.pushEditOperations( + [], + [ + { + range: range, + text: `\n#figure(\n image("${res.path}"),\n caption: []\n)\n`, + }, + ], + () => null + ); } } }; @@ -153,4 +167,4 @@ $: fetchContent(editor, path); -
\ No newline at end of file +
diff --git a/src/components/ExplorerNode.svelte b/src/components/ExplorerNode.svelte index 215faeb..71a0c37 100644 --- a/src/components/ExplorerNode.svelte +++ b/src/components/ExplorerNode.svelte @@ -1,13 +1,10 @@ @@ -25,7 +24,7 @@ Project
diff --git a/src/components/ShellModal.svelte b/src/components/ShellModal.svelte index 13be0ee..057d54a 100644 --- a/src/components/ShellModal.svelte +++ b/src/components/ShellModal.svelte @@ -1,7 +1,7 @@ + +
+
+ {#if $shell.previewState === PreviewState.CompileError} +
+ + Compile error +
+ {:else if $shell.previewState === PreviewState.Compiling} +
+ + Compiling... +
+ {/if} +
diff --git a/src/components/icons/AddIcon.svelte b/src/components/icons/AddIcon.svelte deleted file mode 100644 index 9104cf6..0000000 --- a/src/components/icons/AddIcon.svelte +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/src/components/icons/ArrowDropDownIcon.svelte b/src/components/icons/ArrowDropDownIcon.svelte deleted file mode 100644 index 0a844f8..0000000 --- a/src/components/icons/ArrowDropDownIcon.svelte +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/icons/ArrowRightIcon.svelte b/src/components/icons/ArrowRightIcon.svelte deleted file mode 100644 index 647a613..0000000 --- a/src/components/icons/ArrowRightIcon.svelte +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/icons/CloseIcon.svelte b/src/components/icons/CloseIcon.svelte deleted file mode 100644 index f2adfe5..0000000 --- a/src/components/icons/CloseIcon.svelte +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/src/components/icons/FileIcon.svelte b/src/components/icons/FileIcon.svelte deleted file mode 100644 index ff2798a..0000000 --- a/src/components/icons/FileIcon.svelte +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/components/icons/FolderIcon.svelte b/src/components/icons/FolderIcon.svelte deleted file mode 100644 index 438f083..0000000 --- a/src/components/icons/FolderIcon.svelte +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/src/lib/ipc/typst.ts b/src/lib/ipc/typst.ts index eb14b60..e4a7ce9 100644 --- a/src/lib/ipc/typst.ts +++ b/src/lib/ipc/typst.ts @@ -2,7 +2,7 @@ import { invoke } from "@tauri-apps/api"; export interface TypstCompileEvent { document: TypstDocument | null; - errors: TypstSourceError[] | null; + diagnostics: TypstSourceDiagnostic[] | null; } export interface TypstDocument { @@ -12,9 +12,13 @@ export interface TypstDocument { height: number; } -export interface TypstSourceError { +export type TypstDiagnosticSeverity = "error" | "warning"; + +export interface TypstSourceDiagnostic { range: { start: number; end: number }; + severity: TypstDiagnosticSeverity; message: string; + hints: string[]; } export interface TypstRenderResponse { diff --git a/src/lib/stores.ts b/src/lib/stores.ts index 116d8c6..c3e5118 100644 --- a/src/lib/stores.ts +++ b/src/lib/stores.ts @@ -9,6 +9,7 @@ export const project = writable(null); export interface Shell { selectedFile: string | undefined; modals: Modal[]; + previewState: PreviewState; } export interface BaseModal { @@ -23,10 +24,17 @@ export interface InputModal extends BaseModal { export type Modal = InputModal; +export enum PreviewState { + Idle, + Compiling, + CompileError, +} + const createShell = () => { const { subscribe, set, update } = writable({ selectedFile: undefined, modals: [], + previewState: PreviewState.Idle, }); return { @@ -53,6 +61,9 @@ const createShell = () => { }; }); }, + setPreviewState(previewState: PreviewState) { + update((shell) => ({ ...shell, previewState })); + }, }; };