diff --git a/src/editor/mod.rs b/src/editor/mod.rs index e3bf138..c83a8b1 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -3,7 +3,6 @@ use buffer::Buffer; use buffer::line_formatter::LineFormatter; use buffer::line_formatter::RoundingBehavior::*; -use term_ui::formatter::ConsoleLineFormatter; use std::path::Path; use std::cmp::min; use files::{load_file_to_buffer, save_buffer_to_file}; @@ -13,8 +12,8 @@ use self::cursor::CursorSet; mod cursor; -pub struct Editor { - pub buffer: Buffer, +pub struct Editor { + pub buffer: Buffer, pub file_path: Path, pub soft_tabs: bool, pub dirty: bool, @@ -28,11 +27,11 @@ pub struct Editor { } -impl Editor { +impl Editor { /// Create a new blank editor - pub fn new() -> Editor { + pub fn new(formatter: T) -> Editor { Editor { - buffer: Buffer::new(ConsoleLineFormatter::new(4)), + buffer: Buffer::new(formatter), file_path: Path::new(""), soft_tabs: false, dirty: false, @@ -42,10 +41,11 @@ impl Editor { } } - pub fn new_from_file(path: &Path) -> Editor { - let buf = match load_file_to_buffer(path, ConsoleLineFormatter::new(4)) { + pub fn new_from_file(formatter: T, path: &Path) -> Editor { + let buf = match load_file_to_buffer(path, formatter) { Ok(b) => {b}, - _ => {Buffer::new(ConsoleLineFormatter::new(4))} + // TODO: handle un-openable file better + _ => panic!("Could not open file!"), }; let mut ed = Editor { @@ -163,11 +163,13 @@ impl Editor { } } - self.soft_tabs = true; - self.buffer.formatter.tab_width = width as u8; + // TODO + //self.soft_tabs = true; + //self.buffer.formatter.tab_width = width as u8; } else { - self.soft_tabs = false; + // TODO + //self.soft_tabs = false; } } @@ -271,7 +273,9 @@ impl Editor { // Figure out how many spaces to insert let (_, vis_pos) = self.buffer.index_to_v2d(c.range.0); - let next_tab_stop = ((vis_pos / self.buffer.formatter.tab_width as usize) + 1) * self.buffer.formatter.tab_width as usize; + // TODO: handle tab settings + //let next_tab_stop = ((vis_pos / self.buffer.formatter.tab_width as usize) + 1) * self.buffer.formatter.tab_width as usize; + let next_tab_stop = ((vis_pos / 4) + 1) * 4 as usize; let space_count = min(next_tab_stop - vis_pos, 8); diff --git a/src/gui/formatter.rs b/src/gui/formatter.rs new file mode 100644 index 0000000..495d064 --- /dev/null +++ b/src/gui/formatter.rs @@ -0,0 +1,158 @@ +use string_utils::{is_line_ending}; +use buffer::line::{Line, LineGraphemeIter}; +use buffer::line_formatter::{LineFormatter, RoundingBehavior}; + +//=================================================================== +// LineFormatter implementation for terminals/consoles. +//=================================================================== + +pub struct GUILineFormatter { + pub tab_width: u8, +} + + +impl GUILineFormatter { + pub fn new(tab_width: u8) -> GUILineFormatter { + GUILineFormatter { + tab_width: tab_width, + } + } + + + /// Returns the visual cell width of a line + pub fn vis_width(&self, line: &Line) -> usize { + let mut width = 0; + + for g in line.grapheme_iter() { + let w = grapheme_vis_width_at_vis_pos(g, width, self.tab_width as usize); + width += w; + } + + return width; + } + + + pub fn vis_grapheme_iter<'b>(&'b self, line: &'b Line) -> ConsoleLineFormatterVisIter<'b> { + ConsoleLineFormatterVisIter { + grapheme_iter: line.grapheme_iter(), + f: self, + pos: (0, 0), + } + } +} + + +impl<'a> LineFormatter for GUILineFormatter { + fn single_line_height(&self) -> usize { + return 1; + } + + fn dimensions(&self, line: &Line) -> (usize, usize) { + return (1, self.vis_width(line)); + } + + + fn index_to_v2d(&self, line: &Line, index: usize) -> (usize, usize) { + let mut pos = 0; + let mut iter = line.grapheme_iter(); + + for _ in range(0, index) { + if let Some(g) = iter.next() { + let w = grapheme_vis_width_at_vis_pos(g, pos, self.tab_width as usize); + pos += w; + } + else { + panic!("GUILineFormatter::index_to_v2d(): index past end of line."); + } + } + + return (0, pos); + } + + + fn v2d_to_index(&self, line: &Line, v2d: (usize, usize), rounding: (RoundingBehavior, RoundingBehavior)) -> usize { + let mut pos = 0; + let mut i = 0; + let mut iter = line.grapheme_iter(); + + while pos < v2d.1 { + if let Some(g) = iter.next() { + let w = grapheme_vis_width_at_vis_pos(g, pos, self.tab_width as usize); + if (w + pos) > v2d.1 { + let d1 = v2d.1 - pos; + let d2 = (pos + w) - v2d.1; + if d2 < d1 { + i += 1; + } + break; + } + else { + pos += w; + i += 1; + } + } + else { + break; + } + } + + return i; + } +} + + +//=================================================================== +// An iterator that iterates over the graphemes in a line in a +// manner consistent with the ConsoleFormatter. +//=================================================================== +pub struct ConsoleLineFormatterVisIter<'a> { + grapheme_iter: LineGraphemeIter<'a>, + f: &'a GUILineFormatter, + pos: (usize, usize), +} + + + +impl<'a> Iterator for ConsoleLineFormatterVisIter<'a> { + type Item = (&'a str, (usize, usize), usize); + + fn next(&mut self) -> Option<(&'a str, (usize, usize), usize)> { + if let Some(g) = self.grapheme_iter.next() { + let pos = self.pos; + + let width = grapheme_vis_width_at_vis_pos(g, self.pos.1, self.f.tab_width as usize); + self.pos = (self.pos.0, self.pos.1 + width); + + return Some((g, (pos.0, pos.1), width)); + } + else { + return None; + } + } +} + + + +//=================================================================== +// Helper functions +//=================================================================== + +/// Returns the visual width of a grapheme given a starting +/// position on a line. +fn grapheme_vis_width_at_vis_pos(g: &str, pos: usize, tab_width: usize) -> usize { + match g { + "\t" => { + let ending_pos = ((pos / tab_width) + 1) * tab_width; + return ending_pos - pos; + }, + + _ => { + if is_line_ending(g) { + return 1; + } + else { + return g.width(true); + } + } + } +} diff --git a/src/gui/mod.rs b/src/gui/mod.rs index ab56a41..988e790 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -7,13 +7,15 @@ use sdl2::rect::Rect; use font::Font; use editor::Editor; +use self::formatter::GUILineFormatter; +pub mod formatter; pub struct GUI { renderer: Renderer, draw_buf: Texture, font: Font, - editor: Editor, + editor: Editor, } impl GUI { @@ -25,7 +27,7 @@ impl GUI { let renderer = sdl2::render::Renderer::from_window(window, sdl2::render::RenderDriverIndex::Auto, sdl2::render::ACCELERATED).unwrap(); let draw_buf = renderer.create_texture(sdl2::pixels::PixelFormatFlag::RGBA8888, sdl2::render::TextureAccess::Target, 1, 1).unwrap(); - let mut editor = Editor::new(); + let mut editor = Editor::new(GUILineFormatter::new(4)); editor.update_dim(renderer.get_output_size().unwrap().1 as usize, renderer.get_output_size().unwrap().0 as usize); GUI { @@ -37,7 +39,7 @@ impl GUI { } - pub fn new_from_editor(ed: Editor) -> GUI { + pub fn new_from_editor(ed: Editor) -> GUI { let font = Font::new_default(14); // Get the window and renderer for sdl diff --git a/src/main.rs b/src/main.rs index 6151f35..4fed8e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,9 @@ use std::path::Path; use docopt::Docopt; use editor::Editor; use term_ui::TermUI; +use term_ui::formatter::ConsoleLineFormatter; use gui::GUI; +use gui::formatter::GUILineFormatter; mod string_utils; mod buffer; @@ -49,16 +51,17 @@ fn main() { // Get command-line arguments let args: Args = Docopt::new(USAGE).and_then(|d| d.decode()).unwrap_or_else(|e| e.exit()); - // Load file, if specified - let editor = if let Option::Some(s) = args.arg_file { - Editor::new_from_file(&Path::new(s.as_slice())) - } - else { - Editor::new() - }; - + // Initialize and start UI if args.flag_gui { + // Load file, if specified + let editor = if let Option::Some(s) = args.arg_file { + Editor::new_from_file(GUILineFormatter::new(4), &Path::new(s.as_slice())) + } + else { + Editor::new(GUILineFormatter::new(4)) + }; + // GUI sdl2::init(sdl2::INIT_VIDEO); let mut ui = GUI::new_from_editor(editor); @@ -66,6 +69,14 @@ fn main() { sdl2::quit(); } else { + // Load file, if specified + let editor = if let Option::Some(s) = args.arg_file { + Editor::new_from_file(ConsoleLineFormatter::new(4), &Path::new(s.as_slice())) + } + else { + Editor::new(ConsoleLineFormatter::new(4)) + }; + // Console UI let mut ui = TermUI::new_from_editor(editor); ui.main_ui_loop(); diff --git a/src/term_ui/mod.rs b/src/term_ui/mod.rs index a903fd1..f77aace 100644 --- a/src/term_ui/mod.rs +++ b/src/term_ui/mod.rs @@ -8,6 +8,7 @@ use std::time::duration::Duration; use string_utils::{is_line_ending}; use buffer::line::{line_ending_to_str, LineEnding}; use buffer::line_formatter::{LineFormatter, RoundingBehavior}; +use self::formatter::ConsoleLineFormatter; pub mod formatter; @@ -34,7 +35,7 @@ const K_CTRL_Z: u16 = 26; pub struct TermUI { rb: rustbox::RustBox, - editor: Editor, + editor: Editor, width: usize, height: usize, } @@ -45,7 +46,7 @@ impl TermUI { let rb = rustbox::RustBox::init(&[Some(rustbox::InitOption::BufferStderr)]).unwrap(); let w = rb.width(); let h = rb.height(); - let mut editor = Editor::new(); + let mut editor = Editor::new(ConsoleLineFormatter::new(4)); editor.update_dim(h-1, w); TermUI { @@ -56,7 +57,7 @@ impl TermUI { } } - pub fn new_from_editor(ed: Editor) -> TermUI { + pub fn new_from_editor(ed: Editor) -> TermUI { let rb = rustbox::RustBox::init(&[Some(rustbox::InitOption::BufferStderr)]).unwrap(); let w = rb.width(); let h = rb.height(); @@ -288,7 +289,7 @@ impl TermUI { } - fn draw_editor(&self, editor: &Editor, c1: (usize, usize), c2: (usize, usize)) { + fn draw_editor(&self, editor: &Editor, c1: (usize, usize), c2: (usize, usize)) { let foreground = Color::Black; let background = Color::Cyan; @@ -336,7 +337,7 @@ impl TermUI { } - fn draw_editor_text(&self, editor: &Editor, c1: (usize, usize), c2: (usize, usize)) { + fn draw_editor_text(&self, editor: &Editor, c1: (usize, usize), c2: (usize, usize)) { // Calculate all the starting info let editor_corner_index = editor.buffer.v2d_to_index(editor.view_pos, (RoundingBehavior::Floor, RoundingBehavior::Floor)); let (starting_line, _) = editor.buffer.index_to_line_col(editor_corner_index); diff --git a/todo.md b/todo.md index cd68755..100d105 100644 --- a/todo.md +++ b/todo.md @@ -1,14 +1,15 @@ - Clean separation of text buffer from rest of code: - - Create a "BufferFormatter" trait that informs Buffer how its text - should be formatted. Such formatters then determine how the - text buffer graphemes map to a 2d display (e.g. how tabs are handles, - glyph size, spacing, line wrapping, etc.). Buffers then use - BufferFormatters for maintaing information necessary for - 1d <-> 2d operations. + //- Create a "BufferFormatter" trait that informs Buffer how its text + // should be formatted. Such formatters then determine how the + // text buffer graphemes map to a 2d display (e.g. how tabs are handles, + // glyph size, spacing, line wrapping, etc.). Buffers then use + // BufferFormatters for maintaing information necessary for + // 1d <-> 2d operations. - Create BufferFormatters for console and for freetype, including preferences for tab width (specified in spaces) and line wrapping. The freetype formatter should not reference SDL at all, and should have only the freetype library itself as a dependency. + - Handle tab settings properly after the refactor - Possibly split the text buffer out into its own library...? Would likely be useful to other people as well, and would encourage me to keep the API clean.