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}