WIP: moving GUI code over to use LineFormatter.
This commit is contained in:
parent
8d83fe77d2
commit
109e46a027
|
@ -3,7 +3,6 @@
|
||||||
use buffer::Buffer;
|
use buffer::Buffer;
|
||||||
use buffer::line_formatter::LineFormatter;
|
use buffer::line_formatter::LineFormatter;
|
||||||
use buffer::line_formatter::RoundingBehavior::*;
|
use buffer::line_formatter::RoundingBehavior::*;
|
||||||
use term_ui::formatter::ConsoleLineFormatter;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use files::{load_file_to_buffer, save_buffer_to_file};
|
use files::{load_file_to_buffer, save_buffer_to_file};
|
||||||
|
@ -13,8 +12,8 @@ use self::cursor::CursorSet;
|
||||||
mod cursor;
|
mod cursor;
|
||||||
|
|
||||||
|
|
||||||
pub struct Editor {
|
pub struct Editor<T: LineFormatter> {
|
||||||
pub buffer: Buffer<ConsoleLineFormatter>,
|
pub buffer: Buffer<T>,
|
||||||
pub file_path: Path,
|
pub file_path: Path,
|
||||||
pub soft_tabs: bool,
|
pub soft_tabs: bool,
|
||||||
pub dirty: bool,
|
pub dirty: bool,
|
||||||
|
@ -28,11 +27,11 @@ pub struct Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Editor {
|
impl<T: LineFormatter> Editor<T> {
|
||||||
/// Create a new blank editor
|
/// Create a new blank editor
|
||||||
pub fn new() -> Editor {
|
pub fn new(formatter: T) -> Editor<T> {
|
||||||
Editor {
|
Editor {
|
||||||
buffer: Buffer::new(ConsoleLineFormatter::new(4)),
|
buffer: Buffer::new(formatter),
|
||||||
file_path: Path::new(""),
|
file_path: Path::new(""),
|
||||||
soft_tabs: false,
|
soft_tabs: false,
|
||||||
dirty: false,
|
dirty: false,
|
||||||
|
@ -42,10 +41,11 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_from_file(path: &Path) -> Editor {
|
pub fn new_from_file(formatter: T, path: &Path) -> Editor<T> {
|
||||||
let buf = match load_file_to_buffer(path, ConsoleLineFormatter::new(4)) {
|
let buf = match load_file_to_buffer(path, formatter) {
|
||||||
Ok(b) => {b},
|
Ok(b) => {b},
|
||||||
_ => {Buffer::new(ConsoleLineFormatter::new(4))}
|
// TODO: handle un-openable file better
|
||||||
|
_ => panic!("Could not open file!"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut ed = Editor {
|
let mut ed = Editor {
|
||||||
|
@ -163,11 +163,13 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.soft_tabs = true;
|
// TODO
|
||||||
self.buffer.formatter.tab_width = width as u8;
|
//self.soft_tabs = true;
|
||||||
|
//self.buffer.formatter.tab_width = width as u8;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
self.soft_tabs = false;
|
// TODO
|
||||||
|
//self.soft_tabs = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,7 +273,9 @@ impl Editor {
|
||||||
|
|
||||||
// Figure out how many spaces to insert
|
// Figure out how many spaces to insert
|
||||||
let (_, vis_pos) = self.buffer.index_to_v2d(c.range.0);
|
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);
|
let space_count = min(next_tab_stop - vis_pos, 8);
|
||||||
|
|
||||||
|
|
||||||
|
|
158
src/gui/formatter.rs
Normal file
158
src/gui/formatter.rs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,13 +7,15 @@ use sdl2::rect::Rect;
|
||||||
|
|
||||||
use font::Font;
|
use font::Font;
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
|
use self::formatter::GUILineFormatter;
|
||||||
|
|
||||||
|
pub mod formatter;
|
||||||
|
|
||||||
pub struct GUI {
|
pub struct GUI {
|
||||||
renderer: Renderer,
|
renderer: Renderer,
|
||||||
draw_buf: Texture,
|
draw_buf: Texture,
|
||||||
font: Font,
|
font: Font,
|
||||||
editor: Editor,
|
editor: Editor<GUILineFormatter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GUI {
|
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 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 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);
|
editor.update_dim(renderer.get_output_size().unwrap().1 as usize, renderer.get_output_size().unwrap().0 as usize);
|
||||||
|
|
||||||
GUI {
|
GUI {
|
||||||
|
@ -37,7 +39,7 @@ impl GUI {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn new_from_editor(ed: Editor) -> GUI {
|
pub fn new_from_editor(ed: Editor<GUILineFormatter>) -> GUI {
|
||||||
let font = Font::new_default(14);
|
let font = Font::new_default(14);
|
||||||
|
|
||||||
// Get the window and renderer for sdl
|
// Get the window and renderer for sdl
|
||||||
|
|
25
src/main.rs
25
src/main.rs
|
@ -10,7 +10,9 @@ use std::path::Path;
|
||||||
use docopt::Docopt;
|
use docopt::Docopt;
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use term_ui::TermUI;
|
use term_ui::TermUI;
|
||||||
|
use term_ui::formatter::ConsoleLineFormatter;
|
||||||
use gui::GUI;
|
use gui::GUI;
|
||||||
|
use gui::formatter::GUILineFormatter;
|
||||||
|
|
||||||
mod string_utils;
|
mod string_utils;
|
||||||
mod buffer;
|
mod buffer;
|
||||||
|
@ -49,16 +51,17 @@ fn main() {
|
||||||
// Get command-line arguments
|
// Get command-line arguments
|
||||||
let args: Args = Docopt::new(USAGE).and_then(|d| d.decode()).unwrap_or_else(|e| e.exit());
|
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
|
// Initialize and start UI
|
||||||
if args.flag_gui {
|
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
|
// GUI
|
||||||
sdl2::init(sdl2::INIT_VIDEO);
|
sdl2::init(sdl2::INIT_VIDEO);
|
||||||
let mut ui = GUI::new_from_editor(editor);
|
let mut ui = GUI::new_from_editor(editor);
|
||||||
|
@ -66,6 +69,14 @@ fn main() {
|
||||||
sdl2::quit();
|
sdl2::quit();
|
||||||
}
|
}
|
||||||
else {
|
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
|
// Console UI
|
||||||
let mut ui = TermUI::new_from_editor(editor);
|
let mut ui = TermUI::new_from_editor(editor);
|
||||||
ui.main_ui_loop();
|
ui.main_ui_loop();
|
||||||
|
|
|
@ -8,6 +8,7 @@ use std::time::duration::Duration;
|
||||||
use string_utils::{is_line_ending};
|
use string_utils::{is_line_ending};
|
||||||
use buffer::line::{line_ending_to_str, LineEnding};
|
use buffer::line::{line_ending_to_str, LineEnding};
|
||||||
use buffer::line_formatter::{LineFormatter, RoundingBehavior};
|
use buffer::line_formatter::{LineFormatter, RoundingBehavior};
|
||||||
|
use self::formatter::ConsoleLineFormatter;
|
||||||
|
|
||||||
pub mod formatter;
|
pub mod formatter;
|
||||||
|
|
||||||
|
@ -34,7 +35,7 @@ const K_CTRL_Z: u16 = 26;
|
||||||
|
|
||||||
pub struct TermUI {
|
pub struct TermUI {
|
||||||
rb: rustbox::RustBox,
|
rb: rustbox::RustBox,
|
||||||
editor: Editor,
|
editor: Editor<ConsoleLineFormatter>,
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
}
|
}
|
||||||
|
@ -45,7 +46,7 @@ impl TermUI {
|
||||||
let rb = rustbox::RustBox::init(&[Some(rustbox::InitOption::BufferStderr)]).unwrap();
|
let rb = rustbox::RustBox::init(&[Some(rustbox::InitOption::BufferStderr)]).unwrap();
|
||||||
let w = rb.width();
|
let w = rb.width();
|
||||||
let h = rb.height();
|
let h = rb.height();
|
||||||
let mut editor = Editor::new();
|
let mut editor = Editor::new(ConsoleLineFormatter::new(4));
|
||||||
editor.update_dim(h-1, w);
|
editor.update_dim(h-1, w);
|
||||||
|
|
||||||
TermUI {
|
TermUI {
|
||||||
|
@ -56,7 +57,7 @@ impl TermUI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_from_editor(ed: Editor) -> TermUI {
|
pub fn new_from_editor(ed: Editor<ConsoleLineFormatter>) -> TermUI {
|
||||||
let rb = rustbox::RustBox::init(&[Some(rustbox::InitOption::BufferStderr)]).unwrap();
|
let rb = rustbox::RustBox::init(&[Some(rustbox::InitOption::BufferStderr)]).unwrap();
|
||||||
let w = rb.width();
|
let w = rb.width();
|
||||||
let h = rb.height();
|
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<ConsoleLineFormatter>, c1: (usize, usize), c2: (usize, usize)) {
|
||||||
let foreground = Color::Black;
|
let foreground = Color::Black;
|
||||||
let background = Color::Cyan;
|
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<ConsoleLineFormatter>, c1: (usize, usize), c2: (usize, usize)) {
|
||||||
// Calculate all the starting info
|
// Calculate all the starting info
|
||||||
let editor_corner_index = editor.buffer.v2d_to_index(editor.view_pos, (RoundingBehavior::Floor, RoundingBehavior::Floor));
|
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);
|
let (starting_line, _) = editor.buffer.index_to_line_col(editor_corner_index);
|
||||||
|
|
13
todo.md
13
todo.md
|
@ -1,14 +1,15 @@
|
||||||
- Clean separation of text buffer from rest of code:
|
- Clean separation of text buffer from rest of code:
|
||||||
- Create a "BufferFormatter" trait that informs Buffer how its text
|
//- Create a "BufferFormatter" trait that informs Buffer how its text
|
||||||
should be formatted. Such formatters then determine how the
|
// should be formatted. Such formatters then determine how the
|
||||||
text buffer graphemes map to a 2d display (e.g. how tabs are handles,
|
// text buffer graphemes map to a 2d display (e.g. how tabs are handles,
|
||||||
glyph size, spacing, line wrapping, etc.). Buffers then use
|
// glyph size, spacing, line wrapping, etc.). Buffers then use
|
||||||
BufferFormatters for maintaing information necessary for
|
// BufferFormatters for maintaing information necessary for
|
||||||
1d <-> 2d operations.
|
// 1d <-> 2d operations.
|
||||||
- Create BufferFormatters for console and for freetype, including
|
- Create BufferFormatters for console and for freetype, including
|
||||||
preferences for tab width (specified in spaces) and line wrapping.
|
preferences for tab width (specified in spaces) and line wrapping.
|
||||||
The freetype formatter should not reference SDL at all, and should
|
The freetype formatter should not reference SDL at all, and should
|
||||||
have only the freetype library itself as a dependency.
|
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
|
- Possibly split the text buffer out into its own library...? Would
|
||||||
likely be useful to other people as well, and would encourage me to
|
likely be useful to other people as well, and would encourage me to
|
||||||
keep the API clean.
|
keep the API clean.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user