281 lines
10 KiB
Rust
281 lines
10 KiB
Rust
#![allow(dead_code)]
|
|
|
|
use rustbox;
|
|
use rustbox::Color;
|
|
use editor::Editor;
|
|
use std::char;
|
|
use std::time::duration::Duration;
|
|
use string_utils::{is_line_ending};
|
|
|
|
// Key codes
|
|
const K_ENTER: u16 = 13;
|
|
const K_TAB: u16 = 9;
|
|
const K_SPACE: u16 = 32;
|
|
const K_BACKSPACE: u16 = 127;
|
|
const K_DELETE: u16 = 65522;
|
|
const K_PAGEUP: u16 = 65519;
|
|
const K_PAGEDOWN: u16 = 65518;
|
|
const K_UP: u16 = 65517;
|
|
const K_DOWN: u16 = 65516;
|
|
const K_LEFT: u16 = 65515;
|
|
const K_RIGHT: u16 = 65514;
|
|
const K_ESC: u16 = 27;
|
|
const K_CTRL_Q: u16 = 17;
|
|
const K_CTRL_S: u16 = 19;
|
|
|
|
|
|
pub struct TermUI {
|
|
rb: rustbox::RustBox,
|
|
editor: Editor,
|
|
}
|
|
|
|
|
|
impl TermUI {
|
|
pub fn new() -> TermUI {
|
|
TermUI {
|
|
rb: rustbox::RustBox::init(&[Some(rustbox::InitOption::BufferStderr)]).unwrap(),
|
|
editor: Editor::new(),
|
|
}
|
|
}
|
|
|
|
pub fn new_from_editor(editor: Editor) -> TermUI {
|
|
TermUI {
|
|
rb: rustbox::RustBox::init(&[Some(rustbox::InitOption::BufferStderr)]).unwrap(),
|
|
editor: editor,
|
|
}
|
|
}
|
|
|
|
pub fn ui_loop(&mut self) {
|
|
// Quitting flag
|
|
let mut quit = false;
|
|
|
|
let mut width = self.rb.width();
|
|
let mut height = self.rb.height();
|
|
self.editor.update_dim(height, width);
|
|
|
|
loop {
|
|
// Draw the editor to screen
|
|
self.rb.clear();
|
|
self.draw_editor(&self.editor, (0, 0), (height-1, width-1));
|
|
self.rb.present();
|
|
|
|
|
|
// Handle events. We block on the first event, so that the
|
|
// program doesn't loop like crazy, but then continue pulling
|
|
// events in a non-blocking way until we run out of events
|
|
// to handle.
|
|
let mut e = self.rb.poll_event(); // Block until we get an event
|
|
loop {
|
|
match e {
|
|
Ok(rustbox::Event::KeyEvent(modifier, key, character)) => {
|
|
//println!(" {} {} {}", modifier, key, character);
|
|
match key {
|
|
K_CTRL_Q | K_ESC => {
|
|
quit = true;
|
|
break;
|
|
},
|
|
|
|
K_CTRL_S => {
|
|
self.editor.save_if_dirty();
|
|
},
|
|
|
|
K_PAGEUP => {
|
|
self.editor.page_up();
|
|
},
|
|
|
|
K_PAGEDOWN => {
|
|
self.editor.page_down();
|
|
},
|
|
|
|
K_UP => {
|
|
self.editor.cursor_up(1);
|
|
},
|
|
|
|
K_DOWN => {
|
|
self.editor.cursor_down(1);
|
|
},
|
|
|
|
K_LEFT => {
|
|
self.editor.cursor_left(1);
|
|
},
|
|
|
|
K_RIGHT => {
|
|
self.editor.cursor_right(1);
|
|
},
|
|
|
|
K_ENTER => {
|
|
self.editor.insert_text_at_cursor("\n");
|
|
},
|
|
|
|
K_SPACE => {
|
|
self.editor.insert_text_at_cursor(" ");
|
|
},
|
|
|
|
K_TAB => {
|
|
self.editor.insert_text_at_cursor("\t");
|
|
},
|
|
|
|
K_BACKSPACE => {
|
|
self.editor.remove_text_behind_cursor(1);
|
|
},
|
|
|
|
K_DELETE => {
|
|
self.editor.remove_text_in_front_of_cursor(1);
|
|
},
|
|
|
|
// Character
|
|
0 => {
|
|
if let Option::Some(c) = char::from_u32(character) {
|
|
self.editor.insert_text_at_cursor(c.to_string().as_slice());
|
|
}
|
|
},
|
|
|
|
_ => {}
|
|
}
|
|
},
|
|
|
|
Ok(rustbox::Event::ResizeEvent(w, h)) => {
|
|
width = w as uint;
|
|
height = h as uint;
|
|
self.editor.update_dim(height, width);
|
|
}
|
|
|
|
_ => {
|
|
break;
|
|
}
|
|
}
|
|
|
|
e = self.rb.peek_event(Duration::milliseconds(0)); // Get next event (if any)
|
|
}
|
|
|
|
|
|
// Quit if quit flag is set
|
|
if quit {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
pub fn draw_editor(&self, editor: &Editor, c1: (uint, uint), c2: (uint, uint)) {
|
|
let foreground = Color::Black;
|
|
let background = Color::Cyan;
|
|
|
|
// Fill in top row with info line color
|
|
for i in range(c1.1, c2.1 + 1) {
|
|
self.rb.print(i, c1.0, rustbox::RB_NORMAL, foreground, background, " ");
|
|
}
|
|
|
|
// Filename and dirty marker
|
|
let filename = editor.file_path.display();
|
|
let dirty_char = if editor.dirty {"*"} else {""};
|
|
let name = format!("{}{}", filename, dirty_char);
|
|
self.rb.print(c1.1 + 1, c1.0, rustbox::RB_NORMAL, foreground, background, name.as_slice());
|
|
|
|
// Percentage position in document
|
|
let percentage: uint = if editor.buffer.len() > 0 {
|
|
(((editor.cursor.range.0 as f32) / (editor.buffer.len() as f32)) * 100.0) as uint
|
|
}
|
|
else {
|
|
100
|
|
};
|
|
let pstring = format!("{}%", percentage);
|
|
self.rb.print(c2.1 - pstring.len(), c1.0, rustbox::RB_NORMAL, foreground, background, pstring.as_slice());
|
|
|
|
// Text encoding info and tab style
|
|
let info_line = format!("UTF8:LF tabs:4");
|
|
self.rb.print(c2.1 - 30, c1.0, rustbox::RB_NORMAL, foreground, background, info_line.as_slice());
|
|
|
|
// Draw main text editing area
|
|
self.draw_editor_text(editor, (c1.0 + 1, c1.1), c2);
|
|
}
|
|
|
|
|
|
pub fn draw_editor_text(&self, editor: &Editor, c1: (uint, uint), c2: (uint, uint)) {
|
|
let mut line_iter = editor.buffer.line_iter_at_index(editor.view_pos.0);
|
|
|
|
let mut grapheme_index;
|
|
|
|
let mut vis_line_num = editor.view_pos.0;
|
|
let mut vis_col_num = editor.view_pos.1;
|
|
|
|
let mut print_line_num = c1.0;
|
|
let mut print_col_num = c1.1;
|
|
|
|
let max_print_line = c2.0 - c1.0;
|
|
let max_print_col = c2.1 - c1.1;
|
|
|
|
loop {
|
|
if let Some(line) = line_iter.next() {
|
|
let mut g_iter = line.grapheme_vis_iter();
|
|
let excess = g_iter.skip_vis_positions(editor.view_pos.1);
|
|
|
|
vis_col_num += excess;
|
|
print_col_num += excess;
|
|
|
|
grapheme_index = editor.buffer.pos_vis_2d_to_closest_1d((vis_line_num, vis_col_num));
|
|
|
|
for (g, pos, width) in g_iter {
|
|
print_col_num = pos - editor.view_pos.1;
|
|
|
|
if is_line_ending(g) {
|
|
if grapheme_index == editor.cursor.range.0 {
|
|
self.rb.print(print_col_num, print_line_num, rustbox::RB_NORMAL, Color::Black, Color::White, " ");
|
|
}
|
|
}
|
|
else if g == "\t" {
|
|
for i in range(print_col_num, print_col_num + width) {
|
|
self.rb.print(i, print_line_num, rustbox::RB_NORMAL, Color::White, Color::Black, " ");
|
|
}
|
|
|
|
if grapheme_index == editor.cursor.range.0 {
|
|
self.rb.print(print_col_num, print_line_num, rustbox::RB_NORMAL, Color::Black, Color::White, " ");
|
|
}
|
|
}
|
|
else {
|
|
if grapheme_index == editor.cursor.range.0 {
|
|
self.rb.print(print_col_num, print_line_num, rustbox::RB_NORMAL, Color::Black, Color::White, g);
|
|
}
|
|
else {
|
|
self.rb.print(print_col_num, print_line_num, rustbox::RB_NORMAL, Color::White, Color::Black, g);
|
|
}
|
|
}
|
|
|
|
vis_col_num += width;
|
|
grapheme_index += 1;
|
|
print_col_num += width;
|
|
|
|
if print_col_num > max_print_col {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
|
|
vis_line_num += 1;
|
|
print_line_num += 1;
|
|
vis_col_num = editor.view_pos.1;
|
|
|
|
if print_line_num > max_print_line {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Print cursor if it's at the end of the text, and thus wasn't printed
|
|
// already.
|
|
if editor.cursor.range.0 >= editor.buffer.len() {
|
|
let vis_cursor_pos = editor.buffer.pos_1d_to_closest_vis_2d(editor.cursor.range.0);
|
|
if (vis_cursor_pos.0 >= editor.view_pos.0) && (vis_cursor_pos.1 >= editor.view_pos.1) {
|
|
let print_cursor_pos = (vis_cursor_pos.0 - editor.view_pos.0 + c1.0, vis_cursor_pos.1 - editor.view_pos.1 + c1.1);
|
|
|
|
if print_cursor_pos.0 >= c1.0 && print_cursor_pos.0 <= c2.0 && print_cursor_pos.1 >= c1.1 && print_cursor_pos.1 <= c2.1 {
|
|
self.rb.print(print_cursor_pos.1, print_cursor_pos.0, rustbox::RB_NORMAL, Color::Black, Color::White, " ");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
} |