This commit is contained in:
Schrottkatze 2026-08-31 13:21:30 +02:00
commit 43b0c0d9b1
10 changed files with 20 additions and 2229 deletions

View file

@ -5,7 +5,7 @@ edition = "2021"
[dependencies]
chrono = "0.4.38"
clap = { version = "4.5.4", features = ["derive", "env"] }
clap = { workspace = true, features = ["derive", "env"] }
owo-colors = "4.0.0"
temp-file = "0.1.8"
termsize = "0.1.9"

View file

@ -1,9 +0,0 @@
[package]
name = "typed"
version = "0.1.0"
edition = "2024"
[dependencies]
clap.workspace = true
crossterm = "0.29.0"
notify = "8"

View file

@ -1,156 +0,0 @@
#![feature(lock_value_accessors)]
use std::{
io,
path::Path,
process::{Command, exit},
sync::{
Arc, Condvar, Mutex,
atomic::{AtomicBool, AtomicU32, Ordering},
mpsc::channel,
},
thread::{self},
};
use clap::Parser;
use notify::{Event, RecursiveMode, Watcher};
use crate::cli::Cli;
mod cli {
use clap::Parser;
#[derive(Debug, Parser)]
pub struct Cli {
pub path: String,
#[arg(short, long, env)]
pub editor: Option<String>,
#[arg(short, long, env)]
pub viewer: Option<String>,
#[arg(short, long)]
pub no_edit: bool,
}
}
// TODOs:
// - create empty file if file doesnt exist
// - compile to pdf
// - always ensure pdf exists
// - delete pdf by default on program close
fn main() -> std::io::Result<()> {
let args = Cli::parse();
let pdf_path = format!("{}.pdf", args.path);
let typ_path = format!("{}.typ", args.path);
let mupdf_pid = Arc::new(AtomicU32::new(0));
let editor_pid = Arc::new(AtomicU32::new(0));
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pdf_path_ = pdf_path.clone();
let mupdf_pid_ = Arc::clone(&mupdf_pid);
let viewer = args.viewer.unwrap_or("mupdf-x11".to_string());
let pair_mupdf = pair.clone();
thread::spawn(move || {
let mut mupdf = Command::new(&viewer);
mupdf.arg(&pdf_path_);
loop {
let Ok(mut mupdf) = mupdf.spawn() else {
break;
};
mupdf_pid_.store(mupdf.id(), Ordering::Relaxed);
let wait_res = mupdf.wait();
if let Ok(status) = wait_res
&& status.success()
{
// we exited!
crossterm::terminal::disable_raw_mode().unwrap();
// we can unwrap here, because crashing doesnt matter when we're exiting anyway, it'll just be less clean
pair_mupdf.0.set(true).unwrap();
pair_mupdf.1.notify_all();
break;
}
}
});
let pdf_path_ = pdf_path.clone();
let mupdf_pid_ = Arc::clone(&mupdf_pid);
thread::spawn(move || {
let (tx, rx) = channel::<notify::Result<Event>>();
let mut watcher = notify::recommended_watcher(tx).unwrap();
let _ = watcher.watch(Path::new(&pdf_path_), RecursiveMode::NonRecursive);
loop {
let p = rx.recv();
match p {
Ok(Ok(e)) if e.kind.is_modify() || e.kind.is_create() => {
let mupdf_pid = mupdf_pid_.load(Ordering::Relaxed);
if mupdf_pid != 0 {
let mut kill = Command::new("kill");
let _ = kill.arg("-1").arg(mupdf_pid.to_string()).spawn();
}
}
Ok(Ok(e)) if e.kind.is_remove() => {
let _ = watcher.watch(Path::new(&pdf_path_), RecursiveMode::NonRecursive);
}
_ => {}
}
}
});
// the editor?
let pair_editor = pair.clone();
let editor_pid_ = editor_pid.clone();
if let Some(editor) = args.editor
&& !args.no_edit
{
thread::spawn(move || {
let Ok(mut editor) = Command::new(editor).arg(&typ_path).spawn() else {
pair_editor.0.set(true).unwrap();
pair_editor.1.notify_all();
return;
};
editor_pid_.store(editor.id(), Ordering::Relaxed);
let _ = editor.wait();
// we know the editor exited here!
pair_editor.0.set(true).unwrap();
pair_editor.1.notify_all();
});
}
let (done, cvar) = &*pair;
// result doesnt matter.
// poisoned? we exit
// done? we exit
// we'll complain about the poison tho
let res = done
.lock()
.and_then(|lock| cvar.wait_while(lock, |done| !*done));
let mupdf_pid = mupdf_pid.load(Ordering::Relaxed);
let mupdf_killer = Command::new("kill")
.arg("-9")
.arg(mupdf_pid.to_string())
.spawn();
let editor_pid = editor_pid.load(Ordering::Relaxed);
if editor_pid != 0 {
let editor_killer = Command::new("kill")
.arg("-9")
.arg(editor_pid.to_string())
.spawn();
let _ = editor_killer.unwrap().wait();
}
let _ = mupdf_killer.unwrap().wait();
println!("goodbye :3");
if res.is_err() {
println!("the waiter was poisoned btw :(")
}
Ok(())
}