diff --git a/crates/tinymist/src/actor/preview.rs b/crates/tinymist/src/actor/preview.rs index f1e475a78..31ccc3011 100644 --- a/crates/tinymist/src/actor/preview.rs +++ b/crates/tinymist/src/actor/preview.rs @@ -62,13 +62,11 @@ impl PreviewActor { log::warn!("PreviewTask({task_id}): failed to unregister preview"); } - if !tab.is_primary { - let h = tab.compile_handler.clone(); - let task_id = tab.task_id.clone(); - self.client.handle.spawn(async move { - h.settle().await.log_error_with(|| { - format!("PreviewTask({task_id}): failed to settle") - }); + if tab.is_primary { + tab.compile_handler.unpin_primary(); + } else { + tab.compile_handler.settle().log_error_with(|| { + format!("PreviewTask({}): failed to settle", tab.task_id) }); } diff --git a/crates/tinymist/src/cmd.rs b/crates/tinymist/src/cmd.rs index 0bb399d23..0d50dd776 100644 --- a/crates/tinymist/src/cmd.rs +++ b/crates/tinymist/src/cmd.rs @@ -282,8 +282,27 @@ impl ServerState { /// Start a preview instance. #[cfg(feature = "preview")] pub fn start_preview( + &mut self, + args: Vec, + ) -> SchedulableResponse { + self.start_preview_inner(args, false) + } + + /// Start a preview instance for browsing. + #[cfg(feature = "preview")] + pub fn browse_preview( + &mut self, + args: Vec, + ) -> SchedulableResponse { + self.start_preview_inner(args, true) + } + + /// Start a preview instance. + #[cfg(feature = "preview")] + pub fn start_preview_inner( &mut self, mut args: Vec, + browsing_preview: bool, ) -> SchedulableResponse { use std::path::Path; @@ -299,18 +318,18 @@ impl ServerState { PreviewCliArgs::try_parse_from(cli_args).map_err(|e| invalid_params(e.to_string()))?; // todo: preview specific arguments are not used - let input = cli_args - .compile - .input - .clone() - .ok_or_else(|| internal_error("entry file must be provided"))?; - let input = Path::new(&input); - let entry = if input.is_absolute() { - input.into() - } else { - // std::env::current_dir().unwrap().join(input) - return Err(invalid_params("entry file must be absolute path")); - }; + let entry = cli_args.compile.input.as_ref(); + let entry = entry + .map(|input| { + let input = Path::new(&input); + if !input.is_absolute() { + // std::env::current_dir().unwrap().join(input) + return Err(invalid_params("entry file must be absolute path")); + }; + + Ok(input.into()) + }) + .transpose()?; let task_id = cli_args.preview.task_id.clone(); if task_id == "primary" { @@ -321,14 +340,20 @@ impl ServerState { let watcher = previewer.compile_watcher(); let primary = &mut self.project.compiler.primary; - if !cli_args.not_as_primary && self.preview.watchers.register(&primary.id, watcher) { + // todo: recover pin status reliably + if !cli_args.not_as_primary + && (browsing_preview || entry.is_some()) + && self.preview.watchers.register(&primary.id, watcher) + { let id = primary.id.clone(); - // todo: recover pin status reliably - self.pin_main_file(Some(entry)) - .map_err(|e| internal_error(format!("could not pin file: {e}")))?; + + if let Some(entry) = entry { + self.change_main_file(Some(entry)).map_err(internal_error)?; + } + self.set_pin_by_preview(true); self.preview.start(cli_args, previewer, id, true) - } else { + } else if let Some(entry) = entry { let id = self .restart_dedicate(&task_id, Some(entry)) .map_err(internal_error)?; @@ -348,6 +373,8 @@ impl ServerState { } self.preview.start(cli_args, previewer, id, false) + } else { + return Err(internal_error("entry file must be provided")); } } diff --git a/crates/tinymist/src/input.rs b/crates/tinymist/src/input.rs index 61d62aa59..cae1736e1 100644 --- a/crates/tinymist/src/input.rs +++ b/crates/tinymist/src/input.rs @@ -103,6 +103,11 @@ impl ServerState { /// Main file mutations on the primary project (which is used for the language /// queries.) impl ServerState { + /// Updates the `pinning_by_preview` status. + pub fn set_pin_by_preview(&mut self, pin: bool) { + self.pinning_by_preview = pin; + } + /// Changes main file to the given path. pub fn change_main_file(&mut self, path: Option) -> Result { if path @@ -124,7 +129,7 @@ impl ServerState { /// Pins the main file to the given path pub fn pin_main_file(&mut self, new_entry: Option) -> Result<()> { - self.pinning = new_entry.is_some(); + self.pinning_by_user = new_entry.is_some(); let entry = new_entry .or_else(|| self.entry_resolver().resolve_default()) .or_else(|| self.focusing.clone()); @@ -134,7 +139,7 @@ impl ServerState { /// Focuses main file to the given path. pub fn focus_main_file(&mut self, new_entry: Option) -> Result { - if self.pinning || self.config.compile.has_default_entry_path { + if self.pinning_by_user || self.config.compile.has_default_entry_path { self.focusing = new_entry; return Ok(false); } diff --git a/crates/tinymist/src/lsp_query.rs b/crates/tinymist/src/lsp_query.rs index 171c564b6..8221f3beb 100644 --- a/crates/tinymist/src/lsp_query.rs +++ b/crates/tinymist/src/lsp_query.rs @@ -317,7 +317,6 @@ impl ServerState { pub fn query(&mut self, query: CompilerQueryRequest) -> QueryFuture { use CompilerQueryRequest::*; - let is_pinning = self.pinning; just_ok(match query { FoldingRange(req) => query_source!(self, FoldingRange, req)?, SelectionRange(req) => query_source!(self, SelectionRange, req)?, @@ -327,34 +326,35 @@ impl ServerState { OnExport(req) => return self.on_export(req), ServerInfo(_) => return self.collect_server_info(), // todo: query on dedicate projects - _ => return self.query_on(is_pinning, query), + _ => return self.query_on(query), }) } - fn query_on(&mut self, is_pinning: bool, query: CompilerQueryRequest) -> QueryFuture { + fn query_on(&mut self, query: CompilerQueryRequest) -> QueryFuture { use CompilerQueryRequest::*; type R = CompilerQueryResponse; assert!(query.fold_feature() != FoldRequestFeature::ContextFreeUnique); let (mut snap, stat) = self.query_snapshot_with_stat(&query)?; - let input = query - .associated_path() - .map(|path| self.resolve_task(path.into())) - .or_else(|| { - let root = self.entry_resolver().root(None)?; - Some(TaskInputs { - entry: Some(EntryState::new_rooted_by_id(root, *DETACHED_ENTRY)), - ..Default::default() - }) - }); + // todo: whether it is safe to inherit success_doc with changed entry + if !self.is_pinning() { + let input = query + .associated_path() + .map(|path| self.resolve_task(path.into())) + .or_else(|| { + let root = self.entry_resolver().root(None)?; + Some(TaskInputs { + entry: Some(EntryState::new_rooted_by_id(root, *DETACHED_ENTRY)), + ..Default::default() + }) + }); + + if let Some(input) = input { + snap = snap.task(input); + } + } just_future(async move { - // todo: whether it is safe to inherit success_doc with changed entry - if !is_pinning { - if let Some(input) = input { - snap = snap.task(input); - } - } stat.snap(); if matches!(query, Completion(..)) { diff --git a/crates/tinymist/src/project.rs b/crates/tinymist/src/project.rs index 4dbe52bfa..4f899c845 100644 --- a/crates/tinymist/src/project.rs +++ b/crates/tinymist/src/project.rs @@ -38,8 +38,11 @@ use tokio::sync::mpsc; use typst::{diag::FileResult, foundations::Bytes, layout::Position as TypstPosition}; use super::ServerState; -use crate::actor::editor::{CompileStatus, CompileStatusEnum, EditorRequest, ProjVersion}; use crate::stats::{CompilerQueryStats, QueryStatGuard}; +use crate::{ + actor::editor::{CompileStatus, CompileStatusEnum, EditorRequest, ProjVersion}, + ServerEvent, +}; use crate::{task::ExportUserConfig, Config}; type EditorSender = mpsc::UnboundedSender; @@ -347,19 +350,28 @@ pub struct CompileHandlerImpl { } pub trait ProjectClient: Send + Sync + 'static { - fn send_event(&self, event: LspInterrupt); + fn interrupt(&self, event: LspInterrupt); + fn server_event(&self, event: ServerEvent); } impl ProjectClient for LspClient { - fn send_event(&self, event: LspInterrupt) { + fn interrupt(&self, event: LspInterrupt) { + self.send_event(event); + } + + fn server_event(&self, event: ServerEvent) { self.send_event(event); } } impl ProjectClient for mpsc::UnboundedSender { - fn send_event(&self, event: LspInterrupt) { + fn interrupt(&self, event: LspInterrupt) { self.send(event).log_error("failed to send interrupt"); } + + fn server_event(&self, _event: ServerEvent) { + log::warn!("ProjectClient: server_event is not implemented for mpsc::UnboundedSender"); + } } impl CompileHandlerImpl { @@ -510,7 +522,7 @@ impl CompileHandler for CompileHandlerImpl self.notify_diagnostics(snap); - self.client.send_event(LspInterrupt::Compiled(snap.clone())); + self.client.interrupt(LspInterrupt::Compiled(snap.clone())); self.export.signal(snap); self.editor_tx diff --git a/crates/tinymist/src/server.rs b/crates/tinymist/src/server.rs index 98465f19a..12ccd6d94 100644 --- a/crates/tinymist/src/server.rs +++ b/crates/tinymist/src/server.rs @@ -60,7 +60,10 @@ pub struct ServerState { /// Whether the server has registered document formatter capabilities. pub formatter_registered: bool, /// Whether client is pinning a file. - pub pinning: bool, + pub pinning_by_user: bool, + /// Whether client is pinning caused by preview, which has lower priority + /// than pinning. + pub pinning_by_preview: bool, /// The client focusing file. pub focusing: Option, /// The client ever focused implicitly by activities. @@ -111,7 +114,8 @@ impl ServerState { formatter_registered: false, config, - pinning: false, + pinning_by_user: false, + pinning_by_preview: false, focusing: None, formatter, user_action: Default::default(), @@ -133,6 +137,15 @@ impl ServerState { &self.compile_config().entry_resolver } + /// Whether the main file is pinning. + pub fn is_pinning(&self) -> bool { + self.pinning_by_user + || (self.pinning_by_preview && { + let primary_verse = &self.project.compiler.primary.verse; + !primary_verse.entry_state().is_inactive() + }) + } + /// The entry point for the language server. pub fn main(client: TypedLspClient, config: Config, start: bool) -> Self { log::info!("LanguageState: initialized with config {config:?}"); @@ -171,6 +184,7 @@ impl ServerState { #[cfg(feature = "preview")] let provider = provider .with_command("tinymist.doStartPreview", State::start_preview) + .with_command("tinymist.doStartBrowsingPreview", State::browse_preview) .with_command("tinymist.doKillPreview", State::kill_preview) .with_command("tinymist.scrollPreview", State::scroll_preview); @@ -182,6 +196,10 @@ impl ServerState { &LspInterrupt::Compile(ProjectInsId::default()), State::compile_interrupt::, ) + .with_event( + &ServerEvent::UnpinPrimaryByPreview, + State::server_event::, + ) // lantency sensitive .with_request_::(State::completion) .with_request_::(State::semantic_tokens_full) @@ -274,6 +292,33 @@ impl ServerState { // log::info!("interrupted in {:?}", _start.elapsed()); Ok(()) } + + /// Handles the server events. + fn server_event>( + mut state: ServiceState, + params: ServerEvent, + ) -> anyhow::Result<()> { + let _start = std::time::Instant::now(); + // log::info!("incoming interrupt: {params:?}"); + let Some(ready) = state.ready() else { + log::info!("server event sent to not ready server"); + return Ok(()); + }; + + match params { + ServerEvent::UnpinPrimaryByPreview => { + ready.set_pin_by_preview(false); + } + } + + Ok(()) + } +} + +/// An event sent to the language server. +pub enum ServerEvent { + /// Updates the `pinning_by_preview` status to false. + UnpinPrimaryByPreview, } impl ServerState { diff --git a/crates/tinymist/src/tool/preview.rs b/crates/tinymist/src/tool/preview.rs index 2c322c81e..4054a1330 100644 --- a/crates/tinymist/src/tool/preview.rs +++ b/crates/tinymist/src/tool/preview.rs @@ -252,14 +252,18 @@ impl PreviewProjectHandler { pub fn flush_compile(&self) { let _ = self.project_id; self.client - .send_event(LspInterrupt::Compile(self.project_id.clone())); + .interrupt(LspInterrupt::Compile(self.project_id.clone())); } - pub async fn settle(&self) -> Result<(), Error> { + pub fn settle(&self) -> Result<(), Error> { self.client - .send_event(LspInterrupt::Settle(self.project_id.clone())); + .interrupt(LspInterrupt::Settle(self.project_id.clone())); Ok(()) } + + pub fn unpin_primary(&self) { + self.client.server_event(ServerEvent::UnpinPrimaryByPreview); + } } impl EditorServer for PreviewProjectHandler { @@ -285,7 +289,7 @@ impl EditorServer for PreviewProjectHandler { } else { MemoryEvent::Update(files) }); - self.client.send_event(intr); + self.client.interrupt(intr); Ok(()) } @@ -294,7 +298,7 @@ impl EditorServer for PreviewProjectHandler { // todo: is it safe to believe that the path is normalized? let files = FileChangeSet::new_removes(files.files.into_iter().map(From::from).collect()); self.client - .send_event(LspInterrupt::Memory(MemoryEvent::Update(files))); + .interrupt(LspInterrupt::Memory(MemoryEvent::Update(files))); Ok(()) } @@ -630,7 +634,7 @@ pub async fn preview_main(args: PreviewCliArgs) -> Result<()> { let (dep_tx, dep_rx) = tokio::sync::mpsc::unbounded_channel(); let fs_intr_tx = intr_tx.clone(); tokio::spawn(watch_deps(dep_rx, move |event| { - fs_intr_tx.send_event(LspInterrupt::Fs(event)); + fs_intr_tx.interrupt(LspInterrupt::Fs(event)); })); // Consume editor_rx diff --git a/editors/vscode/package.json b/editors/vscode/package.json index ae5e848fd..701d90eec 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -1023,6 +1023,12 @@ "icon": "$(extensions-sync-enabled)", "category": "Typst" }, + { + "command": "tinymist.browsing-preview", + "title": "Tinymist: Browsing Preview", + "description": "Open a preview panel to browsing through documents. The preview panel will pick a main file when switching between files. By default, every single file is a document. This doesn't work well with multiple-files projects. To allow tinymist learn the structure of your projects, please configure `tinymist.projectResolution` as `lockDatabase`.", + "icon": "$(open-preview)" + }, { "command": "typst-preview.preview", "title": "Typst Preview: Preview Opened File", diff --git a/editors/vscode/src/extension.ts b/editors/vscode/src/extension.ts index 057ee7974..bad75ea6f 100644 --- a/editors/vscode/src/extension.ts +++ b/editors/vscode/src/extension.ts @@ -554,15 +554,20 @@ async function commandRunCodeLens(...args: string[]): Promise { } async function codeLensMore(): Promise { + const kBrowsing = "Browsing Preview Documents" as const; const kPreviewIn = "Preview in .." as const; const kExportAs = "Export as .." as const; - const moreCodeLens = [kPreviewIn, kExportAs] as const; + const moreCodeLens = [kBrowsing, kPreviewIn, kExportAs] as const; const moreAction = (await vscode.window.showQuickPick(moreCodeLens, { title: "More Actions", })) as (typeof moreCodeLens)[number] | undefined; switch (moreAction) { + case kBrowsing: { + void vscode.commands.executeCommand(`tinymist.browsing-preview`); + return; + } case kPreviewIn: { // prompt for enum (doc, slide) with default const mode = await vscode.window.showQuickPick(["doc", "slide"], { diff --git a/editors/vscode/src/features/preview-compat.ts b/editors/vscode/src/features/preview-compat.ts index 3f41b872d..65da6640a 100644 --- a/editors/vscode/src/features/preview-compat.ts +++ b/editors/vscode/src/features/preview-compat.ts @@ -260,6 +260,7 @@ interface LaunchTask { bindDocument: vscode.TextDocument; mode: "doc" | "slide"; webviewPanel?: vscode.WebviewPanel; + isBrowsing?: boolean; isDev?: boolean; isNotPrimary?: boolean; } diff --git a/editors/vscode/src/features/preview.ts b/editors/vscode/src/features/preview.ts index 6160d674e..50a0dba10 100644 --- a/editors/vscode/src/features/preview.ts +++ b/editors/vscode/src/features/preview.ts @@ -99,13 +99,16 @@ export function previewActivate(context: vscode.ExtensionContext, isCompat: bool }), ); + const launchBrowsingPreview = launch("webview", "doc", { isBrowsing: true }); + const launchDevPreview = launch("webview", "doc", { isDev: true }); // Registers preview commands, check `package.json` for descriptions. context.subscriptions.push( + vscode.commands.registerCommand("tinymist.browsing-preview", launchBrowsingPreview), vscode.commands.registerCommand("typst-preview.preview", launch("webview", "doc")), vscode.commands.registerCommand("typst-preview.browser", launch("browser", "doc")), vscode.commands.registerCommand("typst-preview.preview-slide", launch("webview", "slide")), vscode.commands.registerCommand("typst-preview.browser-slide", launch("browser", "slide")), - vscode.commands.registerCommand("tinymist.previewDev", launch("webview", "doc", true)), + vscode.commands.registerCommand("tinymist.previewDev", launchDevPreview), vscode.commands.registerCommand( "typst-preview.revealDocument", isCompat ? revealDocumentCompat : revealDocumentLsp, @@ -142,16 +145,28 @@ export function previewActivate(context: vscode.ExtensionContext, isCompat: bool launchImpl = isCompat ? launchPreviewCompat : launchPreviewLsp; + /** + * Options to launch the preview. + * + * @param isBrowsing Whether to launch the preview in browsing mode. It switches the previewing + * document on focus change. + * @param isDev Whether to launch the preview in development mode. It fixes some random arguments + * to help the `vite dev` server connect the language server via WebSocket. + */ + interface LaunchOpts { + isBrowsing?: boolean; + isDev?: boolean; + // isDev = false + } + /** * Gets current active editor and launches the preview. * * @param kind Which kind of preview to launch, either in external browser or in builtin vscode * webview. * @param mode The preview mode, either viewing as a document or as a slide. - * @param isDev Whether to launch the preview in development mode. It fixes some random arguments - * to help the `vite dev` server connect the language server via WebSocket. */ - function launch(kind: "browser" | "webview", mode: "doc" | "slide", isDev = false) { + function launch(kind: "browser" | "webview", mode: "doc" | "slide", opts?: LaunchOpts) { return async () => { activeEditor = activeEditor || vscode.window.activeTextEditor; if (!activeEditor) { @@ -165,7 +180,8 @@ export function previewActivate(context: vscode.ExtensionContext, isCompat: bool editor: activeEditor, bindDocument, mode, - isDev, + isBrowsing: opts?.isBrowsing || false, + isDev: opts?.isDev || false, }).catch((e) => { vscode.window.showErrorMessage(`failed to launch preview: ${e}`); }); @@ -259,9 +275,10 @@ export async function openPreviewInWebView({ // Determines arguments for the preview HTML. const previewMode = task.mode === "doc" ? "Doc" : "Slide"; - const previewState = { + const previewState: PersistPreviewState = { mode: task.mode, - asPrimary: task.isNotPrimary, + isNotPrimary: !!task.isNotPrimary, + isBrowsing: !!task.isBrowsing, uri: activeEditor.document.uri.toString(), }; const previewStateEncoded = Buffer.from(JSON.stringify(previewState), "utf-8").toString("base64"); @@ -305,7 +322,8 @@ interface TaskControlBlock { const activeTask = new Map(); async function launchPreviewLsp(task: LaunchInBrowserTask | LaunchInWebViewTask) { - const { kind, context, editor, bindDocument, webviewPanel, isDev, isNotPrimary } = task; + const { kind, context, editor, bindDocument, webviewPanel, isBrowsing, isDev, isNotPrimary } = + task; /** * Can only open one preview for one document. @@ -378,11 +396,6 @@ async function launchPreviewLsp(task: LaunchInBrowserTask | LaunchInWebViewTask) if (activeTask.get(bindDocument)?.taskId === taskId) { activeTask.delete(bindDocument); } - - // todo: better way to unpin main - if (isPrimary) { - vscode.commands.executeCommand("tinymist.unpinMain"); - } }); return { message: "ok", taskId }; @@ -395,7 +408,8 @@ async function launchPreviewLsp(task: LaunchInBrowserTask | LaunchInWebViewTask) const invertColorsArgs = ivArgs ? ["--invert-colors", JSON.stringify(ivArgs)] : []; const previewInSlideModeArgs = task.mode === "slide" ? ["--preview-mode=slide"] : []; const dataPlaneHostArgs = !isDev ? ["--data-plane-host", "127.0.0.1:0"] : []; - const { dataPlanePort, staticServerPort, isPrimary } = await tinymist.startPreview([ + + const previewArgs = [ "--task-id", taskId, "--refresh-style", @@ -406,9 +420,13 @@ async function launchPreviewLsp(task: LaunchInBrowserTask | LaunchInWebViewTask) ...previewInSlideModeArgs, ...(isNotPrimary ? ["--not-primary"] : []), filePath, - ]); + ]; + + const { dataPlanePort, staticServerPort, isPrimary } = await (isBrowsing + ? tinymist.startPreview(previewArgs) + : tinymist.startBrowsingPreview(previewArgs)); console.log( - `Launched preview, data plane port:${dataPlanePort}, static server port:${staticServerPort}`, + `Launched preview, browsing:${isBrowsing}, data plane port:${dataPlanePort}, static server port:${staticServerPort}`, ); if (enableCursor) { @@ -740,30 +758,38 @@ export class OutlineItem extends vscode.TreeItem { contextValue = "outline-item"; } -class TypstPreviewSerializer implements vscode.WebviewPanelSerializer { +interface PersistPreviewState { + mode: "doc" | "slide"; + isNotPrimary: boolean; + isBrowsing: boolean; + uri: string; +} + +class TypstPreviewSerializer implements vscode.WebviewPanelSerializer { context: vscode.ExtensionContext; constructor(context: vscode.ExtensionContext) { this.context = context; } - async deserializeWebviewPanel(webviewPanel: vscode.WebviewPanel, state: any) { + async deserializeWebviewPanel(webviewPanel: vscode.WebviewPanel, state: PersistPreviewState) { console.log("deserializeWebviewPanel", state); if (!state) { return; } - state.uri = vscode.Uri.parse(state.uri); + const uri = vscode.Uri.parse(state.uri); // open this file and show in editor const doc = - vscode.workspace.textDocuments.find((doc) => doc.uri === state.uri) || + vscode.workspace.textDocuments.find((doc) => doc.uri === uri) || (await vscode.workspace.openTextDocument(state.uri)); const editor = await vscode.window.showTextDocument(doc, getSensibleTextEditorColumn(), true); const bindDocument = editor.document; const mode = state.mode; const isNotPrimary = state.isNotPrimary; + const isBrowsing = state.isBrowsing; await launchImpl({ kind: "webview", @@ -772,6 +798,7 @@ class TypstPreviewSerializer implements vscode.WebviewPanelSerializer { bindDocument, mode, webviewPanel, + isBrowsing, isNotPrimary, }); } diff --git a/editors/vscode/src/lsp.ts b/editors/vscode/src/lsp.ts index 531bd2023..356668f6e 100644 --- a/editors/vscode/src/lsp.ts +++ b/editors/vscode/src/lsp.ts @@ -304,10 +304,10 @@ export class LanguageState { * The commands group for the *Document Preview* feature. This feature is used to preview multiple * documents at the same time. * - * A preview task is started by calling {@link startPreview} with the *CLI arguments* to pass to - * the preview task like you would do in the terminal. Although language server will stop a - * preview task when no connection is active for a while, it can be killed by calling - * {@link killPreview} with a task id of the preview task. + * A preview task is started by calling {@link startPreview} or {@link startBrowsingPreview} with + * the *CLI arguments* to pass to the preview task like you would do in the terminal. Although + * language server will stop a preview task when no connection is active for a while, it can be + * killed by calling {@link killPreview} with a task id of the preview task. * * The task id of a preview task is determined by the client. If no task id is provided, you * cannot force kill a preview task from client. You also cannot have multiple preview tasks at @@ -336,6 +336,22 @@ export class LanguageState { return res || {}; } + /** + * Starts a browsing preview task. See {@link _GroupDocumentPreviewFeatureCommands} for more information. + * The difference between this and {@link startPreview} is that the main file will change according to the requests + * sent to the language server. + * + * @param previewArgs - The *CLI arguments* to pass to the preview task. See help of the preview + * CLI command for more information. + * @returns The result of the preview task. + */ + async startBrowsingPreview(previewArgs: string[]): Promise { + const res = await tinymist.executeCommand(`tinymist.doStartBrowsingPreview`, [ + previewArgs, + ]); + return res || {}; + } + /** * Kills a preview task. See {@link _GroupDocumentPreviewFeatureCommands} for more information. * diff --git a/tests/e2e/main.rs b/tests/e2e/main.rs index 9e8ea49b3..4683ce81a 100644 --- a/tests/e2e/main.rs +++ b/tests/e2e/main.rs @@ -374,7 +374,7 @@ fn e2e() { }); let hash = replay_log(&tinymist_binary, &root.join("neovim")); - insta::assert_snapshot!(hash, @"siphash128_13:10cac959ce095e74fb67e37b7aa16b7"); + insta::assert_snapshot!(hash, @"siphash128_13:af8a934c44f864eea561172ae925c397"); } { @@ -385,7 +385,7 @@ fn e2e() { }); let hash = replay_log(&tinymist_binary, &root.join("vscode")); - insta::assert_snapshot!(hash, @"siphash128_13:2d215826585b3025e2f84937f4e92821"); + insta::assert_snapshot!(hash, @"siphash128_13:cdc40c3628176d16291ca5e3780f5403"); } }