femto/
main.rs

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
8// Twizzler Runtime Window Change Signal Handling
9use 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; } // Default terminal size to prevent overflow (80x24)
226        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            // Signal was caught, terminal size changed!
297        }
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; } // Default terminal size to prevent overflow (80x24)
314    if h == 0 { h = 24; }
315
316    // Clear and start writing from origin
317    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            // Content
322            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            // ~ as filler for parts of the window that are outside the buffer
333            write!(stdout, "~\n\r").unwrap();
334        }
335    }
336
337    // Status bar
338    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    // Draw cursor on the right place
356    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    // Ensure everything visible
361    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, // Ignore interrupted reads or EOF
370    };
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}