ane/frontend/
cli_frontend.rs1use 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 #[test]
150 fn cli_frontend_is_not_interactive() {
151 let frontend = CliFrontend::new();
152 assert!(!frontend.is_interactive());
153 }
154}