From e7fe0af61c4806996c566e0de71a9a2a2cda20ba Mon Sep 17 00:00:00 2001 From: wflixu Date: Wed, 24 Apr 2024 20:45:31 +0800 Subject: [PATCH] [feat] compile error tips --- src-tauri/src/ipc/commands/typst.rs | 177 +++++++--------------------- src-tauri/src/ipc/model.rs | 1 + src-tauri/src/main.rs | 1 - src-tauri/tauri.conf.json | 7 +- src-tauri/tauri.macos.conf.json | 19 +++ src/components/MonacoEditor.vue | 6 +- src/main.ts | 2 +- src/pages/typst/DiagnosticsTip.vue | 60 ++++++++++ src/pages/typst/PreviewPage.vue | 2 +- src/pages/typst/TypstEditor.vue | 40 +++++-- src/pages/typst/interface.ts | 3 + src/shared/move-hook.ts | 15 +++ src/shared/window-max.ts | 37 ------ src/style/base.css | 5 + src/style/styles.css | 2 + 15 files changed, 191 insertions(+), 186 deletions(-) create mode 100644 src-tauri/tauri.macos.conf.json create mode 100644 src/pages/typst/DiagnosticsTip.vue delete mode 100644 src/shared/window-max.ts diff --git a/src-tauri/src/ipc/commands/typst.rs b/src-tauri/src/ipc/commands/typst.rs index 1c1f173..ae51f56 100644 --- a/src-tauri/src/ipc/commands/typst.rs +++ b/src-tauri/src/ipc/commands/typst.rs @@ -2,7 +2,7 @@ use super::{Error, Result}; use crate::ipc::commands::project; use crate::ipc::model::TypstRenderResponse; use crate::ipc::{ - TypstCompileEvent, TypstDiagnosticSeverity, TypstDocument, TypstPage, TypstSourceDiagnostic + TypstCompileEvent, TypstDiagnosticSeverity, TypstDocument, TypstPage, TypstSourceDiagnostic, }; use crate::project::ProjectManager; use base64::Engine; @@ -11,6 +11,7 @@ use serde::Serialize; use serde_repr::Serialize_repr; use siphasher::sip128::{Hasher128, SipHasher}; use std::hash::Hash; +use std::ops::Range; use std::path::PathBuf; use std::sync::Arc; use std::time::Instant; @@ -72,7 +73,7 @@ pub async fn typst_slot_update( content: String, ) -> Result<()> { let project = project(&window, &project_manager)?; - + let mut world = project.world.lock().unwrap(); let _ = world .slot_update(&path, Some(content)) @@ -86,7 +87,7 @@ pub async fn typst_compile_doc( project_manager: tauri::State<'_, Arc>>, path: PathBuf, content: String, -) -> Result> { +) -> Result<(Vec, Vec)> { let project = project(&window, &project_manager)?; let mut world = project.world.lock().unwrap(); @@ -105,7 +106,8 @@ pub async fn typst_compile_doc( debug!("compiling {:?}: {:?}", path, project); let now = Instant::now(); let mut tracer = Tracer::new(); - let mut res: Vec = Vec::new(); + let mut pages: Vec = Vec::new(); + let mut diags: Vec = Vec::new(); match typst::compile(&*world, &mut tracer) { Ok(doc) => { let elapsed = now.elapsed(); @@ -114,31 +116,27 @@ pub async fn typst_compile_doc( project, elapsed.as_millis() ); - let mut idx:u32 = 0; + let mut idx: u32 = 0; for page in &doc.pages { let mut hasher = SipHasher::new(); page.frame.hash(&mut hasher); let hash = hex::encode(hasher.finish128().as_bytes()); - let width = page.frame.width().to_pt(); + let width = page.frame.width().to_pt(); let height = page.frame.height().to_pt(); - idx +=1; - let pag = TypstPage { + idx += 1; + let pag = TypstPage { num: idx, width, height, - hash: hash.clone() + hash: hash.clone(), }; - res.push(pag); + pages.push(pag); } project.cache.write().unwrap().document = Some(doc); - } Err(diagnostics) => { - debug!( - "compilation failed with {:?} diagnostics", - &diagnostics - ); + debug!("compilation failed with {:?} diagnostics", &diagnostics); let source = world.source(source_id); let diagnostics: Vec = match source { @@ -148,13 +146,11 @@ pub async fn typst_compile_doc( .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 = d.message.to_string(); - info!("############## {}", &message); Some(TypstSourceDiagnostic { - range: start..start + size, + pos: get_range_position(&content, range.clone()), + range, severity: match d.severity { Severity::Error => TypstDiagnosticSeverity::Error, Severity::Warning => TypstDiagnosticSeverity::Warning, @@ -167,123 +163,32 @@ pub async fn typst_compile_doc( Err(_) => vec![], }; - info!("############## {:?}", &diagnostics); - + diags = diagnostics.clone(); - - + info!("############## {:?}", &diags); } } - Ok(res) + Ok((pages, diags)) } -#[tauri::command] -pub async fn typst_compile( - window: tauri::Window, - project_manager: tauri::State<'_, Arc>>, - path: PathBuf, - content: String, -) -> Result<()> { - let project = project(&window, &project_manager)?; - - let mut world = project.world.lock().unwrap(); - let source_id = world - .slot_update(&path, Some(content.clone())) - .map_err(Into::::into)?; - - if !world.is_main_set() { - let config = project.config.read().unwrap(); - if config.apply_main(&project, &mut world).is_err() { - debug!("skipped compilation for {:?} (main not set)", project); - return Ok(()); - } - } - - debug!("compiling {:?}: {:?}", path, project); - let now = Instant::now(); - let mut tracer = Tracer::new(); - match typst::compile(&*world, &mut tracer) { - Ok(doc) => { - let elapsed = now.elapsed(); - debug!( - "compilation succeeded for {:?} in {:?} ms", - project, - elapsed.as_millis() - ); - - let pages = doc.pages.len(); - - let mut hasher = SipHasher::new(); - for page in &doc.pages { - page.frame.hash(&mut hasher); - } - let hash = hex::encode(hasher.finish128().as_bytes()); - - // Assume all pages have the same size - // TODO: Improve this? - let first_page = &doc.pages[0]; - let width = first_page.frame.width(); - let height = first_page.frame.height(); - - project.cache.write().unwrap().document = Some(doc); - - let _ = window.emit( - "typst_compile", - TypstCompileEvent { - document: Some(TypstDocument { - pages, - hash, - width: width.to_pt(), - height: height.to_pt(), - }), - diagnostics: None, - }, - ); +pub fn get_range_position(text: &str, rang: Range) -> (usize, usize) { + let mut ln = 0; + let mut cn = 0; + let mut total: usize = 0; + for line in text.lines() { + + ln += 1; + let row = line.chars().count() + 1; + + if total <= rang.start && rang.start <= total + row { + cn = rang.start - total; + break; } - Err(diagnostics) => { - debug!( - "compilation failed with {:?} diagnostics", - diagnostics.len() - ); - - let source = world.source(source_id); - let diagnostics: Vec = match source { - Ok(source) => diagnostics - .iter() - .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 = 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(), - Err(_) => vec![], - }; - let _ = window.emit( - "typst_compile", - TypstCompileEvent { - document: None, - diagnostics: Some(diagnostics), - }, - ); - } + total += row; } - - Ok(()) + return (ln, cn); } #[tauri::command] @@ -294,17 +199,23 @@ pub async fn typst_render( scale: f32, nonce: u32, ) -> Result { - - info!("typst_render page:{} scale: {} nonce: {}", page, scale, nonce); + info!( + "typst_render page:{} scale: {} nonce: {}", + page, scale, nonce + ); let project = project_manager .get_project(&window) .ok_or(Error::UnknownProject)?; - + let cache = project.cache.read().unwrap(); - - if let Some(p) = cache.document.as_ref().and_then(|doc| doc.pages.get(page-1)) { + + if let Some(p) = cache + .document + .as_ref() + .and_then(|doc| doc.pages.get(page - 1)) + { let now = Instant::now(); - + let bmp = typst_render::render(&p.frame, scale, Color::WHITE); if let Ok(image) = bmp.encode_png() { let elapsed = now.elapsed(); diff --git a/src-tauri/src/ipc/model.rs b/src-tauri/src/ipc/model.rs index 4f78b77..8788fb4 100644 --- a/src-tauri/src/ipc/model.rs +++ b/src-tauri/src/ipc/model.rs @@ -39,6 +39,7 @@ pub struct TypstSourceDiagnostic { pub severity: TypstDiagnosticSeverity, pub message: String, pub hints: Vec, + pub pos:(usize, usize) } #[derive(Serialize, Clone, Debug)] diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index bb899d8..1806f01 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -42,7 +42,6 @@ async fn main() { ipc::commands::fs_write_file_binary, ipc::commands::fs_write_file_text, ipc::commands::load_project_from_path, - ipc::commands::typst_compile, ipc::commands::typst_compile_doc, ipc::commands::typst_render, ipc::commands::typst_autocomplete, diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 9b5db94..5a0b7f5 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -86,11 +86,14 @@ "fullscreen": false, "resizable": true, "title": "typster", + "center": true, "hiddenTitle": true, - "titleBarStyle": "Overlay", + "titleBarStyle": "Transparent", "theme": "Light", "width": 1200, - "height": 900 + "height": 900, + "minWidth": 800, + "minHeight": 400 } ] } diff --git a/src-tauri/tauri.macos.conf.json b/src-tauri/tauri.macos.conf.json new file mode 100644 index 0000000..a285a0d --- /dev/null +++ b/src-tauri/tauri.macos.conf.json @@ -0,0 +1,19 @@ +{ + "tauri": { + "windows": [ + { + "fullscreen": false, + "resizable": true, + "title": "", + "center": true, + "hiddenTitle": false, + "titleBarStyle": "Overlay", + "theme": "Light", + "width": 1200, + "height": 900, + "minWidth": 800, + "minHeight": 400 + } + ] + } +} \ No newline at end of file diff --git a/src/components/MonacoEditor.vue b/src/components/MonacoEditor.vue index 101220e..7eb29fb 100644 --- a/src/components/MonacoEditor.vue +++ b/src/components/MonacoEditor.vue @@ -5,7 +5,7 @@ + + \ No newline at end of file diff --git a/src/pages/typst/PreviewPage.vue b/src/pages/typst/PreviewPage.vue index 6c2c72b..615e068 100644 --- a/src/pages/typst/PreviewPage.vue +++ b/src/pages/typst/PreviewPage.vue @@ -9,7 +9,7 @@ import { computed, onMounted, reactive, ref, watch } from 'vue'; import { invoke } from '@tauri-apps/api'; import defaultUrl from './../../assets/rendering.svg' -import type { TypstCompileEvent, TypstRenderResponse } from './interface'; +import type { TypstRenderResponse } from './interface'; const props = defineProps({ num: Number, diff --git a/src/pages/typst/TypstEditor.vue b/src/pages/typst/TypstEditor.vue index c62316f..70c5ef9 100644 --- a/src/pages/typst/TypstEditor.vue +++ b/src/pages/typst/TypstEditor.vue @@ -59,6 +59,7 @@
+
@@ -71,29 +72,44 @@ import { onMounted, ref, computed } from 'vue'; import { readTextFile } from '@tauri-apps/api/fs'; import { invoke } from "@tauri-apps/api"; import { EditOutlined, ReadOutlined, OneToOneOutlined, ExportOutlined } from '@ant-design/icons-vue' -import type { IAdjust, IMode, TypstPage } from './interface'; +import type { IAdjust, IMode, TypstCompileResult, TypstPage, TypstSourceDiagnostic } from './interface'; import { useSystemStoreHook } from '../../store/store'; import SidebarToggle from '../home/SidebarToggle.vue'; import MonacoEditor from './../../components/MonacoEditor.vue' import PreviewPage from "./PreviewPage.vue" +import DiagnosticsTip from './DiagnosticsTip.vue' + import { save } from '@tauri-apps/api/dialog'; import ViewScale from './ViewScale.vue' import { useWinMove } from "./../../shared/move-hook" -import { useToggleWindowMax } from './../../shared/window-max' + +import { appWindow } from '@tauri-apps/api/window'; const { mousedownHandler, mouseupHandler, mousemoveHandler, mouseleaveHandler } = useWinMove() -const { toggleWindowMax } = useToggleWindowMax() +const toggleWindowMax = async (event: MouseEvent) => { + + if (event.target != event.currentTarget) { + return + } + await appWindow.toggleMaximize() +} const systemStore = useSystemStoreHook(); const mode = ref(systemStore.mode); const pages = ref([]) +const diags = ref([]) const scale = ref(1); +const diagnostic = computed(()=>{ + + return diags.value.shift() ?? null; +}) + const layoutcls = computed(() => { if (mode.value == 'edit') { return 'single-left' @@ -121,20 +137,22 @@ const compile_main_file = async () => { const mainpath = systemStore.editingProject?.path + '/main.typ'; try { const content = await readTextFile(mainpath); - const res = await invoke("typst_compile_doc", { path: '/main.typ', content }); + const [res, diags] = await invoke("typst_compile_doc", { path: '/main.typ', content }); + console.warn(res,diags) if (Array.isArray(res)) { pages.value = res; } } catch (error) { + console.warn(error) pages.value = []; } } -const onCompile = (data: TypstPage[]) => { +const onCompile = (data: TypstCompileResult) => { console.log('onCompile: data', data) - if (Array.isArray(data)) { - pages.value = data; - } + const [rpages, rdiags] = data; + pages.value = rpages; + diags.value = rdiags } @@ -143,6 +161,7 @@ const onChange = (text: string) => { } const onWhell = (evt: WheelEvent) => { + if (evt.ctrlKey) { evt.stopPropagation(); evt.preventDefault(); @@ -212,6 +231,11 @@ onMounted(async () => { justify-content: center; overflow: auto; height: 100%; + position: relative; + } + + .result.error { + overflow: hidden; } } } diff --git a/src/pages/typst/interface.ts b/src/pages/typst/interface.ts index ce1b0ae..2336b66 100644 --- a/src/pages/typst/interface.ts +++ b/src/pages/typst/interface.ts @@ -25,6 +25,8 @@ export interface TypstPage { num: number; } +export type TypstCompileResult = [[TypstPage], [TypstSourceDiagnostic]] + export type TypstDiagnosticSeverity = "error" | "warning"; export interface TypstSourceDiagnostic { @@ -32,6 +34,7 @@ export interface TypstSourceDiagnostic { severity: TypstDiagnosticSeverity; message: string; hints: string[]; + pos: [number, number] } export enum TypstCompletionKind { diff --git a/src/shared/move-hook.ts b/src/shared/move-hook.ts index 26af7de..118726a 100644 --- a/src/shared/move-hook.ts +++ b/src/shared/move-hook.ts @@ -6,6 +6,9 @@ export function useWinMove() { const lastPos = ref([]); const move = async (evt: MouseEvent) => { + if (evt.target != evt.currentTarget) { + return + } const { screenX, screenY, pageX, pageY } = evt; const [sX, sY] = lastPos.value; const ph = new LogicalPosition( @@ -17,6 +20,9 @@ export function useWinMove() { }; const mousedownHandler = async (evt: MouseEvent) => { + if (evt.target != evt.currentTarget) { + return + } const { screenX, screenY } = evt; moving.value = true; lastPos.value = [screenX, screenY]; @@ -24,12 +30,18 @@ export function useWinMove() { }; const mouseupHandler = async (evt: MouseEvent) => { + if (evt.target != evt.currentTarget) { + return + } console.log(" mouseup"); moving.value = false; await move(evt); }; const mousemoveHandler = async (evt: MouseEvent) => { + if (evt.target != evt.currentTarget) { + return + } if (moving.value) { console.log("mousemove"); await move(evt); @@ -37,6 +49,9 @@ export function useWinMove() { }; const mouseleaveHandler = async (evt: MouseEvent) => { + if (evt.target != evt.currentTarget) { + return + } if (moving.value) { moving.value = false; await move(evt); diff --git a/src/shared/window-max.ts b/src/shared/window-max.ts deleted file mode 100644 index 60b18cd..0000000 --- a/src/shared/window-max.ts +++ /dev/null @@ -1,37 +0,0 @@ - -import { ref } from "vue"; -import { - PhysicalPosition, - PhysicalSize, - appWindow, - currentMonitor -} from "@tauri-apps/api/window"; - -export function useToggleWindowMax() { - let curSize: PhysicalSize - let curPosition: PhysicalPosition - let isMax = ref(false); - const toggleWindowMax = async () => { - if (isMax.value) { - if (curSize && curPosition) { - await appWindow.setSize(curSize) - await appWindow.setPosition(curPosition) - isMax.value = false - } - } else { - const monitor = await currentMonitor() - curSize = await appWindow.outerSize() - curPosition = await appWindow.outerPosition() - if (monitor) { - await appWindow.setSize(monitor.size) - await appWindow.setPosition(monitor.position) - isMax.value = true - } - } - } - - return { - isMax, - toggleWindowMax - }; -} diff --git a/src/style/base.css b/src/style/base.css index 16094c7..e792a09 100644 --- a/src/style/base.css +++ b/src/style/base.css @@ -1,4 +1,9 @@ /* css reset */ +:root { + --text-color: #666; +} + + ul { list-style: none; padding-left:0; diff --git a/src/style/styles.css b/src/style/styles.css index 00d4712..0ca104e 100644 --- a/src/style/styles.css +++ b/src/style/styles.css @@ -5,6 +5,8 @@ body,html { margin: 0; padding: 0; + font-family: system-ui, -apple-system, Segoe UI, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", Roboto, Helvetica, Arial, sans-serif ; + color: var(--text-color) } #app {