mirror of
https://forge.katzen.cafe/schrottkatze/nix-configs.git
synced 2026-09-16 17:07:29 +02:00
parent
43b0c0d9b1
commit
b973e36ebb
2 changed files with 165 additions and 0 deletions
9
programs/typed/Cargo.toml
Normal file
9
programs/typed/Cargo.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "typed"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap.workspace = true
|
||||||
|
crossterm = "0.29.0"
|
||||||
|
notify = "8"
|
||||||
156
programs/typed/src/main.rs
Normal file
156
programs/typed/src/main.rs
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
#![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(())
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue