Skip to main content

ane/frontend/
cli_frontend.rs

1use std::sync::Arc;
2
3use anyhow::Result;
4
5use crate::commands::chord::FrontendCapabilities;
6use crate::commands::chord_engine::types::ChordAction;
7use crate::commands::lsp_engine::InstallProgress;
8use crate::data::state::EditorState;
9
10use super::traits::ApplyChordAction;
11
12pub struct CliInstallProgress;
13
14impl InstallProgress for CliInstallProgress {
15    fn on_stdout(&self, line: &str) {
16        println!("{line}");
17    }
18    fn on_stderr(&self, line: &str) {
19        eprintln!("{line}");
20    }
21    fn on_failed(&self, message: &str) {
22        eprintln!("{message}");
23    }
24    fn on_complete(&self) {}
25}
26
27pub fn cli_install_progress() -> Arc<dyn InstallProgress> {
28    Arc::new(CliInstallProgress)
29}
30
31pub struct CliFrontend;
32
33impl Default for CliFrontend {
34    fn default() -> Self {
35        Self
36    }
37}
38
39impl CliFrontend {
40    pub fn new() -> Self {
41        Self
42    }
43}
44
45impl FrontendCapabilities for CliFrontend {
46    fn is_interactive(&self) -> bool {
47        false
48    }
49}
50
51impl ApplyChordAction for CliFrontend {
52    fn apply(&mut self, state: &mut EditorState, action: &ChordAction) -> Result<String> {
53        if let Some(ref diff) = action.diff {
54            if let Some(buf) = state.current_buffer_mut() {
55                let new_lines: Vec<String> = diff.modified.lines().map(String::from).collect();
56                buf.lines = if new_lines.is_empty() {
57                    vec![String::new()]
58                } else {
59                    new_lines
60                };
61                buf.dirty = true;
62            }
63            Ok(diff.modified.clone())
64        } else if let Some(ref yanked) = action.yanked_content {
65            Ok(yanked.clone())
66        } else {
67            Ok(String::new())
68        }
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use crate::commands::chord_engine::types::UnifiedDiff;
76    use crate::data::state::EditorState;
77    use std::io::Write;
78
79    fn make_state(content: &str) -> (tempfile::NamedTempFile, EditorState) {
80        let mut f = tempfile::NamedTempFile::new().unwrap();
81        f.write_all(content.as_bytes()).unwrap();
82        f.flush().unwrap();
83        let state = EditorState::for_file(f.path()).unwrap();
84        (f, state)
85    }
86
87    fn diff_action(modified: &str) -> ChordAction {
88        ChordAction {
89            buffer_name: "test".to_string(),
90            diff: Some(UnifiedDiff {
91                original: String::new(),
92                modified: modified.to_string(),
93                hunks: vec![],
94            }),
95            yanked_content: None,
96            cursor_destination: None,
97            mode_after: None,
98            highlight_ranges: vec![],
99            warnings: vec![],
100        }
101    }
102
103    fn yank_action(content: &str) -> ChordAction {
104        ChordAction {
105            buffer_name: "test".to_string(),
106            diff: None,
107            yanked_content: Some(content.to_string()),
108            cursor_destination: None,
109            mode_after: None,
110            highlight_ranges: vec![],
111            warnings: vec![],
112        }
113    }
114
115    #[test]
116    fn apply_diff_updates_buffer_lines_and_returns_modified_content() {
117        let (_f, mut state) = make_state("old line 1\nold line 2");
118        let action = diff_action("new line 1\nnew line 2");
119
120        let mut frontend = CliFrontend::new();
121        let result = frontend.apply(&mut state, &action).unwrap();
122
123        assert_eq!(result, "new line 1\nnew line 2");
124        let buf = state.current_buffer().unwrap();
125        assert_eq!(buf.lines, vec!["new line 1", "new line 2"]);
126        assert!(buf.dirty);
127    }
128
129    #[test]
130    fn apply_yank_returns_content_without_modifying_buffer() {
131        let (_f, mut state) = make_state("original line 1\noriginal line 2");
132        let original_lines = state.current_buffer().unwrap().lines.clone();
133        let action = yank_action("yanked content");
134
135        let mut frontend = CliFrontend::new();
136        let result = frontend.apply(&mut state, &action).unwrap();
137
138        assert_eq!(result, "yanked content");
139        let buf = state.current_buffer().unwrap();
140        assert_eq!(
141            buf.lines, original_lines,
142            "yank must not modify buffer lines"
143        );
144        assert!(!buf.dirty, "yank must not mark buffer dirty");
145    }
146
147    // --- work item 0005: Jump / To / Delimiter ---
148
149    #[test]
150    fn cli_frontend_is_not_interactive() {
151        let frontend = CliFrontend::new();
152        assert!(!frontend.is_interactive());
153    }
154}