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