1use crate::{config::config_path, editor::built_in_commands};
3use std::{
4 iter::Peekable,
5 path::Path,
6 str::Chars,
7 sync::{Arc, LockResult, RwLock, RwLockReadGuard},
8};
9use tracing::warn;
10
11#[derive(Debug, Default, Clone)]
14pub struct ReadOnlyLock<T>(Arc<RwLock<T>>);
15
16impl<T> ReadOnlyLock<T> {
17 pub fn new(inner: Arc<RwLock<T>>) -> Self {
19 Self(inner)
20 }
21
22 pub fn read(&self) -> LockResult<RwLockReadGuard<'_, T>> {
24 self.0.read()
25 }
26}
27
28pub(crate) fn gen_help_docs() -> String {
31 let help_template = include_str!("../data/help-template.txt");
32
33 help_template
34 .replace("{{BUILT_IN_COMMANDS}}", &commands_section())
35 .replace("{{CONFIG_PATH}}", &config_path())
36}
37
38fn commands_section() -> String {
39 let commands = built_in_commands();
40 let mut buf = Vec::with_capacity(commands.len());
41
42 for (cmds, desc) in commands.into_iter() {
43 buf.push((cmds.join(" | "), desc));
44 }
45
46 let w_max = buf.iter().map(|(s, _)| s.len()).max().unwrap();
47 let mut s = String::new();
48
49 for (cmds, desc) in buf.into_iter() {
50 s.push_str(&format!("{:width$} -- {desc}\n", cmds, width = w_max));
51 }
52
53 s
54}
55
56pub(crate) fn parse_num(initial: char, it: &mut Peekable<Chars<'_>>) -> usize {
59 let mut s = String::from(initial);
60 loop {
61 match it.peek() {
62 Some(ch) if ch.is_ascii_digit() => {
63 s.push(it.next().unwrap());
64 }
65 _ => return s.parse().unwrap(),
66 }
67 }
68}
69
70pub(crate) fn normalize_line_endings(mut s: String) -> String {
71 if !s.contains('\r') {
72 return s;
73 }
74
75 warn!("normalizing \\r characters to \\n");
76 s = s.replace("\r\n", "\n");
77 s.replace("\r", "\n")
78}
79
80pub(crate) fn parent_dir_containing<'a>(initial: &'a Path, target: &str) -> Option<&'a Path> {
82 initial
83 .ancestors()
84 .find(|&p| p.is_dir() && p.join(target).exists())
85}