Skip to main content

navi/finder/
post.rs

1use crate::common::shell;
2use crate::finder::structures::SuggestionType;
3use crate::prelude::*;
4use shell::EOF;
5use std::process::Stdio;
6
7fn apply_map(text: String, map_fn: Option<String>) -> Result<String> {
8    if let Some(m) = map_fn {
9        let cmd = if CONFIG.shell().contains("fish") {
10            format!(r#"printf "%s" "{text}" | {m}"#)
11        } else {
12            format!(
13                r#"_navi_input() {{
14cat <<'{EOF}'
15{text}
16{EOF}
17}}
18
19_navi_map_fn() {{
20  {m}
21}}
22
23_navi_nonewline() {{
24  printf "%s" "$(cat)"
25}}
26
27_navi_input | _navi_map_fn | _navi_nonewline"#
28            )
29        };
30
31        let output = shell::out()
32            .arg(cmd.as_str())
33            .stderr(Stdio::inherit())
34            .output()
35            .context("Failed to execute map function")?;
36
37        String::from_utf8(output.stdout).context("Invalid utf8 output for map function")
38    } else {
39        Ok(text)
40    }
41}
42
43fn get_column(text: String, column: Option<u8>, delimiter: Option<&str>) -> String {
44    if let Some(c) = column {
45        let mut result = String::from("");
46        let re = regex::Regex::new(delimiter.unwrap_or(r"\s\s+")).expect("Invalid regex");
47        for line in text.split('\n') {
48            if (line).is_empty() {
49                continue;
50            }
51            let mut parts = re.split(line).skip((c - 1) as usize);
52            if !result.is_empty() {
53                result.push('\n');
54            }
55            result.push_str(parts.next().unwrap_or(""));
56        }
57        result
58    } else {
59        text
60    }
61}
62
63pub fn process(
64    text: String,
65    column: Option<u8>,
66    delimiter: Option<&str>,
67    map_fn: Option<String>,
68) -> Result<String> {
69    apply_map(get_column(text, column, delimiter), map_fn)
70}
71
72pub(super) fn parse_output_single(mut text: String, suggestion_type: SuggestionType) -> Result<String> {
73    Ok(match suggestion_type {
74        SuggestionType::SingleSelection => text
75            .lines()
76            .next()
77            .context("No sufficient data for single selection")?
78            .to_string(),
79        SuggestionType::MultipleSelections | SuggestionType::Disabled | SuggestionType::SnippetSelection => {
80            let len = text.len();
81            if len > 1 {
82                text.truncate(len - 1);
83            }
84            text
85        }
86        SuggestionType::SingleRecommendation => {
87            let lines: Vec<&str> = text.lines().collect();
88
89            match (lines.first(), lines.get(1), lines.get(2)) {
90                (Some(one), Some(termination), Some(two))
91                    if *termination == "enter" || termination.is_empty() =>
92                {
93                    if two.is_empty() {
94                        (*one).to_string()
95                    } else {
96                        (*two).to_string()
97                    }
98                }
99                (Some(one), Some(termination), None) if *termination == "enter" || termination.is_empty() => {
100                    (*one).to_string()
101                }
102                (Some(one), Some(termination), _) if *termination == "tab" => (*one).to_string(),
103                _ => "".to_string(),
104            }
105        }
106    })
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_parse_output1() {
115        let text = "palo\n".to_string();
116        let output = parse_output_single(text, SuggestionType::SingleSelection).unwrap();
117        assert_eq!(output, "palo");
118    }
119
120    #[test]
121    fn test_parse_output2() {
122        let text = "\nenter\npalo".to_string();
123        let output = parse_output_single(text, SuggestionType::SingleRecommendation).unwrap();
124        assert_eq!(output, "palo");
125    }
126
127    #[test]
128    fn test_parse_recommendation_output_1() {
129        let text = "\nenter\npalo".to_string();
130        let output = parse_output_single(text, SuggestionType::SingleRecommendation).unwrap();
131        assert_eq!(output, "palo");
132    }
133
134    #[test]
135    fn test_parse_recommendation_output_2() {
136        let text = "p\nenter\npalo".to_string();
137        let output = parse_output_single(text, SuggestionType::SingleRecommendation).unwrap();
138        assert_eq!(output, "palo");
139    }
140
141    #[test]
142    fn test_parse_recommendation_output_3() {
143        let text = "peter\nenter\n".to_string();
144        let output = parse_output_single(text, SuggestionType::SingleRecommendation).unwrap();
145        assert_eq!(output, "peter");
146    }
147
148    #[test]
149    fn test_parse_output3() {
150        let text = "p\ntab\npalo".to_string();
151        let output = parse_output_single(text, SuggestionType::SingleRecommendation).unwrap();
152        assert_eq!(output, "p");
153    }
154
155    #[test]
156    fn test_parse_snippet_request() {
157        let text = "enter\nssh                     ⠀login to a server and forward to ssh key (d…  ⠀ssh -A <user>@<server>  ⠀ssh  ⠀login to a server and forward to ssh key (dangerous but useful for bastion hosts)  ⠀ssh -A <user>@<server>  ⠀\n".to_string();
158        let output = parse_output_single(text, SuggestionType::SnippetSelection).unwrap();
159        assert_eq!(output,     "enter\nssh                     ⠀login to a server and forward to ssh key (d…  ⠀ssh -A <user>@<server>  ⠀ssh  ⠀login to a server and forward to ssh key (dangerous but useful for bastion hosts)  ⠀ssh -A <user>@<server>  ⠀");
160    }
161}