1use core::str::FromStr;
4use std::path::PathBuf;
5
6use color_eyre::eyre;
7use color_eyre::eyre::WrapErr;
8use color_eyre::eyre::bail;
9use color_eyre::eyre::eyre;
10
11#[derive(Debug, Eq, PartialEq)]
13#[cfg_attr(any(test, feature = "fake"), derive(fake::Dummy))]
14pub struct HxStatusLine {
15 pub file_path: PathBuf,
17 pub position: HxCursorPosition,
19}
20
21impl FromStr for HxStatusLine {
23 type Err = eyre::Error;
24
25 fn from_str(hx_status_line: &str) -> Result<Self, Self::Err> {
26 let hx_status_line = hx_status_line.trim();
27
28 let elements: Vec<&str> = hx_status_line.split_ascii_whitespace().collect();
29
30 let path_left_separator_idx = elements
31 .iter()
32 .position(|x| x == &"`")
33 .ok_or_else(|| eyre!("error missing left path separator | elements={elements:#?}"))?;
34 let path_right_separator_idx = elements
35 .iter()
36 .rposition(|x| x == &"`")
37 .ok_or_else(|| eyre!("error missing right path separator | elements={elements:#?}"))?;
38
39 let path_slice_range = path_left_separator_idx..path_right_separator_idx;
40 let path_slice = elements
41 .get(path_slice_range.clone())
42 .ok_or_else(|| eyre!("error invalid path slice indices | range={path_slice_range:#?}"))?;
43 let ["`", path] = path_slice else {
44 bail!("missing path | elements={elements:#?}");
45 };
46
47 Ok(Self {
48 file_path: path.into(),
49 position: HxCursorPosition::from_str(
50 elements
51 .last()
52 .ok_or_else(|| eyre!("error missing last element | elements={elements:#?}"))?,
53 )?,
54 })
55 }
56}
57
58#[derive(Debug, Eq, PartialEq)]
60#[cfg_attr(any(test, feature = "fake"), derive(fake::Dummy))]
61pub struct HxCursorPosition {
62 pub column: usize,
64 pub line: usize,
66}
67
68impl FromStr for HxCursorPosition {
70 type Err = eyre::Error;
71
72 fn from_str(s: &str) -> Result<Self, Self::Err> {
73 let (line, column) = s
74 .split_once(':')
75 .ok_or_else(|| eyre!("error missing line column delimiter | input={s}"))?;
76
77 Ok(Self {
78 line: line
79 .parse()
80 .wrap_err_with(|| eyre!("invalid line number | input={s:?}"))?,
81 column: column
82 .parse()
83 .wrap_err_with(|| eyre!("invalid column number | input={s:?}"))?,
84 })
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn hx_cursor_from_str_works_as_expected_with_a_file_path_pointing_to_an_existent_file_in_normal_mode() {
94 let result = HxStatusLine::from_str(
95 " ● 1 ` src/utils.rs ` 1 sel 1 char W ● 1 42:33 ",
96 );
97 let expected = HxStatusLine {
98 file_path: "src/utils.rs".into(),
99 position: HxCursorPosition { line: 42, column: 33 },
100 };
101
102 assert_eq!(result.unwrap(), expected);
103 }
104
105 #[test]
106 fn hx_cursor_from_str_works_as_expected_with_a_file_path_pointing_to_an_existent_file_and_a_spinner() {
107 let result = HxStatusLine::from_str(
108 "⣷ ` src/utils.rs ` 1 sel 1 char W ● 1 33:42 ",
109 );
110 let expected = HxStatusLine {
111 file_path: "src/utils.rs".into(),
112 position: HxCursorPosition { line: 33, column: 42 },
113 };
114
115 assert_eq!(result.unwrap(), expected);
116 }
117}