1use cool_rust_input::{
4 set_terminal_line, CoolInput, CustomInputHandler, HandlerContext, InputTransform,
5 KeyPressResult,
6};
7use crossterm::cursor;
8use crossterm::event::{Event, KeyCode, KeyEventKind, KeyModifiers};
9use crossterm::style::{ResetColor, SetBackgroundColor};
10use crossterm::{
11 queue,
12 style::{Color, SetForegroundColor},
13};
14use std::env;
15use std::fs;
16use std::io::stdout;
17
18fn save_file(filename: &str, text: &str) {
19 fs::write(filename, text).expect("Unable to write new contents.");
20}
21pub struct FileEditorInput {
22 pub filename: String,
23 original_text: String,
24 is_new: bool,
25}
26impl FileEditorInput {
27 fn open_filename(filename: String, original_text: String, is_new: bool) -> Self {
28 FileEditorInput {
29 filename,
30 original_text,
31 is_new,
32 }
33 }
34}
35impl CustomInputHandler for FileEditorInput {
36 fn handle_key_press(&mut self, key: &Event, ctx: HandlerContext) -> KeyPressResult {
37 if let Event::Key(key_event) = key {
38 if key_event.kind == crossterm::event::KeyEventKind::Press {
39 if let KeyCode::Char(c) = key_event.code {
40 if key_event.modifiers.contains(KeyModifiers::CONTROL) {
41 if c == 'c' {
43 return KeyPressResult::Stop;
44 }
45 if c == 's' {
47 save_file(&self.filename, &ctx.text_data.text);
48 self.is_new = false;
49 self.original_text = ctx.text_data.text.to_owned();
50 return KeyPressResult::Handled;
51 }
52 }
53 }
54 }
55 }
56 KeyPressResult::Continue
57 }
58 fn after_draw_text(&mut self, ctx: HandlerContext) {
59 let _ = queue!(
60 stdout(),
61 SetForegroundColor(Color::Black),
62 SetBackgroundColor(Color::White)
63 );
64 let left_text = format!("BANANO v{}", env!("CARGO_PKG_VERSION"));
65 let center_text = format!("FILE: '{}'", self.filename);
66 let mut right_text = "NOT MODIFIED";
67
68 if self.original_text != ctx.text_data.text {
69 right_text = "MODIFIED";
70 }
71 if self.is_new {
72 right_text = "NEW FILE"
73 }
74
75 let bottom_text_position = (ctx.terminal_size.1 - 1) as usize;
76 let width = self.get_input_transform(ctx).size.0;
77
78 let _ = set_terminal_line(&left_text, 0, 0, true);
79 let _ = set_terminal_line(
80 ¢er_text,
81 (width as usize - center_text.len()) / 2,
82 0,
83 false,
84 );
85 let _ = set_terminal_line(right_text, width as usize - right_text.len(), 0, false);
86
87 let keybinds = ["^S".to_string(), "^C".to_string()];
88 let descriptions = ["Save File".to_string(), "Exit".to_string()];
89
90 let mut offset = 0;
91 for (keybind, description) in keybinds.iter().zip(descriptions) {
92 let _ = queue!(
93 stdout(),
94 SetForegroundColor(Color::Black),
95 SetBackgroundColor(Color::White)
96 );
97 let _ = set_terminal_line(keybind, offset, bottom_text_position, false);
98 offset += keybind.chars().count() + 1;
99 let _ = queue!(stdout(), ResetColor);
100 let _ = set_terminal_line(&description, offset, bottom_text_position, false);
101 offset += description.chars().count() + 1;
102 }
103 }
104 fn get_input_transform(&mut self, ctx: HandlerContext) -> InputTransform {
105 let size = (ctx.terminal_size.0, ctx.terminal_size.1 - 3);
106 let offset = (0, 2);
107 InputTransform { size, offset }
108 }
109}
110
111pub fn path_exists(path: &str) -> bool {
112 fs::metadata(path).is_ok()
113}
114
115pub struct ConfirmationInputHandler {
117 pub prompt: String,
118 pub value: bool,
119}
120impl ConfirmationInputHandler {
121 pub fn prompt(prompt: &str) -> Result<bool, std::io::Error> {
122 let handler = ConfirmationInputHandler {
123 prompt: prompt.to_string(),
124 value: false,
125 };
126 let mut input = CoolInput::new(handler, 0);
127 input.listen()?;
128 Ok(input.custom_input.value)
129 }
130}
131impl CustomInputHandler for ConfirmationInputHandler {
132 fn get_input_transform(&mut self, ctx: HandlerContext) -> InputTransform {
133 let prompt_offset = self.prompt.chars().count() as u16;
134 InputTransform {
135 size: (ctx.terminal_size.0 - prompt_offset, ctx.terminal_size.1),
136 offset: (prompt_offset, 0),
137 }
138 }
139 fn after_update_cursor(&mut self, _: HandlerContext) {
140 let _ = queue!(stdout(), cursor::Hide);
141 }
142 fn after_draw_text(&mut self, _: HandlerContext) {
143 let _ = set_terminal_line(&self.prompt, 0, 0, false);
144 }
145 fn handle_key_press(&mut self, key: &Event, _: HandlerContext) -> KeyPressResult {
146 if let Event::Key(key_event) = key {
147 if key_event.kind == KeyEventKind::Press {
148 if let KeyCode::Char(c) = key_event.code {
150 if c == 'c' && key_event.modifiers.contains(KeyModifiers::CONTROL) {
151 return KeyPressResult::Stop;
152 } else if c == 'y' || c == 'n' {
153 self.value = c == 'y';
154 return KeyPressResult::Stop;
155 }
156 }
157 }
158 }
159 KeyPressResult::Handled
160 }
161}
162
163fn main() -> Result<(), std::io::Error> {
164 let args: Vec<_> = env::args().collect();
165 if args.len() != 2 {
166 println!("please specify a filename!");
167 return Ok(());
168 }
169 let filename = &args[1];
170 let mut text = String::new();
171 let mut is_new = true;
172 if path_exists(filename) {
173 text = fs::read_to_string(filename).expect("Unable to read file contents.");
174 is_new = false;
175 }
176 let mut cool_input = CoolInput::new(
177 FileEditorInput::open_filename(filename.to_string(), text.to_owned(), is_new),
178 0,
179 );
180 cool_input.text_data.text = text;
181 cool_input.listen()?;
182 if cool_input.custom_input.original_text != cool_input.text_data.text {
183 let save = ConfirmationInputHandler::prompt("Save file? [y/n]").unwrap();
184 if save {
185 save_file(filename, &cool_input.text_data.text);
186 }
187 }
188 Ok(())
189}