pepper/
lib.rs

1pub mod application;
2pub mod buffer;
3pub mod buffer_history;
4pub mod buffer_position;
5pub mod buffer_view;
6pub mod client;
7pub mod command;
8pub mod config;
9pub mod cursor;
10pub mod editor;
11pub mod editor_utils;
12pub mod events;
13pub mod glob;
14pub mod help;
15pub mod mode;
16pub mod navigation_history;
17pub mod pattern;
18pub mod picker;
19pub mod platform;
20pub mod plugin;
21pub mod serialization;
22pub mod syntax;
23pub mod theme;
24pub mod ui;
25pub mod word_database;
26
27pub const DEFAULT_CONFIGS: ResourceFile = ResourceFile {
28    name: "default_configs.pepper",
29    content: include_str!("../rc/default_configs.pepper"),
30};
31pub const DEFAULT_SYNTAXES: ResourceFile = ResourceFile {
32    name: "default_syntaxes.pepper",
33    content: include_str!("../rc/default_syntaxes.pepper"),
34};
35
36#[derive(Clone, Copy)]
37pub struct ResourceFile {
38    pub name: &'static str,
39    pub content: &'static str,
40}
41
42pub struct ArgsConfig {
43    pub path: String,
44    pub suppress_file_not_found: bool,
45}
46
47#[derive(Default)]
48pub struct Args {
49    pub version: bool,
50    pub session_name: String,
51    pub print_session: bool,
52    pub as_focused_client: bool,
53    pub quit: bool,
54    pub server: bool,
55    pub configs: Vec<ArgsConfig>,
56    pub files: Vec<String>,
57}
58
59fn print_version() {
60    let name = env!("CARGO_PKG_NAME");
61    let version = env!("CARGO_PKG_VERSION");
62    println!("{} version {}", name, version);
63}
64
65fn print_help() {
66    print_version();
67    println!("{}", env!("CARGO_PKG_DESCRIPTION"));
68    println!();
69    println!("usage: pepper [<options...>] [<files...>]");
70    println!();
71    println!("  files: file paths to open as a buffer (clients only)");
72    println!("         you can append ':<line>[:<column>]' to open it at that position");
73    println!();
74    println!("options:");
75    println!();
76    println!("  -h, --help               prints help and quits");
77    println!("  -v, --version            prints version and quits");
78    println!("  -s, --session            overrides the session name to connect to");
79    println!("  --print-session          prints the computed session name and quits");
80    println!("  --as-focused-client      sends events as if it was the currently focused client");
81    println!("  --quit                   sends a `quit` event on start");
82    println!("  --server                 only run as server");
83    println!("  -c, --config[!]          sources config file at path (repeatable) (server only)");
84    println!("                           with `!` it will suppress the 'file not found' error");
85}
86
87impl Args {
88    pub fn parse() -> Self {
89        fn error(message: std::fmt::Arguments) -> ! {
90            eprintln!("{}", message);
91            std::process::exit(0);
92        }
93
94        fn arg_to_str(arg: &std::ffi::OsString) -> &str {
95            match arg.to_str() {
96                Some(arg) => arg,
97                None => error(format_args!("could not parse arg {:?}", arg)),
98            }
99        }
100
101        let mut args = std::env::args_os();
102        args.next();
103
104        let mut parsed = Args::default();
105        while let Some(arg) = args.next() {
106            let arg = arg_to_str(&arg);
107            match arg {
108                "-h" | "--help" => {
109                    print_help();
110                    std::process::exit(0);
111                }
112                "-v" | "--version" => {
113                    print_version();
114                    std::process::exit(0);
115                }
116                "-s" | "--session" => match args.next() {
117                    Some(arg) => {
118                        let arg = arg_to_str(&arg);
119                        if !arg.chars().all(char::is_alphanumeric) {
120                            error(format_args!(
121                                "invalid session name '{}'. it can only contain alphanumeric characters", arg
122                            ));
123                        }
124                        parsed.session_name = arg.into();
125                    }
126                    None => error(format_args!("expected session after {}", arg)),
127                },
128                "--print-session" => parsed.print_session = true,
129                "--as-focused-client" => parsed.as_focused_client = true,
130                "--quit" => parsed.quit = true,
131                "--server" => parsed.server = true,
132                "-c" | "-c!" | "--config" | "--config!" => {
133                    let suppress_file_not_found = arg.ends_with('!');
134                    match args.next() {
135                        Some(arg) => {
136                            let arg = arg_to_str(&arg);
137                            parsed.configs.push(ArgsConfig {
138                                path: arg.into(),
139                                suppress_file_not_found,
140                            });
141                        }
142                        None => error(format_args!("expected config path after {}", arg)),
143                    }
144                }
145                "--" => {
146                    while let Some(arg) = args.next() {
147                        let arg = arg_to_str(&arg);
148                        parsed.files.push(arg.into());
149                    }
150                }
151                _ => {
152                    if arg.starts_with('-') {
153                        error(format_args!("invalid option '{}'", arg));
154                    } else {
155                        parsed.files.push(arg.into());
156                    }
157                }
158            }
159        }
160
161        parsed
162    }
163}
164
165#[cfg(target_os = "windows")]
166#[path = "platforms"]
167mod platform_impl {
168    #[path = "windows.rs"]
169    pub mod sys;
170}
171
172#[cfg(target_os = "linux")]
173#[path = "platforms"]
174mod platform_impl {
175    #[path = "linux.rs"]
176    pub mod sys;
177}
178
179#[cfg(target_os = "macos")]
180#[path = "platforms"]
181mod platform_impl {
182    #[path = "bsd.rs"]
183    pub mod sys;
184}
185
186#[cfg(any(
187    target_os = "freebsd",
188    target_os = "netbsd",
189    target_os = "openbsd",
190    target_os = "dragonfly",
191))]
192#[path = "platforms"]
193mod platform_impl {
194    #[path = "bsd.rs"]
195    pub mod sys;
196}
197
198#[cfg(not(any(
199    target_os = "windows",
200    target_os = "linux",
201    target_os = "macos",
202    target_os = "freebsd",
203    target_os = "netbsd",
204    target_os = "openbsd",
205    target_os = "dragonfly",
206)))]
207mod platform_impl {
208    use super::*;
209    pub mod sys {
210        use super::*;
211        pub fn try_attach_debugger() {}
212        pub fn main(_: application::ApplicationConfig) {}
213    }
214}
215
216pub fn init(config: &application::ApplicationConfig) {
217    use std::{fs, io, mem::MaybeUninit, panic};
218
219    static mut ORIGINAL_PANIC_HOOK: MaybeUninit<Box<dyn Fn(&panic::PanicInfo) + Sync + Send>> =
220        MaybeUninit::uninit();
221    unsafe { ORIGINAL_PANIC_HOOK = MaybeUninit::new(panic::take_hook()) };
222
223    static mut ON_PANIC_CONFIG: MaybeUninit<application::OnPanicConfig> = MaybeUninit::uninit();
224    unsafe { ON_PANIC_CONFIG = MaybeUninit::new(config.on_panic_config) };
225
226    static HANDLED_PANIC: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
227
228    panic::set_hook(Box::new(|info| unsafe {
229        let config = ON_PANIC_CONFIG.assume_init_ref();
230
231        let handled_panic = HANDLED_PANIC.swap(true, std::sync::atomic::Ordering::Relaxed);
232        if !handled_panic {
233            if let Some(path) = config.write_info_to_file {
234                if let Ok(mut file) = fs::File::create(path) {
235                    use io::Write;
236                    let _ = writeln!(file, "{}", info);
237                }
238            }
239
240            if config.try_attaching_debugger {
241                platform_impl::sys::try_attach_debugger();
242            }
243        }
244
245        let hook = ORIGINAL_PANIC_HOOK.assume_init_ref();
246        hook(info);
247    }));
248}
249
250pub fn run(config: application::ApplicationConfig) {
251    init(&config);
252    platform_impl::sys::main(config);
253}