1use std::cmp::{max, min};
2use std::fs::File;
3use std::io::{prelude::*, stdin, stdout, BufReader, Stdout, Write};
4use std::path::PathBuf;
5use termion::style::{Invert, Reset};
6use termion::{cursor::Goto, event::Key, input::TermRead, raw::IntoRawMode, screen::ToAlternateScreen};
7
8use std::sync::atomic::{AtomicBool, Ordering};
10use std::ffi::c_void;
11use twizzler_abi::upcall::UpcallData;
12use twizzler_rt_abi::bindings::twz_rt_set_upcall_handler;
13
14static SIGWINCH_RECEIVED: AtomicBool = AtomicBool::new(false);
15
16unsafe extern "C-unwind" fn upcall_handler(_frame: *mut c_void, data: *const c_void) {
17 if let Some(data) = unsafe { data.cast::<UpcallData>().as_ref() } {
18 if let twizzler_abi::upcall::UpcallInfo::Mailbox(val) = data.info {
19 if val == libc::SIGWINCH as u64 {
20 SIGWINCH_RECEIVED.store(true, Ordering::SeqCst);
21 }
22 }
23 }
24}
25
26fn to_str(s: &Vec<char>) -> String {
27 s.iter().collect()
28}
29
30fn to_vec(s: &str) -> Vec<char> {
31 s.chars().collect()
32}
33
34enum State {
35 Femto,
36 Cmd((Command, LineBuffer)),
37}
38
39enum Command {
40 Open,
41 Save,
42}
43
44trait Buffer {
45 fn push(&mut self, c: char);
46 fn backspace(&mut self);
47 fn delete(&mut self);
48 fn move_caret(&mut self, row: i32, col: i32);
49}
50
51struct Editor {
52 file_buffer: FileBuffer,
53 state: State,
54 message: Option<String>,
55}
56
57impl Editor {
58 fn new() -> Self {
59 Self {
60 file_buffer: FileBuffer::new(),
61 state: State::Femto,
62 message: None,
63 }
64 }
65
66 fn buffer(&mut self) -> &mut dyn Buffer {
67 match &mut self.state {
68 State::Femto => &mut self.file_buffer,
69 State::Cmd((_, buffer)) => buffer,
70 }
71 }
72
73 fn push(&mut self, c: char) {
74 if c == '\n' {
75 match &self.state {
76 State::Femto => self.buffer().push(c),
77 State::Cmd((state, buffer)) => {
78 let line = buffer.line.clone();
79 match state {
80 Command::Open => self.open(PathBuf::from(&to_str(&line))),
81 Command::Save => self.save(PathBuf::from(&to_str(&line))),
82 }
83 }
84 }
85 } else {
86 self.buffer().push(c);
87 }
88 }
89
90 fn start_open(&mut self) {
91 self.state = State::Cmd((Command::Open, LineBuffer::default()));
92 }
93
94 fn open(&mut self, path: PathBuf) {
95 match self.file_buffer.load(path.clone()) {
96 Ok(_) => self.exit_command(),
97 Err(err) => self.show_message(err.to_string()),
98 }
99 }
100
101 fn start_save(&mut self) {
102 let mut buffer = LineBuffer::default();
103 buffer.line = self.file_buffer.path.to_str().unwrap().chars().collect();
104 buffer.col = buffer.line.len();
105 self.state = State::Cmd((Command::Save, buffer));
106 }
107
108 fn save(&mut self, path: PathBuf) {
109 match self.file_buffer.save(path.clone()) {
110 Ok(_) => self.exit_command(),
111 Err(err) => self.show_message(err.to_string()),
112 }
113 }
114
115 fn prompt(&self) -> (&str, String, usize) {
116 match &self.state {
117 State::Femto => match &self.message {
118 Some(message) => ("", message.clone(), 0),
119 None => ("femto", String::new(), 0),
120 },
121 State::Cmd((state, buf)) => match state {
122 Command::Open => ("Open file at: ", to_str(&buf.line), buf.col),
123 Command::Save => ("Save file at: ", to_str(&buf.line), buf.col),
124 },
125 }
126 }
127
128 fn show_message(&mut self, message: String) {
129 self.exit_command();
130 self.message = Some(message);
131 }
132
133 fn exit_command(&mut self) {
134 self.state = State::Femto;
135 }
136}
137
138struct FileBuffer {
139 row_offset: usize,
140 col_offset: usize,
141 row: usize,
142 col: usize,
143 path: PathBuf,
144 lines: Vec<Vec<char>>,
145}
146
147impl FileBuffer {
148 fn new() -> Self {
149 Self {
150 row_offset: 0,
151 col_offset: 0,
152 row: 0,
153 col: 0,
154 path: PathBuf::default(),
155 lines: vec![vec![]],
156 }
157 }
158
159 fn line(&mut self) -> &mut Vec<char> {
160 self.lines.get_mut(self.row).unwrap()
161 }
162
163 fn load(&mut self, path: PathBuf) -> Result<(), std::io::Error> {
164 let file = File::open(path.clone())?;
165 let converter = |l: Result<String, _>| to_vec(&l.unwrap());
166 self.lines = BufReader::new(file).lines().map(converter).collect();
167 self.path = path;
168 self.row = 0;
169 self.col = 0;
170 Ok(())
171 }
172
173 fn save(&self, path: PathBuf) -> Result<(), std::io::Error> {
174 let mut file = File::create(path.clone())?;
175 for line in self.lines.iter() {
176 writeln!(file, "{}", to_str(line)).unwrap();
177 }
178 Ok(())
179 }
180}
181
182impl Buffer for FileBuffer {
183 fn push(&mut self, c: char) {
184 let (col, row) = (self.col, self.row);
185
186 if c == '\n' {
187 let new_line = self.line().drain(col..).collect();
188 self.lines.insert(row + 1, new_line);
189 self.move_caret(1, -(col as i32));
190 return;
191 }
192
193 self.line().insert(col, c);
194 self.move_caret(0, 1);
195 }
196
197 fn backspace(&mut self) {
198 let (col, row) = (self.col, self.row);
199
200 if col == 0 && row != 0 {
201 let line = self.lines.remove(row);
202 self.move_caret(-1, 0);
203 let len = self.line().len() as i32 - col as i32;
204 self.move_caret(0, len);
205 self.line().extend(line.iter());
206 } else if col != 0 {
207 self.line().remove(col - 1);
208 self.move_caret(0, -1);
209 }
210 }
211
212 fn delete(&mut self) {
213 let (col, row) = (self.col, self.row);
214
215 if col == self.line().len() && row != self.lines.len() - 1 {
216 let line = self.lines.remove(row + 1);
217 self.line().extend(line.iter());
218 } else if col != self.line().len() {
219 self.line().remove(col);
220 }
221 }
222
223 fn move_caret(&mut self, row: i32, col: i32) {
224 let (mut w, mut h) = termion::terminal_size().expect("Unsupported terminal.");
225 if w == 0 { w = 80; } if h == 0 { h = 24; }
227
228 let num_lines = self.lines.len() as i32;
229 self.row = min(max(self.row as i32 + row, 0), num_lines - 1) as usize;
230 if self.row < self.row_offset {
231 self.row_offset = self.row;
232 } else if self.row > self.row_offset + (h as usize - 2) {
233 self.row_offset = self.row - (h as usize - 2);
234 }
235
236 let line_len = self.line().len() as i32;
237 self.col = min(max(self.col as i32 + col, 0), line_len) as usize;
238 if self.col < self.col_offset {
239 self.col_offset = self.col;
240 } else if self.col > self.col_offset + (w as usize - 1) {
241 self.col_offset = self.col - (w as usize - 1);
242 }
243 }
244}
245
246#[derive(Default)]
247struct LineBuffer {
248 col: usize,
249 line: Vec<char>,
250}
251
252impl Buffer for LineBuffer {
253 fn push(&mut self, c: char) {
254 if c != '\n' {
255 self.line.insert(self.col, c);
256 self.move_caret(0, 1);
257 }
258 }
259
260 fn backspace(&mut self) {
261 if self.col != 0 {
262 self.line.remove(self.col - 1);
263 self.move_caret(0, -1);
264 }
265 }
266
267 fn delete(&mut self) {
268 if self.col != self.line.len() {
269 self.line.remove(self.col);
270 }
271 }
272
273 fn move_caret(&mut self, _: i32, col: i32) {
274 let line_len = self.line.len() as i32;
275 self.col = min(max(self.col as i32 + col, 0), line_len) as usize;
276 }
277}
278
279fn main() {
280 unsafe { twz_rt_set_upcall_handler(Some(upcall_handler)) };
281
282 let mut editor = Editor::new();
283
284 let mut args: Vec<String> = std::env::args().collect();
285 if args.len() == 2 {
286 editor.open(PathBuf::from(args.remove(1)));
287 } else if args.len() > 2 {
288 return println!("Error: too many arguments.\nusage: femto [FILE]");
289 }
290
291 let mut stdout = stdout().into_raw_mode().expect("Unsupported terminal.");
292 write!(stdout, "{}", ToAlternateScreen).unwrap();
293
294 loop {
295 if SIGWINCH_RECEIVED.swap(false, Ordering::SeqCst) {
296 }
298 print_screen(&mut stdout, &mut editor);
299 if handle_keys(&mut editor) {
300 break;
301 }
302 }
303
304 write!(stdout, "{}{}", termion::clear::All, Goto(1, 1)).unwrap();
305 stdout.flush().unwrap();
306}
307
308fn print_screen(stdout: &mut Stdout, editor: &mut Editor) {
309 let file_buf = &editor.file_buffer;
310 let (roff, coff) = (file_buf.row_offset, file_buf.col_offset);
311 let (r, c) = (file_buf.row + 1, file_buf.col + 1);
312 let (mut w, mut h) = termion::terminal_size().expect("Unsupported terminal.");
313 if w == 0 { w = 80; } if h == 0 { h = 24; }
315
316 write!(stdout, "{}{}", termion::clear::All, Goto(1, 1)).unwrap();
318
319 for i in roff..(roff + h as usize - 1) {
320 if i < file_buf.lines.len() {
321 let line = file_buf.lines.get(i).unwrap();
323
324 if line.len() < coff {
325 write!(stdout, "\n\r").unwrap();
326 continue;
327 }
328
329 let part = &line[coff..min(coff + w as usize, line.len())];
330 write!(stdout, "{}\n\r", to_str(&Vec::from(part))).unwrap();
331 } else {
332 write!(stdout, "~\n\r").unwrap();
334 }
335 }
336
337 let (prompt, mut cmd, cmd_col) = editor.prompt();
339 let bar_right = format!(" row: {}, col: {}", r, c);
340 let avail = w as usize - bar_right.len() - prompt.len();
341 let cmd_cur_pos = (prompt.len() + cmd_col + 1) as u16;
342 let mut start = 0;
343
344 let mut spacer = String::new();
345 if cmd.len() > avail {
346 start = min(cmd_col, cmd.len() - avail);
347 cmd = cmd[start..(start + avail)].to_string();
348 } else {
349 spacer = " ".repeat(avail - cmd.len());
350 }
351
352 let bar = format!("{}{}{}{}", prompt, cmd, spacer, bar_right);
353 write!(stdout, "{}{}{}", Invert, bar, Reset).unwrap();
354
355 match editor.state {
357 State::Femto => write!(stdout, "{}", Goto((c - coff) as u16, (r - roff) as u16)).unwrap(),
358 _ => write!(stdout, "{}", Goto(cmd_cur_pos - start as u16, h)).unwrap(),
359 }
360 stdout.flush().unwrap();
362
363 editor.message = None;
364}
365
366fn handle_keys(editor: &mut Editor) -> bool {
367 let c = match stdin().keys().next() {
368 Some(Ok(key)) => key,
369 _ => return false, };
371 match c {
372 Key::Char('\t') => for _ in 0..4 { editor.push(' ') },
373 Key::Char(c) => editor.push(c),
374 Key::Ctrl('x') => return true,
375 Key::Ctrl('o') => editor.start_open(),
376 Key::Ctrl('s') => editor.start_save(),
377 Key::Backspace => editor.buffer().backspace(),
378 Key::Delete => editor.buffer().delete(),
379 Key::Esc => editor.exit_command(),
380 Key::Left => editor.buffer().move_caret(0, -1),
381 Key::Right => editor.buffer().move_caret(0, 1),
382 Key::Up => editor.buffer().move_caret(-1, 0),
383 Key::Down => editor.buffer().move_caret(1, 0),
384 Key::Home => editor.buffer().move_caret(0, std::i32::MIN / 2),
385 Key::End => editor.buffer().move_caret(0, std::i32::MAX / 2),
386 _ => {}
387 }
388 false
389}