fix: open exported files using rust's open crate (#838)

* fix: open exported files using rust's `open` crate

* feat: explorer as file opener on windows

* dev: link related issue
This commit is contained in:
Myriad-Dreamin 2024-11-17 22:08:13 +08:00 committed by GitHub
parent 263458a80b
commit 78a4117ec6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 110 additions and 37 deletions

View file

@ -208,8 +208,12 @@ mod polymorphic {
#[derive(Debug, Clone)]
pub struct OnExportRequest {
/// The path of the document to export.
pub path: PathBuf,
/// The kind of the export.
pub kind: ExportKind,
/// Whether to open the exported file(s) after the export is done.
pub open: bool,
}
#[derive(Debug, Clone)]

View file

@ -22,7 +22,7 @@
//!
//! The [`CompileHandler`] will push information to other actors.
use std::{collections::HashMap, ops::Deref, path::PathBuf, sync::Arc};
use std::{collections::HashMap, ops::Deref, sync::Arc};
use anyhow::bail;
use log::{error, info, trace};
@ -33,7 +33,7 @@ use reflexo_typst::{
use sync_lsp::{just_future, QueryFuture};
use tinymist_query::{
analysis::{Analysis, AnalysisRevLock, LocalContextGuard},
CompilerQueryRequest, CompilerQueryResponse, DiagnosticsMap, ExportKind, SemanticRequest,
CompilerQueryRequest, CompilerQueryResponse, DiagnosticsMap, OnExportRequest, SemanticRequest,
ServerInfoResponse, StatefulRequest, VersionedDocument,
};
use tokio::sync::{mpsc, oneshot};
@ -317,7 +317,8 @@ impl CompileClientActor {
self.handle.export.change_config(config);
}
pub fn on_export(&self, kind: ExportKind, path: PathBuf) -> QueryFuture {
pub fn on_export(&self, req: OnExportRequest) -> QueryFuture {
let OnExportRequest { path, kind, open } = req;
let snap = self.snapshot()?;
let entry = self.config.determine_entry(Some(path.as_path().into()));
@ -325,6 +326,22 @@ impl CompileClientActor {
just_future(async move {
let res = export.await?;
// See https://github.com/Myriad-Dreamin/tinymist/issues/837
// Also see https://github.com/Byron/open-rs/issues/105
#[cfg(not(target_os = "windows"))]
let do_open = ::open::that_detached;
#[cfg(target_os = "windows")]
fn do_open(path: impl AsRef<std::ffi::OsStr>) -> std::io::Result<()> {
::open::with_detached(path, "explorer")
}
if let Some(Some(path)) = open.then_some(res.as_ref()) {
log::info!("open with system default apps: {path:?}");
if let Err(e) = do_open(path) {
log::error!("failed to open with system default apps: {e}");
};
}
log::info!("CompileActor: on export end: {path:?} as {res:?}");
Ok(tinymist_query::CompilerQueryResponse::OnExport(res))
})

View file

@ -20,14 +20,19 @@ use super::server::*;
use super::*;
use crate::tool::package::InitTask;
/// See [`ExportKind`].
#[derive(Debug, Clone, Default, Deserialize)]
struct ExportOpts {
creation_timestamp: Option<String>,
fill: Option<String>,
ppi: Option<f64>,
#[serde(default)]
page: PageSelection,
/// Whether to open the exported file(s) after the export is done.
open: Option<bool>,
}
/// See [`ExportKind`].
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
struct QueryOpts {
@ -38,6 +43,8 @@ struct QueryOpts {
selector: String,
field: Option<String>,
one: Option<bool>,
/// Whether to open the exported file(s) after the export is done.
open: Option<bool>,
}
#[derive(Debug, Clone, Default, Deserialize)]
@ -61,22 +68,49 @@ impl LanguageState {
self.config.compile.determine_creation_timestamp()
};
self.export(req_id, ExportKind::Pdf { creation_timestamp }, args)
self.export(
req_id,
ExportKind::Pdf { creation_timestamp },
opts.open.unwrap_or_default(),
args,
)
}
/// Export the current document as HTML file(s).
pub fn export_html(&mut self, req_id: RequestId, args: Vec<JsonValue>) -> ScheduledResult {
self.export(req_id, ExportKind::Html {}, args)
pub fn export_html(&mut self, req_id: RequestId, mut args: Vec<JsonValue>) -> ScheduledResult {
let opts = get_arg_or_default!(args[1] as ExportOpts);
self.export(
req_id,
ExportKind::Html {},
opts.open.unwrap_or_default(),
args,
)
}
/// Export the current document as Markdown file(s).
pub fn export_markdown(&mut self, req_id: RequestId, args: Vec<JsonValue>) -> ScheduledResult {
self.export(req_id, ExportKind::Markdown {}, args)
pub fn export_markdown(
&mut self,
req_id: RequestId,
mut args: Vec<JsonValue>,
) -> ScheduledResult {
let opts = get_arg_or_default!(args[1] as ExportOpts);
self.export(
req_id,
ExportKind::Markdown {},
opts.open.unwrap_or_default(),
args,
)
}
/// Export the current document as Text file(s).
pub fn export_text(&mut self, req_id: RequestId, args: Vec<JsonValue>) -> ScheduledResult {
self.export(req_id, ExportKind::Text {}, args)
pub fn export_text(&mut self, req_id: RequestId, mut args: Vec<JsonValue>) -> ScheduledResult {
let opts = get_arg_or_default!(args[1] as ExportOpts);
self.export(
req_id,
ExportKind::Text {},
opts.open.unwrap_or_default(),
args,
)
}
/// Query the current document and export the result as JSON file(s).
@ -93,6 +127,7 @@ impl LanguageState {
pretty: opts.pretty.unwrap_or(true),
one: opts.one.unwrap_or(false),
},
opts.open.unwrap_or_default(),
args,
)
}
@ -100,7 +135,12 @@ impl LanguageState {
/// Export the current document as Svg file(s).
pub fn export_svg(&mut self, req_id: RequestId, mut args: Vec<JsonValue>) -> ScheduledResult {
let opts = get_arg_or_default!(args[1] as ExportOpts);
self.export(req_id, ExportKind::Svg { page: opts.page }, args)
self.export(
req_id,
ExportKind::Svg { page: opts.page },
opts.open.unwrap_or_default(),
args,
)
}
/// Export the current document as Png file(s).
@ -113,6 +153,7 @@ impl LanguageState {
ppi: opts.ppi,
page: opts.page,
},
opts.open.unwrap_or_default(),
args,
)
}
@ -123,11 +164,12 @@ impl LanguageState {
&mut self,
req_id: RequestId,
kind: ExportKind,
open: bool,
mut args: Vec<JsonValue>,
) -> ScheduledResult {
let path = get_arg!(args[0] as PathBuf);
run_query!(req_id, self.OnExport(path, kind))
run_query!(req_id, self.OnExport(path, open, kind))
}
/// Export a range of the current document as Ansi highlighted text.

View file

@ -24,7 +24,7 @@ use sync_lsp::*;
use task::{CacheTask, ExportUserConfig, FormatTask, FormatUserConfig, UserActionTask};
use tinymist_query::PageSelection;
use tinymist_query::{
lsp_to_typst, CompilerQueryRequest, CompilerQueryResponse, FoldRequestFeature, OnExportRequest,
lsp_to_typst, CompilerQueryRequest, CompilerQueryResponse, FoldRequestFeature,
PositionEncoding, SyntaxRequest,
};
use tokio::sync::mpsc;
@ -1040,7 +1040,7 @@ impl LanguageState {
DocumentSymbol(req) => query_source!(self, DocumentSymbol, req)?,
OnEnter(req) => query_source!(self, OnEnter, req)?,
ColorPresentation(req) => CompilerQueryResponse::ColorPresentation(req.request()),
OnExport(OnExportRequest { kind, path }) => return primary().on_export(kind, path),
OnExport(req) => return primary().on_export(req),
ServerInfo(_) => return primary().collect_server_info(),
_ => return Self::query_on(primary(), is_pinning, query),
})

View file

@ -500,6 +500,23 @@ async function commandShow(kind: "Pdf" | "Svg" | "Png", extraOpts?: any): Promis
return;
}
const conf = vscode.workspace.getConfiguration("tinymist");
const openIn: string = conf.get("showExportFileIn", "editorTab");
// Telling the language server to open the file instead of using
// ```
// vscode.env.openExternal(exportUri);
// ```
// , which is buggy.
//
// See https://github.com/Myriad-Dreamin/tinymist/issues/837
// Also see https://github.com/microsoft/vscode/issues/85930
const openBySystemDefault = openIn === "systemDefault";
if (openBySystemDefault) {
extraOpts = extraOpts || {};
extraOpts.open = true;
}
// only create pdf if it does not exist yet
const exportPath = await commandExport(kind, extraOpts);
@ -509,37 +526,30 @@ async function commandShow(kind: "Pdf" | "Svg" | "Png", extraOpts?: any): Promis
return;
}
const exportUri = Uri.file(exportPath);
// find and replace exportUri
// todo: we may find them in tabs
vscode.window.tabGroups;
let uriToFind = exportUri.toString();
findTab: for (const editor of vscode.window.tabGroups.all) {
for (const tab of editor.tabs) {
if ((tab.input as any)?.uri?.toString() === uriToFind) {
await vscode.window.tabGroups.close(tab, true);
break findTab;
}
}
}
const conf = vscode.workspace.getConfiguration("tinymist");
const openIn: string = conf.get("showExportFileIn", "editorTab");
switch (openIn) {
default:
case "editorTab":
case "systemDefault":
break;
case "editorTab": {
// find and replace exportUri
const exportUri = Uri.file(exportPath);
let uriToFind = exportUri.toString();
findTab: for (const editor of vscode.window.tabGroups.all) {
for (const tab of editor.tabs) {
if ((tab.input as any)?.uri?.toString() === uriToFind) {
await vscode.window.tabGroups.close(tab, true);
break findTab;
}
}
}
// here we can be sure that the pdf exists
await commands.executeCommand("vscode.open", exportUri, {
viewColumn: ViewColumn.Beside,
preserveFocus: true,
} as vscode.TextDocumentShowOptions);
break;
case "systemDefault":
await vscode.env.openExternal(exportUri);
break;
}
}
}