mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-11-25 05:22:52 +00:00
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:
parent
263458a80b
commit
78a4117ec6
5 changed files with 110 additions and 37 deletions
|
|
@ -208,8 +208,12 @@ mod polymorphic {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct OnExportRequest {
|
pub struct OnExportRequest {
|
||||||
|
/// The path of the document to export.
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
/// The kind of the export.
|
||||||
pub kind: ExportKind,
|
pub kind: ExportKind,
|
||||||
|
/// Whether to open the exported file(s) after the export is done.
|
||||||
|
pub open: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
//!
|
//!
|
||||||
//! The [`CompileHandler`] will push information to other actors.
|
//! 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 anyhow::bail;
|
||||||
use log::{error, info, trace};
|
use log::{error, info, trace};
|
||||||
|
|
@ -33,7 +33,7 @@ use reflexo_typst::{
|
||||||
use sync_lsp::{just_future, QueryFuture};
|
use sync_lsp::{just_future, QueryFuture};
|
||||||
use tinymist_query::{
|
use tinymist_query::{
|
||||||
analysis::{Analysis, AnalysisRevLock, LocalContextGuard},
|
analysis::{Analysis, AnalysisRevLock, LocalContextGuard},
|
||||||
CompilerQueryRequest, CompilerQueryResponse, DiagnosticsMap, ExportKind, SemanticRequest,
|
CompilerQueryRequest, CompilerQueryResponse, DiagnosticsMap, OnExportRequest, SemanticRequest,
|
||||||
ServerInfoResponse, StatefulRequest, VersionedDocument,
|
ServerInfoResponse, StatefulRequest, VersionedDocument,
|
||||||
};
|
};
|
||||||
use tokio::sync::{mpsc, oneshot};
|
use tokio::sync::{mpsc, oneshot};
|
||||||
|
|
@ -317,7 +317,8 @@ impl CompileClientActor {
|
||||||
self.handle.export.change_config(config);
|
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 snap = self.snapshot()?;
|
||||||
|
|
||||||
let entry = self.config.determine_entry(Some(path.as_path().into()));
|
let entry = self.config.determine_entry(Some(path.as_path().into()));
|
||||||
|
|
@ -325,6 +326,22 @@ impl CompileClientActor {
|
||||||
just_future(async move {
|
just_future(async move {
|
||||||
let res = export.await?;
|
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:?}");
|
log::info!("CompileActor: on export end: {path:?} as {res:?}");
|
||||||
Ok(tinymist_query::CompilerQueryResponse::OnExport(res))
|
Ok(tinymist_query::CompilerQueryResponse::OnExport(res))
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,19 @@ use super::server::*;
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::tool::package::InitTask;
|
use crate::tool::package::InitTask;
|
||||||
|
|
||||||
|
/// See [`ExportKind`].
|
||||||
#[derive(Debug, Clone, Default, Deserialize)]
|
#[derive(Debug, Clone, Default, Deserialize)]
|
||||||
struct ExportOpts {
|
struct ExportOpts {
|
||||||
creation_timestamp: Option<String>,
|
creation_timestamp: Option<String>,
|
||||||
fill: Option<String>,
|
fill: Option<String>,
|
||||||
ppi: Option<f64>,
|
ppi: Option<f64>,
|
||||||
|
#[serde(default)]
|
||||||
page: PageSelection,
|
page: PageSelection,
|
||||||
|
/// Whether to open the exported file(s) after the export is done.
|
||||||
|
open: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// See [`ExportKind`].
|
||||||
#[derive(Debug, Clone, Default, Deserialize)]
|
#[derive(Debug, Clone, Default, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct QueryOpts {
|
struct QueryOpts {
|
||||||
|
|
@ -38,6 +43,8 @@ struct QueryOpts {
|
||||||
selector: String,
|
selector: String,
|
||||||
field: Option<String>,
|
field: Option<String>,
|
||||||
one: Option<bool>,
|
one: Option<bool>,
|
||||||
|
/// Whether to open the exported file(s) after the export is done.
|
||||||
|
open: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Deserialize)]
|
#[derive(Debug, Clone, Default, Deserialize)]
|
||||||
|
|
@ -61,22 +68,49 @@ impl LanguageState {
|
||||||
self.config.compile.determine_creation_timestamp()
|
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).
|
/// Export the current document as HTML file(s).
|
||||||
pub fn export_html(&mut self, req_id: RequestId, args: Vec<JsonValue>) -> ScheduledResult {
|
pub fn export_html(&mut self, req_id: RequestId, mut args: Vec<JsonValue>) -> ScheduledResult {
|
||||||
self.export(req_id, ExportKind::Html {}, args)
|
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).
|
/// Export the current document as Markdown file(s).
|
||||||
pub fn export_markdown(&mut self, req_id: RequestId, args: Vec<JsonValue>) -> ScheduledResult {
|
pub fn export_markdown(
|
||||||
self.export(req_id, ExportKind::Markdown {}, args)
|
&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).
|
/// Export the current document as Text file(s).
|
||||||
pub fn export_text(&mut self, req_id: RequestId, args: Vec<JsonValue>) -> ScheduledResult {
|
pub fn export_text(&mut self, req_id: RequestId, mut args: Vec<JsonValue>) -> ScheduledResult {
|
||||||
self.export(req_id, ExportKind::Text {}, args)
|
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).
|
/// Query the current document and export the result as JSON file(s).
|
||||||
|
|
@ -93,6 +127,7 @@ impl LanguageState {
|
||||||
pretty: opts.pretty.unwrap_or(true),
|
pretty: opts.pretty.unwrap_or(true),
|
||||||
one: opts.one.unwrap_or(false),
|
one: opts.one.unwrap_or(false),
|
||||||
},
|
},
|
||||||
|
opts.open.unwrap_or_default(),
|
||||||
args,
|
args,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -100,7 +135,12 @@ impl LanguageState {
|
||||||
/// Export the current document as Svg file(s).
|
/// Export the current document as Svg file(s).
|
||||||
pub fn export_svg(&mut self, req_id: RequestId, mut args: Vec<JsonValue>) -> ScheduledResult {
|
pub fn export_svg(&mut self, req_id: RequestId, mut args: Vec<JsonValue>) -> ScheduledResult {
|
||||||
let opts = get_arg_or_default!(args[1] as ExportOpts);
|
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).
|
/// Export the current document as Png file(s).
|
||||||
|
|
@ -113,6 +153,7 @@ impl LanguageState {
|
||||||
ppi: opts.ppi,
|
ppi: opts.ppi,
|
||||||
page: opts.page,
|
page: opts.page,
|
||||||
},
|
},
|
||||||
|
opts.open.unwrap_or_default(),
|
||||||
args,
|
args,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -123,11 +164,12 @@ impl LanguageState {
|
||||||
&mut self,
|
&mut self,
|
||||||
req_id: RequestId,
|
req_id: RequestId,
|
||||||
kind: ExportKind,
|
kind: ExportKind,
|
||||||
|
open: bool,
|
||||||
mut args: Vec<JsonValue>,
|
mut args: Vec<JsonValue>,
|
||||||
) -> ScheduledResult {
|
) -> ScheduledResult {
|
||||||
let path = get_arg!(args[0] as PathBuf);
|
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.
|
/// Export a range of the current document as Ansi highlighted text.
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ use sync_lsp::*;
|
||||||
use task::{CacheTask, ExportUserConfig, FormatTask, FormatUserConfig, UserActionTask};
|
use task::{CacheTask, ExportUserConfig, FormatTask, FormatUserConfig, UserActionTask};
|
||||||
use tinymist_query::PageSelection;
|
use tinymist_query::PageSelection;
|
||||||
use tinymist_query::{
|
use tinymist_query::{
|
||||||
lsp_to_typst, CompilerQueryRequest, CompilerQueryResponse, FoldRequestFeature, OnExportRequest,
|
lsp_to_typst, CompilerQueryRequest, CompilerQueryResponse, FoldRequestFeature,
|
||||||
PositionEncoding, SyntaxRequest,
|
PositionEncoding, SyntaxRequest,
|
||||||
};
|
};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
@ -1040,7 +1040,7 @@ impl LanguageState {
|
||||||
DocumentSymbol(req) => query_source!(self, DocumentSymbol, req)?,
|
DocumentSymbol(req) => query_source!(self, DocumentSymbol, req)?,
|
||||||
OnEnter(req) => query_source!(self, OnEnter, req)?,
|
OnEnter(req) => query_source!(self, OnEnter, req)?,
|
||||||
ColorPresentation(req) => CompilerQueryResponse::ColorPresentation(req.request()),
|
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(),
|
ServerInfo(_) => return primary().collect_server_info(),
|
||||||
_ => return Self::query_on(primary(), is_pinning, query),
|
_ => return Self::query_on(primary(), is_pinning, query),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -500,6 +500,23 @@ async function commandShow(kind: "Pdf" | "Svg" | "Png", extraOpts?: any): Promis
|
||||||
return;
|
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
|
// only create pdf if it does not exist yet
|
||||||
const exportPath = await commandExport(kind, extraOpts);
|
const exportPath = await commandExport(kind, extraOpts);
|
||||||
|
|
||||||
|
|
@ -509,37 +526,30 @@ async function commandShow(kind: "Pdf" | "Svg" | "Png", extraOpts?: any): Promis
|
||||||
return;
|
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) {
|
switch (openIn) {
|
||||||
default:
|
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
|
// here we can be sure that the pdf exists
|
||||||
await commands.executeCommand("vscode.open", exportUri, {
|
await commands.executeCommand("vscode.open", exportUri, {
|
||||||
viewColumn: ViewColumn.Beside,
|
viewColumn: ViewColumn.Beside,
|
||||||
preserveFocus: true,
|
preserveFocus: true,
|
||||||
} as vscode.TextDocumentShowOptions);
|
} as vscode.TextDocumentShowOptions);
|
||||||
break;
|
break;
|
||||||
case "systemDefault":
|
}
|
||||||
await vscode.env.openExternal(exportUri);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue