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::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<ConsoleLineFormatter>,
|
||||
pub struct Editor<T: LineFormatter> {
|
||||
pub buffer: Buffer<T>,
|
||||
pub file_path: Path,
|
||||
pub soft_tabs: bool,
|
||||
pub dirty: bool,
|
||||
|
@ -28,11 +27,11 @@ pub struct Editor {
|
|||
}
|
||||
|
||||
|
||||
impl Editor {
|
||||
impl<T: LineFormatter> Editor<T> {
|
||||
/// Create a new blank editor
|
||||
pub fn new() -> Editor {
|
||||
pub fn new(formatter: T) -> Editor<T> {
|
||||
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<T> {
|
||||
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);
|
||||
|
||||
|
||||
|
|
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 editor::Editor;
|
||||
use self::formatter::GUILineFormatter;
|
||||
|
||||
pub mod formatter;
|
||||
|
||||
pub struct GUI {
|
||||
renderer: Renderer,
|
||||
draw_buf: Texture,
|
||||
font: Font,
|
||||
editor: Editor,
|
||||
editor: Editor<GUILineFormatter>,
|
||||
}
|
||||
|
||||
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<GUILineFormatter>) -> GUI {
|
||||
let font = Font::new_default(14);
|
||||
|
||||
// Get the window and renderer for sdl
|
||||
|
|
27
src/main.rs
27
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();
|
||||
|
|
|
@ -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<ConsoleLineFormatter>,
|
||||
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<ConsoleLineFormatter>) -> 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<ConsoleLineFormatter>, 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<ConsoleLineFormatter>, 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);
|
||||
|
|
13
todo.md
13
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.
|
||||
|
|
Loading…
Reference in New Issue
Block a user