Skip to content

Commit

Permalink
feat: configure the output path of pdf files
Browse files Browse the repository at this point in the history
  • Loading branch information
Myriad-Dreamin committed Mar 10, 2024
1 parent 988b09a commit fc573db
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 36 deletions.
15 changes: 8 additions & 7 deletions crates/tinymist/src/actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,25 @@ impl TypstLanguageServer {
let (doc_tx, doc_rx) = watch::channel(None);
let (render_tx, _) = broadcast::channel(10);

let roots = self.roots.clone();
let root_dir = roots.first().cloned().unwrap_or_default();
// Run the PDF export actor before preparing cluster to avoid loss of events
tokio::spawn(
PdfExportActor::new(
doc_rx.clone(),
render_tx.subscribe(),
Some(PdfExportConfig {
path: entry
.as_ref()
.map(|e| e.clone().with_extension("pdf").into()),
PdfExportConfig {
substitute_pattern: self.config.output_path.clone(),
root: root_dir.clone().into(),
path: entry.clone().map(From::from),
mode: self.config.export_pdf,
}),
},
)
.run(),
);

let roots = self.roots.clone();
let opts = CompileOpts {
root_dir: roots.first().cloned().unwrap_or_default(),
root_dir,
// todo: font paths
// font_paths: arguments.font_paths.clone(),
with_embedded_fonts: typst_assets::fonts().map(Cow::Borrowed).collect(),
Expand Down
107 changes: 97 additions & 10 deletions crates/tinymist/src/actor/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,28 @@ use tokio::sync::{
watch,
};
use typst::foundations::Smart;
use typst_ts_core::{ImmutPath, TypstDocument};
use typst_ts_core::{path::PathClean, ImmutPath, TypstDocument};

use crate::ExportPdfMode;

#[derive(Debug, Clone)]
pub enum RenderActorRequest {
OnTyped,
OnSaved(PathBuf),
ChangeExportPath(Option<ImmutPath>),
ChangeExportPath(PdfPathVars),
ChangeConfig(PdfExportConfig),
}

#[derive(Debug, Clone)]
pub struct PdfPathVars {
pub root: ImmutPath,
pub path: Option<ImmutPath>,
}

#[derive(Debug, Clone)]
pub struct PdfExportConfig {
pub substitute_pattern: String,
pub root: ImmutPath,
pub path: Option<ImmutPath>,
pub mode: ExportPdfMode,
}
Expand All @@ -34,6 +42,8 @@ pub struct PdfExportActor {
render_rx: broadcast::Receiver<RenderActorRequest>,
document: watch::Receiver<Option<Arc<TypstDocument>>>,

pub substitute_pattern: String,
pub root: ImmutPath,
pub path: Option<ImmutPath>,
pub mode: ExportPdfMode,
}
Expand All @@ -42,13 +52,15 @@ impl PdfExportActor {
pub fn new(
document: watch::Receiver<Option<Arc<TypstDocument>>>,
render_rx: broadcast::Receiver<RenderActorRequest>,
config: Option<PdfExportConfig>,
config: PdfExportConfig,
) -> Self {
Self {
render_rx,
document,
path: config.as_ref().and_then(|c| c.path.clone()),
mode: config.map(|c| c.mode).unwrap_or(ExportPdfMode::Auto),
substitute_pattern: config.substitute_pattern,
root: config.root,
path: config.path,
mode: config.mode,
}
}

Expand All @@ -72,11 +84,14 @@ impl PdfExportActor {
info!("PdfRenderActor: received request: {req:?}", req = req);
match req {
RenderActorRequest::ChangeConfig(cfg) => {
self.substitute_pattern = cfg.substitute_pattern;
self.root = cfg.root;
self.path = cfg.path;
self.mode = cfg.mode;
}
RenderActorRequest::ChangeExportPath(cfg) => {
self.path = cfg;
self.root = cfg.root;
self.path = cfg.path;
}
_ => {
self.check_mode_and_export(req).await;
Expand All @@ -99,7 +114,10 @@ impl PdfExportActor {
_ => unreachable!(),
};

info!("PdfRenderActor: check path {:?}", self.path);
info!(
"PdfRenderActor: check path {:?} with output directory {}",
self.path, self.substitute_pattern
);
if let Some(path) = self.path.as_ref() {
if (get_mode(self.mode) == eq_mode) || validate_document(&req, self.mode, &document) {
let Err(err) = self.export_pdf(&document, path).await else {
Expand Down Expand Up @@ -135,15 +153,84 @@ impl PdfExportActor {
}

async fn export_pdf(&self, doc: &TypstDocument, path: &Path) -> anyhow::Result<()> {
let Some(to) = substitute_path(&self.substitute_pattern, &self.root, path) else {
return Err(anyhow::anyhow!("failed to substitute path"));
};
if to.is_relative() {
return Err(anyhow::anyhow!("path is relative: {to:?}"));
}
if to.is_dir() {
return Err(anyhow::anyhow!("path is a directory: {to:?}"));
}

let to = to.with_extension("pdf");
info!("exporting PDF {path:?} to {to:?}");

if let Some(e) = to.parent() {
if !e.exists() {
std::fs::create_dir_all(e).context("failed to create directory")?;
}
}

// todo: Some(pdf_uri.as_str())
// todo: timestamp world.now()
info!("exporting PDF {path}", path = path.display());

let data = typst_pdf::pdf(doc, Smart::Auto, None);

std::fs::write(path, data).context("failed to export PDF")?;
std::fs::write(to, data).context("failed to export PDF")?;

info!("PDF export complete");
Ok(())
}
}

#[comemo::memoize]
fn substitute_path(substitute_pattern: &str, root: &Path, path: &Path) -> Option<ImmutPath> {
if substitute_pattern.is_empty() {
return Some(path.to_path_buf().clean().into());
}

let path = path.strip_prefix(root).ok()?;
let dir = path.parent();
let file_name = path.file_name().unwrap_or_default();

let w = root.to_string_lossy();
let f = file_name.to_string_lossy();

// replace all $root
let mut path = substitute_pattern.replace("$root", &w);
if let Some(dir) = dir {
let d = dir.to_string_lossy();
path = path.replace("$dir", &d);
}
path = path.replace("$name", &f);

Some(PathBuf::from(path).clean().into())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_substitute_path() {
let root = Path::new("/root");
let path = Path::new("/root/dir1/dir2/file.txt");

assert_eq!(
substitute_path("/substitute/$dir/$name", root, path),
Some(PathBuf::from("/substitute/dir1/dir2/file.txt").into())
);
assert_eq!(
substitute_path("/substitute/$dir/../$name", root, path),
Some(PathBuf::from("/substitute/dir1/file.txt").into())
);
assert_eq!(
substitute_path("/substitute/$name", root, path),
Some(PathBuf::from("/substitute/file.txt").into())
);
assert_eq!(
substitute_path("/substitute/target/$dir/$name", root, path),
Some(PathBuf::from("/substitute/target/dir1/dir2/file.txt").into())
);
}
}
28 changes: 18 additions & 10 deletions crates/tinymist/src/actor/typst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use typst_ts_core::{

use super::compile::CompileClient as TsCompileClient;
use super::{compile::CompileActor as CompileActorInner, render::PdfExportConfig};
use crate::actor::render::RenderActorRequest;
use crate::actor::render::{PdfPathVars, RenderActorRequest};
use crate::ConstConfig;

type CompileService<H> = CompileActorInner<Reporter<CompileExporter<CompileDriver>, H>>;
Expand Down Expand Up @@ -80,14 +80,15 @@ pub fn create_server(
inner: driver,
cb: handler.clone(),
};
let driver = CompileActorInner::new(driver, root).with_watch(true);
let driver = CompileActorInner::new(driver, root.clone()).with_watch(true);

let (server, client) = driver.split();

current_runtime.spawn(server.spawn());

let this = CompileActor::new(
diag_group,
root.into(),
cfg.position_encoding,
handler,
client,
Expand Down Expand Up @@ -297,6 +298,7 @@ pub struct CompileActor {
diag_group: String,
position_encoding: PositionEncoding,
handler: CompileHandler,
root: ImmutPath,
entry: Arc<Mutex<Option<ImmutPath>>>,
pub inner: CompileClient<CompileHandler>,
render_tx: broadcast::Sender<RenderActorRequest>,
Expand Down Expand Up @@ -383,9 +385,10 @@ impl CompileActor {
);

self.render_tx
.send(RenderActorRequest::ChangeExportPath(Some(
next.with_extension("pdf").into(),
)))
.send(RenderActorRequest::ChangeExportPath(PdfPathVars {
root: self.root.clone(),
path: Some(next.clone()),
}))
.unwrap();

// todo
Expand All @@ -402,9 +405,10 @@ impl CompileActor {

if res.is_err() {
self.render_tx
.send(RenderActorRequest::ChangeExportPath(
prev.clone().map(|e| e.with_extension("pdf").into()),
))
.send(RenderActorRequest::ChangeExportPath(PdfPathVars {
root: self.root.clone(),
path: prev.clone(),
}))
.unwrap();

let mut entry = entry.lock();
Expand All @@ -423,16 +427,18 @@ impl CompileActor {
Ok(())
}

pub(crate) fn change_export_pdf(&self, export_pdf: crate::ExportPdfMode) {
pub(crate) fn change_export_pdf(&self, config: PdfExportConfig) {
let entry = self.entry.lock();
let path = entry
.as_ref()
.map(|e| e.clone().with_extension("pdf").into());
let _ = self
.render_tx
.send(RenderActorRequest::ChangeConfig(PdfExportConfig {
substitute_pattern: config.substitute_pattern,
root: self.root.clone(),
path,
mode: export_pdf,
mode: config.mode,
}))
.unwrap();
}
Expand Down Expand Up @@ -526,13 +532,15 @@ impl CompileHost for CompileActor {}
impl CompileActor {
fn new(
diag_group: String,
root: ImmutPath,
position_encoding: PositionEncoding,
handler: CompileHandler,
inner: CompileClient<CompileHandler>,
render_tx: broadcast::Sender<RenderActorRequest>,
) -> Self {
Self {
diag_group,
root,
position_encoding,
handler,
entry: Arc::new(Mutex::new(None)),
Expand Down
10 changes: 10 additions & 0 deletions crates/tinymist/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ pub enum SemanticTokensMode {
type Listener<T> = Box<dyn FnMut(&T) -> anyhow::Result<()>>;

const CONFIG_ITEMS: &[&str] = &[
"outputPath",
"exportPdf",
"rootPath",
"semanticTokens",
Expand All @@ -152,6 +153,8 @@ const CONFIG_ITEMS: &[&str] = &[
/// The user configuration read from the editor.
#[derive(Default)]
pub struct Config {
/// The output directory for PDF export.
pub output_path: String,
/// The mode of PDF export.
pub export_pdf: ExportPdfMode,
/// Specifies the root path of the project manually.
Expand Down Expand Up @@ -210,6 +213,12 @@ impl Config {
/// # Errors
/// Errors if the update is invalid.
pub fn update_by_map(&mut self, update: &Map<String, JsonValue>) -> anyhow::Result<()> {
if let Some(JsonValue::String(output_path)) = update.get("outputPath") {
self.output_path = output_path.to_owned();
} else {
self.output_path = String::new();
}

let export_pdf = update
.get("exportPdf")
.map(ExportPdfMode::deserialize)
Expand All @@ -218,6 +227,7 @@ impl Config {
self.export_pdf = export_pdf;
}

// todo: it doesn't respect the root path
let root_path = update.get("rootPath");
if let Some(root_path) = root_path {
if root_path.is_null() {
Expand Down
17 changes: 14 additions & 3 deletions crates/tinymist/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ use typst::util::Deferred;

pub type MaySyncResult<'a> = Result<JsonValue, BoxFuture<'a, JsonValue>>;

use crate::actor::render::PdfExportConfig;
use crate::init::*;

// Enforces drop order
Expand Down Expand Up @@ -793,17 +794,27 @@ impl TypstLanguageServer {
}

fn on_changed_configuration(&mut self, values: Map<String, JsonValue>) -> LspResult<()> {
let output_directory = self.config.output_path.clone();
let export_pdf = self.config.export_pdf;
match self.config.update_by_map(&values) {
Ok(()) => {
info!("new settings applied");

if export_pdf != self.config.export_pdf {
self.primary().change_export_pdf(self.config.export_pdf);
if output_directory != self.config.output_path
|| export_pdf != self.config.export_pdf
{
let config = PdfExportConfig {
substitute_pattern: self.config.output_path.clone(),
mode: self.config.export_pdf,
root: Path::new("").into(),
path: None,
};

self.primary().change_export_pdf(config.clone());
{
let m = self.main.lock();
if let Some(main) = m.as_ref() {
main.wait().change_export_pdf(self.config.export_pdf);
main.wait().change_export_pdf(config);
}
}
}
Expand Down
Loading

0 comments on commit fc573db

Please sign in to comment.