A bunch more work on refactoring how formatting works.
This compiles, but does not yet work correctly again. Tons of bugs.
This commit is contained in:
parent
8c1778f674
commit
bfeaee602c
|
@ -23,7 +23,7 @@ use self::undo_stack::{Operation::*, UndoStack};
|
||||||
|
|
||||||
/// A text buffer
|
/// A text buffer
|
||||||
pub struct Buffer {
|
pub struct Buffer {
|
||||||
text: Rope,
|
pub text: Rope,
|
||||||
file_path: Option<PathBuf>,
|
file_path: Option<PathBuf>,
|
||||||
undo_stack: UndoStack,
|
undo_stack: UndoStack,
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,8 @@ impl Cursor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_vis_start<T: LineFormatter>(&mut self, buf: &Buffer, f: &T) {
|
pub fn update_vis_start(&mut self, buf: &Buffer, f: &LineFormatter) {
|
||||||
self.vis_start = f.index_to_horizontal_v2d(buf, self.range.0);
|
self.vis_start = f.get_horizontal(buf, self.range.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,16 +11,15 @@ use std::{
|
||||||
use crate::{
|
use crate::{
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
formatter::LineFormatter,
|
formatter::LineFormatter,
|
||||||
formatter::RoundingBehavior::*,
|
|
||||||
string_utils::{char_count, rope_slice_to_line_ending, LineEnding},
|
string_utils::{char_count, rope_slice_to_line_ending, LineEnding},
|
||||||
utils::{digit_count, RopeGraphemes},
|
utils::{digit_count, RopeGraphemes},
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::cursor::CursorSet;
|
use self::cursor::CursorSet;
|
||||||
|
|
||||||
pub struct Editor<T: LineFormatter> {
|
pub struct Editor {
|
||||||
pub buffer: Buffer,
|
pub buffer: Buffer,
|
||||||
pub formatter: T,
|
pub formatter: LineFormatter,
|
||||||
pub file_path: PathBuf,
|
pub file_path: PathBuf,
|
||||||
pub line_ending_type: LineEnding,
|
pub line_ending_type: LineEnding,
|
||||||
pub soft_tabs: bool,
|
pub soft_tabs: bool,
|
||||||
|
@ -39,9 +38,9 @@ pub struct Editor<T: LineFormatter> {
|
||||||
pub cursors: CursorSet,
|
pub cursors: CursorSet,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: LineFormatter> Editor<T> {
|
impl Editor {
|
||||||
/// Create a new blank editor
|
/// Create a new blank editor
|
||||||
pub fn new(formatter: T) -> Editor<T> {
|
pub fn new(formatter: LineFormatter) -> Editor {
|
||||||
Editor {
|
Editor {
|
||||||
buffer: Buffer::new(),
|
buffer: Buffer::new(),
|
||||||
formatter: formatter,
|
formatter: formatter,
|
||||||
|
@ -57,7 +56,7 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_from_file(formatter: T, path: &Path) -> Editor<T> {
|
pub fn new_from_file(formatter: LineFormatter, path: &Path) -> Editor {
|
||||||
let buf = match Buffer::new_from_file(path) {
|
let buf = match Buffer::new_from_file(path) {
|
||||||
Ok(b) => b,
|
Ok(b) => b,
|
||||||
// TODO: handle un-openable file better
|
// TODO: handle un-openable file better
|
||||||
|
@ -307,28 +306,24 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
// the closest cursor.
|
// the closest cursor.
|
||||||
|
|
||||||
// Find the first and last char index visible within the editor.
|
// Find the first and last char index visible within the editor.
|
||||||
let c_first =
|
let c_first = self
|
||||||
|
.formatter
|
||||||
|
.set_horizontal(&self.buffer, self.view_pos.0, 0);
|
||||||
|
let mut c_last =
|
||||||
self.formatter
|
self.formatter
|
||||||
.index_set_horizontal_v2d(&self.buffer, self.view_pos.0, 0, Floor);
|
.offset_vertical(&self.buffer, c_first, self.view_dim.0 as isize);
|
||||||
let mut c_last = self.formatter.index_offset_vertical_v2d(
|
c_last = self
|
||||||
&self.buffer,
|
.formatter
|
||||||
c_first,
|
.set_horizontal(&self.buffer, c_last, self.view_dim.1);
|
||||||
self.view_dim.0 as isize,
|
|
||||||
(Floor, Floor),
|
|
||||||
);
|
|
||||||
c_last =
|
|
||||||
self.formatter
|
|
||||||
.index_set_horizontal_v2d(&self.buffer, c_last, self.view_dim.1, Floor);
|
|
||||||
|
|
||||||
// Adjust the view depending on where the cursor is
|
// Adjust the view depending on where the cursor is
|
||||||
if self.cursors[0].range.0 < c_first {
|
if self.cursors[0].range.0 < c_first {
|
||||||
self.view_pos.0 = self.cursors[0].range.0;
|
self.view_pos.0 = self.cursors[0].range.0;
|
||||||
} else if self.cursors[0].range.0 > c_last {
|
} else if self.cursors[0].range.0 > c_last {
|
||||||
self.view_pos.0 = self.formatter.index_offset_vertical_v2d(
|
self.view_pos.0 = self.formatter.offset_vertical(
|
||||||
&self.buffer,
|
&self.buffer,
|
||||||
self.cursors[0].range.0,
|
self.cursors[0].range.0,
|
||||||
-(self.view_dim.0 as isize),
|
-(self.view_dim.0 as isize),
|
||||||
(Floor, Floor),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -369,9 +364,7 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
c.range.1 += offset;
|
c.range.1 += offset;
|
||||||
|
|
||||||
// Figure out how many spaces to insert
|
// Figure out how many spaces to insert
|
||||||
let vis_pos = self
|
let vis_pos = self.formatter.get_horizontal(&self.buffer, c.range.0);
|
||||||
.formatter
|
|
||||||
.index_to_horizontal_v2d(&self.buffer, c.range.0);
|
|
||||||
// TODO: handle tab settings
|
// TODO: handle tab settings
|
||||||
let next_tab_stop =
|
let next_tab_stop =
|
||||||
((vis_pos / self.soft_tab_width as usize) + 1) * self.soft_tab_width as usize;
|
((vis_pos / self.soft_tab_width as usize) + 1) * self.soft_tab_width as usize;
|
||||||
|
@ -555,18 +548,12 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
for c in self.cursors.iter_mut() {
|
for c in self.cursors.iter_mut() {
|
||||||
let vmove = -1 * n as isize;
|
let vmove = -1 * n as isize;
|
||||||
|
|
||||||
let mut temp_index = self.formatter.index_offset_vertical_v2d(
|
let mut temp_index = self
|
||||||
&self.buffer,
|
.formatter
|
||||||
c.range.0,
|
.offset_vertical(&self.buffer, c.range.0, vmove);
|
||||||
vmove,
|
temp_index = self
|
||||||
(Round, Round),
|
.formatter
|
||||||
);
|
.set_horizontal(&self.buffer, temp_index, c.vis_start);
|
||||||
temp_index = self.formatter.index_set_horizontal_v2d(
|
|
||||||
&self.buffer,
|
|
||||||
temp_index,
|
|
||||||
c.vis_start,
|
|
||||||
Round,
|
|
||||||
);
|
|
||||||
|
|
||||||
if !self.buffer.is_grapheme(temp_index) {
|
if !self.buffer.is_grapheme(temp_index) {
|
||||||
temp_index = self.buffer.nth_prev_grapheme(temp_index, 1);
|
temp_index = self.buffer.nth_prev_grapheme(temp_index, 1);
|
||||||
|
@ -591,18 +578,12 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
for c in self.cursors.iter_mut() {
|
for c in self.cursors.iter_mut() {
|
||||||
let vmove = n as isize;
|
let vmove = n as isize;
|
||||||
|
|
||||||
let mut temp_index = self.formatter.index_offset_vertical_v2d(
|
let mut temp_index = self
|
||||||
&self.buffer,
|
.formatter
|
||||||
c.range.0,
|
.offset_vertical(&self.buffer, c.range.0, vmove);
|
||||||
vmove,
|
temp_index = self
|
||||||
(Round, Round),
|
.formatter
|
||||||
);
|
.set_horizontal(&self.buffer, temp_index, c.vis_start);
|
||||||
temp_index = self.formatter.index_set_horizontal_v2d(
|
|
||||||
&self.buffer,
|
|
||||||
temp_index,
|
|
||||||
c.vis_start,
|
|
||||||
Round,
|
|
||||||
);
|
|
||||||
|
|
||||||
if !self.buffer.is_grapheme(temp_index) {
|
if !self.buffer.is_grapheme(temp_index) {
|
||||||
temp_index = self.buffer.nth_prev_grapheme(temp_index, 1);
|
temp_index = self.buffer.nth_prev_grapheme(temp_index, 1);
|
||||||
|
@ -625,11 +606,10 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
|
|
||||||
pub fn page_up(&mut self) {
|
pub fn page_up(&mut self) {
|
||||||
let move_amount = self.view_dim.0 - max(self.view_dim.0 / 8, 1);
|
let move_amount = self.view_dim.0 - max(self.view_dim.0 / 8, 1);
|
||||||
self.view_pos.0 = self.formatter.index_offset_vertical_v2d(
|
self.view_pos.0 = self.formatter.offset_vertical(
|
||||||
&self.buffer,
|
&self.buffer,
|
||||||
self.view_pos.0,
|
self.view_pos.0,
|
||||||
-1 * move_amount as isize,
|
-1 * move_amount as isize,
|
||||||
(Round, Round),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
self.cursor_up(move_amount);
|
self.cursor_up(move_amount);
|
||||||
|
@ -640,12 +620,9 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
|
|
||||||
pub fn page_down(&mut self) {
|
pub fn page_down(&mut self) {
|
||||||
let move_amount = self.view_dim.0 - max(self.view_dim.0 / 8, 1);
|
let move_amount = self.view_dim.0 - max(self.view_dim.0 / 8, 1);
|
||||||
self.view_pos.0 = self.formatter.index_offset_vertical_v2d(
|
self.view_pos.0 =
|
||||||
&self.buffer,
|
self.formatter
|
||||||
self.view_pos.0,
|
.offset_vertical(&self.buffer, self.view_pos.0, move_amount as isize);
|
||||||
move_amount as isize,
|
|
||||||
(Round, Round),
|
|
||||||
);
|
|
||||||
|
|
||||||
self.cursor_down(move_amount);
|
self.cursor_down(move_amount);
|
||||||
|
|
||||||
|
@ -656,12 +633,9 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
pub fn jump_to_line(&mut self, n: usize) {
|
pub fn jump_to_line(&mut self, n: usize) {
|
||||||
let pos = self.buffer.line_col_to_index((n, 0));
|
let pos = self.buffer.line_col_to_index((n, 0));
|
||||||
self.cursors.truncate(1);
|
self.cursors.truncate(1);
|
||||||
self.cursors[0].range.0 = self.formatter.index_set_horizontal_v2d(
|
self.cursors[0].range.0 =
|
||||||
&self.buffer,
|
self.formatter
|
||||||
pos,
|
.set_horizontal(&self.buffer, pos, self.cursors[0].vis_start);
|
||||||
self.cursors[0].vis_start,
|
|
||||||
Round,
|
|
||||||
);
|
|
||||||
self.cursors[0].range.1 = self.cursors[0].range.0;
|
self.cursors[0].range.1 = self.cursors[0].range.0;
|
||||||
|
|
||||||
// Adjust view
|
// Adjust view
|
||||||
|
|
567
src/formatter.rs
567
src/formatter.rs
|
@ -1,10 +1,12 @@
|
||||||
use std::cmp::min;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use ropey::RopeSlice;
|
use ropey::{Rope, RopeSlice};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
utils::{is_grapheme_boundary, prev_grapheme_boundary, RopeGraphemes},
|
string_utils::char_count,
|
||||||
|
string_utils::{is_line_ending, str_is_whitespace},
|
||||||
|
utils::{grapheme_width, is_grapheme_boundary, prev_grapheme_boundary, RopeGraphemes},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Maximum chars in a line before a soft line break is forced.
|
// Maximum chars in a line before a soft line break is forced.
|
||||||
|
@ -17,176 +19,457 @@ const LINE_BLOCK_LENGTH: usize = 1 << 12;
|
||||||
// breaks.
|
// breaks.
|
||||||
const LINE_BLOCK_FUDGE: usize = 32;
|
const LINE_BLOCK_FUDGE: usize = 32;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
//--------------------------------------------------------------------------
|
||||||
#[derive(Copy, Clone, PartialEq)]
|
|
||||||
pub enum RoundingBehavior {
|
#[derive(Clone)]
|
||||||
Round,
|
pub struct LineFormatter {
|
||||||
Floor,
|
pub tab_width: usize,
|
||||||
Ceiling,
|
pub wrap_width: usize,
|
||||||
|
pub maintain_indent: bool,
|
||||||
|
pub wrap_extra_indent: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait LineFormatter {
|
impl LineFormatter {
|
||||||
/// Returns the 2d visual dimensions of the given text when formatted
|
pub fn new(tab_width: usize) -> LineFormatter {
|
||||||
/// by the formatter.
|
LineFormatter {
|
||||||
/// The text to be formatted is passed as a grapheme iterator.
|
tab_width: tab_width,
|
||||||
fn dimensions(&self, g_iter: RopeGraphemes) -> (usize, usize);
|
wrap_width: 80,
|
||||||
|
maintain_indent: true,
|
||||||
|
wrap_extra_indent: 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Converts a char index within a text into a visual 2d position.
|
/// Returns an iterator over the blocks of the buffer, starting at the
|
||||||
/// The text to be formatted is passed as a grapheme iterator.
|
/// block containing the given char. Also returns the offset of that char
|
||||||
fn index_to_v2d(&self, g_iter: RopeGraphemes, char_idx: usize) -> (usize, usize);
|
/// relative to the start of the first block.
|
||||||
|
pub fn iter<'b>(&'b self, buf: &'b Buffer, char_idx: usize) -> (Blocks<'b>, usize) {
|
||||||
/// Converts a visual 2d position into a char index within a text.
|
// Get the line.
|
||||||
/// The text to be formatted is passed as a grapheme iterator.
|
|
||||||
fn v2d_to_index(
|
|
||||||
&self,
|
|
||||||
g_iter: RopeGraphemes,
|
|
||||||
v2d: (usize, usize),
|
|
||||||
rounding: (RoundingBehavior, RoundingBehavior),
|
|
||||||
) -> usize;
|
|
||||||
|
|
||||||
/// Converts from char index to the horizontal 2d char index.
|
|
||||||
fn index_to_horizontal_v2d(&self, buf: &Buffer, char_idx: usize) -> usize {
|
|
||||||
let (line_i, col_i) = buf.index_to_line_col(char_idx);
|
let (line_i, col_i) = buf.index_to_line_col(char_idx);
|
||||||
let line = buf.get_line(line_i);
|
let line = buf.get_line(line_i);
|
||||||
|
|
||||||
// Find the right block in the line, and the index within that block
|
// Find the right block in the line, and the index within that block
|
||||||
let (_, block_range) = block_index_and_range(&line, col_i);
|
let (block_index, block_range) = block_index_and_range(&line, col_i);
|
||||||
let col_i_adjusted = col_i - block_range.0;
|
let col_i_adjusted = col_i - block_range.0;
|
||||||
|
|
||||||
// Get an iter into the right block
|
(
|
||||||
let g_iter = RopeGraphemes::new(&line.slice(block_range.0..block_range.1));
|
Blocks {
|
||||||
return self.index_to_v2d(g_iter, col_i_adjusted).1;
|
formatter: self,
|
||||||
}
|
buf: &buf.text,
|
||||||
|
line_idx: line_i,
|
||||||
/// Takes a char index and a visual vertical offset, and returns the char
|
line_block_count: block_count(&line),
|
||||||
/// index after that visual offset is applied.
|
block_idx: block_index,
|
||||||
fn index_offset_vertical_v2d(
|
},
|
||||||
&self,
|
|
||||||
buf: &Buffer,
|
|
||||||
char_idx: usize,
|
|
||||||
offset: isize,
|
|
||||||
rounding: (RoundingBehavior, RoundingBehavior),
|
|
||||||
) -> usize {
|
|
||||||
// TODO: handle rounding modes
|
|
||||||
// TODO: do this with bidirectional line iterator
|
|
||||||
|
|
||||||
// Get the line and char index within that line.
|
|
||||||
let (mut line_i, mut col_i) = buf.index_to_line_col(char_idx);
|
|
||||||
let mut line = buf.get_line(line_i);
|
|
||||||
|
|
||||||
// Get the block information for the char offset in the line.
|
|
||||||
let (line_block, block_range) = block_index_and_range(&line, col_i);
|
|
||||||
let col_i_adjusted = col_i - block_range.0;
|
|
||||||
|
|
||||||
// Get the 2d coordinates within the block.
|
|
||||||
let (mut y, x) = self.index_to_v2d(
|
|
||||||
RopeGraphemes::new(&line.slice(block_range.0..block_range.1)),
|
|
||||||
col_i_adjusted,
|
col_i_adjusted,
|
||||||
);
|
)
|
||||||
|
|
||||||
// First, find the right line while keeping track of the vertical offset
|
|
||||||
let mut new_y = y as isize + offset;
|
|
||||||
|
|
||||||
let mut block_index: usize = line_block;
|
|
||||||
loop {
|
|
||||||
line = buf.get_line(line_i);
|
|
||||||
let (block_start, block_end) = char_range_from_block_index(&line, block_index);
|
|
||||||
let (h, _) = self.dimensions(RopeGraphemes::new(&line.slice(block_start..block_end)));
|
|
||||||
|
|
||||||
if new_y >= 0 && new_y < h as isize {
|
|
||||||
y = new_y as usize;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
if new_y > 0 {
|
|
||||||
let is_last_block = block_index >= (block_count(&line) - 1);
|
|
||||||
|
|
||||||
// Check for off-the-end
|
|
||||||
if is_last_block && (line_i + 1) >= buf.line_count() {
|
|
||||||
return buf.char_count();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_last_block {
|
/// Converts from char index to the horizontal 2d char index.
|
||||||
line_i += 1;
|
pub fn get_horizontal(&self, buf: &Buffer, char_idx: usize) -> usize {
|
||||||
block_index = 0;
|
let (_, vis_iter, char_offset) = self.block_vis_iter_and_char_offset(buf, char_idx);
|
||||||
} else {
|
|
||||||
block_index += 1;
|
|
||||||
}
|
|
||||||
new_y -= h as isize;
|
|
||||||
} else if new_y < 0 {
|
|
||||||
// Check for off-the-end
|
|
||||||
if block_index == 0 && line_i == 0 {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if block_index == 0 {
|
// Traverse the iterator and find the horizontal position of the char
|
||||||
line_i -= 1;
|
// index.
|
||||||
line = buf.get_line(line_i);
|
let mut hpos = 0;
|
||||||
block_index = block_count(&line) - 1;
|
let mut i = 0;
|
||||||
} else {
|
let mut last_width = 0;
|
||||||
block_index -= 1;
|
|
||||||
}
|
for (g, pos, width) in vis_iter {
|
||||||
let (block_start, block_end) = char_range_from_block_index(&line, block_index);
|
hpos = pos.1;
|
||||||
let (h, _) =
|
last_width = width;
|
||||||
self.dimensions(RopeGraphemes::new(&line.slice(block_start..block_end)));
|
i += char_count(&g);
|
||||||
new_y += h as isize;
|
|
||||||
} else {
|
if i > char_offset {
|
||||||
unreachable!();
|
return hpos;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, convert the resulting coordinates back into buffer-wide
|
// If we went off the end, calculate the position of the end of the
|
||||||
// coordinates.
|
// block.
|
||||||
let (block_start, block_end) = char_range_from_block_index(&line, block_index);
|
return hpos + last_width;
|
||||||
let block_len = block_end - block_start;
|
|
||||||
let block_slice = line.slice(block_start..block_end);
|
|
||||||
let block_col_i = min(
|
|
||||||
self.v2d_to_index(RopeGraphemes::new(&block_slice), (y, x), rounding),
|
|
||||||
block_len.saturating_sub(1),
|
|
||||||
);
|
|
||||||
col_i = block_start + block_col_i;
|
|
||||||
|
|
||||||
return buf.line_col_to_index((line_i, col_i));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes a char index and a desired visual horizontal position, and
|
/// Takes a char index and a desired visual horizontal position, and
|
||||||
/// returns a char index on the same visual line as the given index,
|
/// returns a char index on the same visual line as the given index,
|
||||||
/// but offset to have the desired horizontal position.
|
/// but offset to have the desired horizontal position (or as close as is
|
||||||
fn index_set_horizontal_v2d(
|
/// possible.
|
||||||
|
pub fn set_horizontal(&self, buf: &Buffer, char_idx: usize, horizontal: usize) -> usize {
|
||||||
|
let (_, vis_iter, char_offset) = self.block_vis_iter_and_char_offset(buf, char_idx);
|
||||||
|
|
||||||
|
let mut hpos_char_idx = None;
|
||||||
|
let mut i = 0;
|
||||||
|
let mut last_i = 0;
|
||||||
|
let mut last_pos = (0, 0);
|
||||||
|
for (g, pos, width) in vis_iter {
|
||||||
|
// Check if we moved to the next line.
|
||||||
|
if pos.0 > last_pos.0 {
|
||||||
|
// If we did, but we're already passed the given char_idx,
|
||||||
|
// that means the target was on the previous line but the line
|
||||||
|
// wasn't long enough, so return the index of the last grapheme
|
||||||
|
// of the previous line.
|
||||||
|
if i > char_offset {
|
||||||
|
return last_i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise reset and keep going.
|
||||||
|
hpos_char_idx = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we found the horizontal position on this line,
|
||||||
|
// and set it if so.
|
||||||
|
if hpos_char_idx == None && horizontal < (pos.1 + width) {
|
||||||
|
hpos_char_idx = Some(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we've found the horizontal position _and_ the passed
|
||||||
|
// char_idx on the same line, and return if so.
|
||||||
|
if i >= char_offset && hpos_char_idx != None {
|
||||||
|
return hpos_char_idx.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
last_pos = pos;
|
||||||
|
last_i = i;
|
||||||
|
i += char_count(&g);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we reached the end of the text, return the last char index.
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes a char index and a visual vertical offset, and returns the char
|
||||||
|
/// index after that visual offset is applied.
|
||||||
|
pub fn offset_vertical(&self, buf: &Buffer, char_idx: usize, v_offset: isize) -> usize {
|
||||||
|
let mut char_idx = char_idx;
|
||||||
|
let mut v_offset = v_offset;
|
||||||
|
while v_offset != 0 {
|
||||||
|
// Get our block and the offset of the char inside it.
|
||||||
|
let (block, block_vis_iter, char_offset) =
|
||||||
|
self.block_vis_iter_and_char_offset(buf, char_idx);
|
||||||
|
|
||||||
|
// Get the vertical size of the block and the vertical
|
||||||
|
// position of the char_idx within it.
|
||||||
|
let block_v_dim = (block_vis_iter.clone().last().unwrap().1).0 + 1;
|
||||||
|
let char_v_pos = block_vis_iter.clone().vpos(char_offset);
|
||||||
|
|
||||||
|
// Get the char's vertical position within the block after offset
|
||||||
|
// by v_offset.
|
||||||
|
let offset_char_v_pos = char_v_pos as isize + v_offset;
|
||||||
|
|
||||||
|
// Check if the offset position is within the block or not,
|
||||||
|
// and handle appropriately.
|
||||||
|
if offset_char_v_pos < 0 {
|
||||||
|
// If we're off the start of the block.
|
||||||
|
if char_idx == 0 {
|
||||||
|
// We reached the start of the whole buffer.
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// Set our variables appropriately for the next iteration.
|
||||||
|
char_idx -= char_offset + 1;
|
||||||
|
v_offset += char_v_pos as isize + 1;
|
||||||
|
}
|
||||||
|
} else if offset_char_v_pos >= block_v_dim as isize {
|
||||||
|
// If we're off the end of the block.
|
||||||
|
if char_idx >= buf.text.len_chars() {
|
||||||
|
// We reached the end of the whole buffer.
|
||||||
|
char_idx = buf.text.len_chars();
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// Set our variables appropriately for the next iteration.
|
||||||
|
char_idx += block.len_chars() - char_offset;
|
||||||
|
v_offset -= block_v_dim as isize - char_v_pos as isize;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the vertical offset is within this block, calculate an
|
||||||
|
// appropriate char index and return.
|
||||||
|
let mut i = 0;
|
||||||
|
for (g, pos, _) in block_vis_iter {
|
||||||
|
if pos.0 == offset_char_v_pos as usize {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i += char_count(&g);
|
||||||
|
}
|
||||||
|
char_idx += block.len_chars() - char_offset + i;
|
||||||
|
v_offset = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return char_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------
|
||||||
|
// Helper methods
|
||||||
|
|
||||||
|
/// Returns the amount of indentation to use for soft-line wrapping
|
||||||
|
/// given the start of a line.
|
||||||
|
fn get_line_indent(&self, line: &RopeSlice) -> usize {
|
||||||
|
if !self.maintain_indent {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut indent = 0;
|
||||||
|
for c in line.chars() {
|
||||||
|
match c {
|
||||||
|
' ' => {
|
||||||
|
indent += 1;
|
||||||
|
}
|
||||||
|
'\t' => {
|
||||||
|
indent = tab_stop_from_vis_pos(indent, self.tab_width);
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the indent is too long for the wrap width, do no indentation.
|
||||||
|
if (indent + self.wrap_extra_indent + 2) > self.wrap_width {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
indent
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the appropriate BlockVisIter containing the given char, and the
|
||||||
|
/// char's offset within that iter.
|
||||||
|
fn block_vis_iter_and_char_offset<'b>(
|
||||||
&self,
|
&self,
|
||||||
buf: &Buffer,
|
buf: &'b Buffer,
|
||||||
char_idx: usize,
|
char_idx: usize,
|
||||||
horizontal: usize,
|
) -> (RopeSlice<'b>, BlockVisIter<'b>, usize) {
|
||||||
rounding: RoundingBehavior,
|
|
||||||
) -> usize {
|
|
||||||
// Get the line info.
|
|
||||||
let (line_i, col_i) = buf.index_to_line_col(char_idx);
|
let (line_i, col_i) = buf.index_to_line_col(char_idx);
|
||||||
let line = buf.get_line(line_i);
|
let line = buf.get_line(line_i);
|
||||||
|
|
||||||
// Get the right block within the line.
|
// Find the right block in the line, and the index within that block
|
||||||
let (block_i, block_range) = block_index_and_range(&line, col_i);
|
let (block_index, block_range) = block_index_and_range(&line, col_i);
|
||||||
let col_i_adjusted = col_i - block_range.0;
|
let col_i_adjusted = col_i - block_range.0;
|
||||||
|
|
||||||
// Calculate the horizontal position.
|
// Get the right block and an iter into it.
|
||||||
let (v, _) = self.index_to_v2d(
|
let block = line.slice(block_range.0..block_range.1);
|
||||||
RopeGraphemes::new(&line.slice(block_range.0..block_range.1)),
|
let g_iter = RopeGraphemes::new(&block);
|
||||||
col_i_adjusted,
|
|
||||||
);
|
|
||||||
let block_col_i = self.v2d_to_index(
|
|
||||||
RopeGraphemes::new(&line.slice(block_range.0..block_range.1)),
|
|
||||||
(v, horizontal),
|
|
||||||
(RoundingBehavior::Floor, rounding),
|
|
||||||
);
|
|
||||||
let new_col_i = if (line_i + 1) < buf.line_count() || (block_i + 1) < block_count(&line) {
|
|
||||||
min(block_range.0 + block_col_i, block_range.1.saturating_sub(1))
|
|
||||||
} else {
|
|
||||||
min(block_range.0 + block_col_i, block_range.1)
|
|
||||||
};
|
|
||||||
|
|
||||||
return (char_idx + new_col_i) - col_i;
|
// Get an appropriate visual block iter.
|
||||||
|
let vis_iter = BlockVisIter::new(
|
||||||
|
g_iter,
|
||||||
|
self.wrap_width,
|
||||||
|
self.tab_width,
|
||||||
|
block_index == 0,
|
||||||
|
if block_index == 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
self.get_line_indent(&line)
|
||||||
|
},
|
||||||
|
self.wrap_extra_indent,
|
||||||
|
);
|
||||||
|
|
||||||
|
(block, vis_iter, col_i_adjusted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Blocks<'a> {
|
||||||
|
formatter: &'a LineFormatter,
|
||||||
|
buf: &'a Rope,
|
||||||
|
line_idx: usize,
|
||||||
|
line_block_count: usize,
|
||||||
|
block_idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for Blocks<'a> {
|
||||||
|
type Item = (BlockVisIter<'a>, bool);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
// Check if we're done already.
|
||||||
|
if self.line_idx >= self.buf.len_lines() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get our return values.
|
||||||
|
let (iter, is_line_start) = {
|
||||||
|
let line = self.buf.line(self.line_idx);
|
||||||
|
let (start, end) = char_range_from_block_index(&line, self.block_idx);
|
||||||
|
let block = line.slice(start..end);
|
||||||
|
let iter = BlockVisIter::new(
|
||||||
|
RopeGraphemes::new(&block),
|
||||||
|
self.formatter.wrap_width,
|
||||||
|
self.formatter.tab_width,
|
||||||
|
self.block_idx == 0,
|
||||||
|
if self.block_idx == 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
self.formatter.get_line_indent(&line)
|
||||||
|
},
|
||||||
|
self.formatter.wrap_extra_indent,
|
||||||
|
);
|
||||||
|
|
||||||
|
(iter, self.block_idx == 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Progress the values of the iterator.
|
||||||
|
self.block_idx += 1;
|
||||||
|
if self.block_idx >= self.line_block_count {
|
||||||
|
self.line_idx += 1;
|
||||||
|
self.block_idx = 0;
|
||||||
|
if self.line_idx < self.buf.len_lines() {
|
||||||
|
self.line_block_count = block_count(&self.buf.line(self.line_idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return.
|
||||||
|
Some((iter, is_line_start))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// An iterator over the visual printable characters of a block of text,
|
||||||
|
/// yielding the text of the character, its position in 2d space, and its
|
||||||
|
/// visial width.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BlockVisIter<'a> {
|
||||||
|
grapheme_itr: RopeGraphemes<'a>,
|
||||||
|
|
||||||
|
wrap_width: usize,
|
||||||
|
tab_width: usize,
|
||||||
|
indent: usize, // Size of soft indent to use.
|
||||||
|
wrap_extra_indent: usize, // Additional amount to indent soft-wrapped lines.
|
||||||
|
finding_indent: bool,
|
||||||
|
|
||||||
|
word_buf: Vec<(Cow<'a, str>, usize)>, // Printable character and its width.
|
||||||
|
word_i: usize,
|
||||||
|
pos: (usize, usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BlockVisIter<'a> {
|
||||||
|
fn new(
|
||||||
|
grapheme_itr: RopeGraphemes<'a>,
|
||||||
|
wrap_width: usize,
|
||||||
|
tab_width: usize,
|
||||||
|
find_indent: bool,
|
||||||
|
starting_indent: usize,
|
||||||
|
wrap_extra_indent: usize,
|
||||||
|
) -> BlockVisIter<'a> {
|
||||||
|
BlockVisIter {
|
||||||
|
grapheme_itr: grapheme_itr,
|
||||||
|
wrap_width: wrap_width,
|
||||||
|
tab_width: tab_width,
|
||||||
|
indent: starting_indent,
|
||||||
|
wrap_extra_indent: wrap_extra_indent,
|
||||||
|
finding_indent: find_indent,
|
||||||
|
|
||||||
|
word_buf: Vec::new(),
|
||||||
|
word_i: 0,
|
||||||
|
pos: (0, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vpos(&mut self, char_offset: usize) -> usize {
|
||||||
|
let mut vpos = 0;
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
for (g, pos, _) in self {
|
||||||
|
vpos = pos.0;
|
||||||
|
i += char_count(&g);
|
||||||
|
|
||||||
|
if i > char_offset {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vpos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for BlockVisIter<'a> {
|
||||||
|
type Item = (Cow<'a, str>, (usize, usize), usize);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.pos == (0, 0) {
|
||||||
|
self.pos = (0, self.indent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get next word if necessary
|
||||||
|
if self.word_i >= self.word_buf.len() {
|
||||||
|
let mut word_width = 0;
|
||||||
|
self.word_buf.truncate(0);
|
||||||
|
|
||||||
|
while let Some(g) = self.grapheme_itr.next().map(|g| Cow::<str>::from(g)) {
|
||||||
|
let width =
|
||||||
|
grapheme_vis_width_at_vis_pos(&g, self.pos.1 + word_width, self.tab_width);
|
||||||
|
self.word_buf.push((g.clone(), width));
|
||||||
|
word_width += width;
|
||||||
|
|
||||||
|
if str_is_whitespace(&g) {
|
||||||
|
if self.finding_indent && (g.as_bytes()[0] == 0x09 || g.as_bytes()[0] == 0x20) {
|
||||||
|
if (self.indent + self.wrap_extra_indent + width + 2) > self.wrap_width {
|
||||||
|
// Cancel indentation if it's too long for the screen.
|
||||||
|
self.indent = 0;
|
||||||
|
self.wrap_extra_indent = 0;
|
||||||
|
self.finding_indent = false;
|
||||||
|
} else {
|
||||||
|
self.indent += width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
self.finding_indent = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.word_buf.len() == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to next line if necessary
|
||||||
|
if (self.pos.1 + word_width) > self.wrap_width && (self.pos.1 > self.indent) {
|
||||||
|
if self.pos.1 > 0 {
|
||||||
|
self.pos = (self.pos.0 + 1, self.indent + self.wrap_extra_indent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.word_i = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get next grapheme and width from the current word.
|
||||||
|
let (g, g_width) = {
|
||||||
|
let (ref g, mut width) = self.word_buf[self.word_i];
|
||||||
|
if g == "\t" {
|
||||||
|
width = grapheme_vis_width_at_vis_pos(&g, self.pos.1, self.tab_width);
|
||||||
|
}
|
||||||
|
(g, width)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get our character's position and update the position for the next
|
||||||
|
// grapheme.
|
||||||
|
if (self.pos.1 + g_width) > self.wrap_width && self.pos.1 > 0 {
|
||||||
|
self.pos.0 += 1;
|
||||||
|
self.pos.1 = self.indent + self.wrap_extra_indent;
|
||||||
|
}
|
||||||
|
let pos = self.pos;
|
||||||
|
self.pos.1 += g_width;
|
||||||
|
|
||||||
|
// Increment index and return.
|
||||||
|
self.word_i += 1;
|
||||||
|
return Some((g.clone(), pos, g_width));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
if g == "\t" {
|
||||||
|
return tab_stop_from_vis_pos(pos, tab_width) - pos;
|
||||||
|
} else if is_line_ending(g) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return grapheme_width(&g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tab_stop_from_vis_pos(pos: usize, tab_width: usize) -> usize {
|
||||||
|
((pos / tab_width) + 1) * tab_width
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
// Finds the best break at or before the given char index, bounded by
|
// Finds the best break at or before the given char index, bounded by
|
||||||
// the given `lower_limit`.
|
// the given `lower_limit`.
|
||||||
pub fn find_good_break(slice: &RopeSlice, lower_limit: usize, char_idx: usize) -> usize {
|
pub fn find_good_break(slice: &RopeSlice, lower_limit: usize, char_idx: usize) -> usize {
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::{io::Write, path::Path};
|
||||||
|
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use term_ui::formatter::ConsoleLineFormatter;
|
use formatter::LineFormatter;
|
||||||
use term_ui::TermUI;
|
use term_ui::TermUI;
|
||||||
|
|
||||||
mod buffer;
|
mod buffer;
|
||||||
|
@ -29,9 +29,9 @@ fn main() {
|
||||||
|
|
||||||
// Load file, if specified
|
// Load file, if specified
|
||||||
let editor = if let Some(filepath) = args.value_of("file") {
|
let editor = if let Some(filepath) = args.value_of("file") {
|
||||||
Editor::new_from_file(ConsoleLineFormatter::new(4), &Path::new(&filepath[..]))
|
Editor::new_from_file(LineFormatter::new(4), &Path::new(&filepath[..]))
|
||||||
} else {
|
} else {
|
||||||
Editor::new(ConsoleLineFormatter::new(4))
|
Editor::new(LineFormatter::new(4))
|
||||||
};
|
};
|
||||||
|
|
||||||
// Holds stderr output in an internal buffer, and prints it when dropped.
|
// Holds stderr output in an internal buffer, and prints it when dropped.
|
||||||
|
|
|
@ -10,28 +10,36 @@ pub fn is_line_ending(text: &str) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_whitespace(text: &str) -> bool {
|
pub fn str_is_whitespace(text: &str) -> bool {
|
||||||
|
if let Some(c) = text.chars().nth(0) {
|
||||||
|
is_whitespace(c)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_whitespace(c: char) -> bool {
|
||||||
// TODO: this is a naive categorization of whitespace characters.
|
// TODO: this is a naive categorization of whitespace characters.
|
||||||
// For better categorization these should be split up into groups
|
// For better categorization these should be split up into groups
|
||||||
// based on e.g. breaking vs non-breaking spaces, among other things.
|
// based on e.g. breaking vs non-breaking spaces, among other things.
|
||||||
match text.chars().nth(0) {
|
match c {
|
||||||
//Some('\u{1680}') | // OGHAM SPACE MARK (here for completeness, but usually displayed as a dash, not as whitespace)
|
//'\u{1680}' | // OGHAM SPACE MARK (here for completeness, but usually displayed as a dash, not as whitespace)
|
||||||
Some('\u{0009}') | // CHARACTER TABULATION
|
'\u{0009}' | // CHARACTER TABULATION
|
||||||
Some('\u{0020}') | // SPACE
|
'\u{0020}' | // SPACE
|
||||||
Some('\u{00A0}') | // NO-BREAK SPACE
|
'\u{00A0}' | // NO-BREAK SPACE
|
||||||
Some('\u{180E}') | // MONGOLIAN VOWEL SEPARATOR
|
'\u{180E}' | // MONGOLIAN VOWEL SEPARATOR
|
||||||
Some('\u{202F}') | // NARROW NO-BREAK SPACE
|
'\u{202F}' | // NARROW NO-BREAK SPACE
|
||||||
Some('\u{205F}') | // MEDIUM MATHEMATICAL SPACE
|
'\u{205F}' | // MEDIUM MATHEMATICAL SPACE
|
||||||
Some('\u{3000}') | // IDEOGRAPHIC SPACE
|
'\u{3000}' | // IDEOGRAPHIC SPACE
|
||||||
Some('\u{FEFF}') // ZERO WIDTH NO-BREAK SPACE
|
'\u{FEFF}' // ZERO WIDTH NO-BREAK SPACE
|
||||||
=> true,
|
=> true,
|
||||||
|
|
||||||
// EN QUAD, EM QUAD, EN SPACE, EM SPACE, THREE-PER-EM SPACE,
|
// EN QUAD, EM QUAD, EN SPACE, EM SPACE, THREE-PER-EM SPACE,
|
||||||
// FOUR-PER-EM SPACE, SIX-PER-EM SPACE, FIGURE SPACE,
|
// FOUR-PER-EM SPACE, SIX-PER-EM SPACE, FIGURE SPACE,
|
||||||
// PUNCTUATION SPACE, THIN SPACE, HAIR SPACE, ZERO WIDTH SPACE.
|
// PUNCTUATION SPACE, THIN SPACE, HAIR SPACE, ZERO WIDTH SPACE.
|
||||||
Some(c) if c >= '\u{2000}' && c <= '\u{200B}' => true,
|
c if c >= '\u{2000}' && c <= '\u{200B}' => true,
|
||||||
|
|
||||||
// None, or not a matching whitespace character.
|
// Not a matching whitespace character.
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,9 +116,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn char_count_1() {
|
fn char_count_1() {
|
||||||
|
let text_0 = "";
|
||||||
let text_1 = "Hello world!";
|
let text_1 = "Hello world!";
|
||||||
let text_2 = "今日はみんなさん!";
|
let text_2 = "今日はみんなさん!";
|
||||||
|
|
||||||
|
assert_eq!(0, char_count(text_0));
|
||||||
assert_eq!(12, char_count(text_1));
|
assert_eq!(12, char_count(text_1));
|
||||||
assert_eq!(9, char_count(text_2));
|
assert_eq!(9, char_count(text_2));
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -13,15 +13,12 @@ use crossterm::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
editor::Editor,
|
editor::Editor,
|
||||||
formatter::{block_count, block_index_and_range, char_range_from_block_index, LineFormatter},
|
formatter::LineFormatter,
|
||||||
string_utils::{is_line_ending, line_ending_to_str, LineEnding},
|
string_utils::{char_count, is_line_ending, line_ending_to_str, LineEnding},
|
||||||
utils::{digit_count, RopeGraphemes, Timer},
|
utils::{digit_count, Timer},
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::{
|
use self::screen::{Screen, Style};
|
||||||
formatter::ConsoleLineFormatter,
|
|
||||||
screen::{Screen, Style},
|
|
||||||
};
|
|
||||||
|
|
||||||
const EMPTY_MOD: KeyModifiers = KeyModifiers::empty();
|
const EMPTY_MOD: KeyModifiers = KeyModifiers::empty();
|
||||||
const UPDATE_TICK_MS: u64 = 10;
|
const UPDATE_TICK_MS: u64 = 10;
|
||||||
|
@ -159,10 +156,7 @@ macro_rules! ui_loop {
|
||||||
$term_ui
|
$term_ui
|
||||||
.editor
|
.editor
|
||||||
.update_dim($term_ui.height - 1, $term_ui.width);
|
.update_dim($term_ui.height - 1, $term_ui.width);
|
||||||
$term_ui
|
$term_ui.editor.formatter.wrap_width = $term_ui.editor.view_dim.1;
|
||||||
.editor
|
|
||||||
.formatter
|
|
||||||
.set_wrap_width($term_ui.editor.view_dim.1);
|
|
||||||
|
|
||||||
// Draw!
|
// Draw!
|
||||||
{
|
{
|
||||||
|
@ -176,7 +170,7 @@ macro_rules! ui_loop {
|
||||||
|
|
||||||
pub struct TermUI {
|
pub struct TermUI {
|
||||||
screen: Screen,
|
screen: Screen,
|
||||||
editor: Editor<ConsoleLineFormatter>,
|
editor: Editor,
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
quit: bool,
|
quit: bool,
|
||||||
|
@ -190,10 +184,10 @@ enum LoopStatus {
|
||||||
|
|
||||||
impl TermUI {
|
impl TermUI {
|
||||||
pub fn new() -> TermUI {
|
pub fn new() -> TermUI {
|
||||||
TermUI::new_from_editor(Editor::new(ConsoleLineFormatter::new(4)))
|
TermUI::new_from_editor(Editor::new(LineFormatter::new(4)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_from_editor(ed: Editor<ConsoleLineFormatter>) -> TermUI {
|
pub fn new_from_editor(ed: Editor) -> TermUI {
|
||||||
let (w, h) = crossterm::terminal::size().unwrap();
|
let (w, h) = crossterm::terminal::size().unwrap();
|
||||||
let mut editor = ed;
|
let mut editor = ed;
|
||||||
editor.update_dim(h as usize - 1, w as usize);
|
editor.update_dim(h as usize - 1, w as usize);
|
||||||
|
@ -216,7 +210,7 @@ impl TermUI {
|
||||||
self.width = w as usize;
|
self.width = w as usize;
|
||||||
self.height = h as usize;
|
self.height = h as usize;
|
||||||
self.editor.update_dim(self.height - 1, self.width);
|
self.editor.update_dim(self.height - 1, self.width);
|
||||||
self.editor.formatter.set_wrap_width(self.editor.view_dim.1);
|
self.editor.formatter.wrap_width = self.editor.view_dim.1;
|
||||||
self.screen.resize(w as usize, h as usize);
|
self.screen.resize(w as usize, h as usize);
|
||||||
|
|
||||||
// Start the UI
|
// Start the UI
|
||||||
|
@ -444,12 +438,7 @@ impl TermUI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_editor(
|
fn draw_editor(&self, editor: &Editor, c1: (usize, usize), c2: (usize, usize)) {
|
||||||
&self,
|
|
||||||
editor: &Editor<ConsoleLineFormatter>,
|
|
||||||
c1: (usize, usize),
|
|
||||||
c2: (usize, usize),
|
|
||||||
) {
|
|
||||||
// Fill in top row with info line color
|
// Fill in top row with info line color
|
||||||
for i in c1.1..(c2.1 + 1) {
|
for i in c1.1..(c2.1 + 1) {
|
||||||
self.screen.draw(i, c1.0, " ", STYLE_INFO);
|
self.screen.draw(i, c1.0, " ", STYLE_INFO);
|
||||||
|
@ -502,23 +491,20 @@ impl TermUI {
|
||||||
self.draw_editor_text(editor, (c1.0 + 1, c1.1), c2);
|
self.draw_editor_text(editor, (c1.0 + 1, c1.1), c2);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_editor_text(
|
fn draw_editor_text(&self, editor: &Editor, c1: (usize, usize), c2: (usize, usize)) {
|
||||||
&self,
|
|
||||||
editor: &Editor<ConsoleLineFormatter>,
|
|
||||||
c1: (usize, usize),
|
|
||||||
c2: (usize, usize),
|
|
||||||
) {
|
|
||||||
// Calculate all the starting info
|
// Calculate all the starting info
|
||||||
let gutter_width = editor.editor_dim.1 - editor.view_dim.1;
|
let gutter_width = editor.editor_dim.1 - editor.view_dim.1;
|
||||||
let blank_gutter = &" "[..gutter_width - 1];
|
let blank_gutter = &" "[..gutter_width - 1];
|
||||||
let (line_index, col_i) = editor.buffer.index_to_line_col(editor.view_pos.0);
|
let line_index = editor.buffer.text.char_to_line(editor.view_pos.0);
|
||||||
let temp_line = editor.buffer.get_line(line_index);
|
|
||||||
let (mut line_block_index, block_range) = block_index_and_range(&temp_line, col_i);
|
let (blocks_iter, char_offset) = editor.formatter.iter(&editor.buffer, editor.view_pos.0);
|
||||||
let mut char_index = editor.buffer.line_col_to_index((line_index, block_range.0));
|
|
||||||
let (vis_line_offset, _) = editor.formatter.index_to_v2d(
|
let vis_line_offset = blocks_iter
|
||||||
RopeGraphemes::new(&temp_line.slice(block_range.0..block_range.1)),
|
.clone()
|
||||||
editor.view_pos.0 - char_index,
|
.next()
|
||||||
);
|
.unwrap()
|
||||||
|
.0
|
||||||
|
.vpos(editor.view_pos.0);
|
||||||
|
|
||||||
let mut screen_line = c1.0 as isize - vis_line_offset as isize;
|
let mut screen_line = c1.0 as isize - vis_line_offset as isize;
|
||||||
let screen_col = c1.1 as isize + gutter_width as isize;
|
let screen_col = c1.1 as isize + gutter_width as isize;
|
||||||
|
@ -528,8 +514,16 @@ impl TermUI {
|
||||||
self.screen.draw(c1.1, y, blank_gutter, STYLE_GUTTER_ODD);
|
self.screen.draw(c1.1, y, blank_gutter, STYLE_GUTTER_ODD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loop through the blocks, printing them to the screen.
|
||||||
|
let mut is_first_loop = true;
|
||||||
let mut line_num = line_index + 1;
|
let mut line_num = line_index + 1;
|
||||||
for line in editor.buffer.line_iter_at_index(line_index) {
|
let mut char_index = editor.view_pos.0 - char_offset;
|
||||||
|
for (block_vis_iter, is_line_start) in blocks_iter {
|
||||||
|
if is_line_start && !is_first_loop {
|
||||||
|
line_num += 1;
|
||||||
|
}
|
||||||
|
is_first_loop = false;
|
||||||
|
|
||||||
let gutter_style = if (line_num % 2) == 0 {
|
let gutter_style = if (line_num % 2) == 0 {
|
||||||
STYLE_GUTTER_EVEN
|
STYLE_GUTTER_EVEN
|
||||||
} else {
|
} else {
|
||||||
|
@ -537,7 +531,7 @@ impl TermUI {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Print line number
|
// Print line number
|
||||||
if line_block_index == 0 {
|
if is_line_start {
|
||||||
let lnx = c1.1;
|
let lnx = c1.1;
|
||||||
let lny = screen_line as usize;
|
let lny = screen_line as usize;
|
||||||
if lny >= c1.0 && lny <= c2.0 {
|
if lny >= c1.0 && lny <= c2.0 {
|
||||||
|
@ -555,40 +549,23 @@ impl TermUI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop through the graphemes of the line and print them to
|
// Loop through the graphemes of the block and print them to
|
||||||
// the screen.
|
// the screen.
|
||||||
let max_block_index = block_count(&line) - 1;
|
|
||||||
let block_range = char_range_from_block_index(&line, line_block_index);
|
|
||||||
let mut g_iter = editor.formatter.iter(RopeGraphemes::new(
|
|
||||||
&line.slice(block_range.0..block_range.1),
|
|
||||||
));
|
|
||||||
|
|
||||||
let mut last_pos_y = 0;
|
let mut last_pos_y = 0;
|
||||||
let mut lines_traversed: usize = 0;
|
for (g, (pos_y, pos_x), width) in block_vis_iter {
|
||||||
loop {
|
// Calculate the cell coordinates at which to draw the grapheme
|
||||||
for (g, (pos_y, pos_x), width) in g_iter {
|
if pos_y > last_pos_y {
|
||||||
let do_gutter =
|
screen_line += 1;
|
||||||
last_pos_y != pos_y || (lines_traversed == 0 && line_block_index != 0);
|
|
||||||
if last_pos_y != pos_y {
|
|
||||||
if last_pos_y < pos_y {
|
|
||||||
lines_traversed += pos_y - last_pos_y;
|
|
||||||
}
|
|
||||||
last_pos_y = pos_y;
|
last_pos_y = pos_y;
|
||||||
}
|
}
|
||||||
// Calculate the cell coordinates at which to draw the grapheme
|
|
||||||
let px = pos_x as isize + screen_col - editor.view_pos.1 as isize;
|
let px = pos_x as isize + screen_col - editor.view_pos.1 as isize;
|
||||||
let py = lines_traversed as isize + screen_line;
|
let py = screen_line;
|
||||||
|
|
||||||
// If we're off the bottom, we're done
|
// If we're off the bottom, we're done
|
||||||
if py > c2.0 as isize {
|
if py > c2.0 as isize {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if do_gutter {
|
|
||||||
self.screen
|
|
||||||
.draw(c1.1, py as usize, blank_gutter, gutter_style);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the grapheme to the screen if it's in bounds
|
// Draw the grapheme to the screen if it's in bounds
|
||||||
if (px >= c1.1 as isize) && (py >= c1.0 as isize) && (px <= c2.1 as isize) {
|
if (px >= c1.1 as isize) && (py >= c1.0 as isize) && (px <= c2.1 as isize) {
|
||||||
// Check if the character is within a cursor
|
// Check if the character is within a cursor
|
||||||
|
@ -627,25 +604,8 @@ impl TermUI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char_index += g.chars().count();
|
char_index += char_count(&g);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move on to the next block.
|
|
||||||
line_block_index += 1;
|
|
||||||
if line_block_index <= max_block_index {
|
|
||||||
let block_range = char_range_from_block_index(&line, line_block_index);
|
|
||||||
g_iter = editor.formatter.iter(RopeGraphemes::new(
|
|
||||||
&line.slice(block_range.0..block_range.1),
|
|
||||||
));
|
|
||||||
lines_traversed += 1;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
line_block_index = 0;
|
|
||||||
screen_line += lines_traversed as isize + 1;
|
|
||||||
line_num += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we get here, it means we reached the end of the text buffer
|
// If we get here, it means we reached the end of the text buffer
|
||||||
|
@ -664,7 +624,7 @@ impl TermUI {
|
||||||
// Calculate the cell coordinates at which to draw the cursor
|
// Calculate the cell coordinates at which to draw the cursor
|
||||||
let pos_x = editor
|
let pos_x = editor
|
||||||
.formatter
|
.formatter
|
||||||
.index_to_horizontal_v2d(&self.editor.buffer, self.editor.buffer.char_count());
|
.get_horizontal(&self.editor.buffer, self.editor.buffer.char_count());
|
||||||
let mut px = pos_x as isize + screen_col - editor.view_pos.1 as isize;
|
let mut px = pos_x as isize + screen_col - editor.view_pos.1 as isize;
|
||||||
let mut py = screen_line - 1;
|
let mut py = screen_line - 1;
|
||||||
if px > c2.1 as isize {
|
if px > c2.1 as isize {
|
||||||
|
|
|
@ -122,6 +122,7 @@ pub fn is_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An iterator over the graphemes of a RopeSlice.
|
/// An iterator over the graphemes of a RopeSlice.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct RopeGraphemes<'a> {
|
pub struct RopeGraphemes<'a> {
|
||||||
text: RopeSlice<'a>,
|
text: RopeSlice<'a>,
|
||||||
chunks: Chunks<'a>,
|
chunks: Chunks<'a>,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user