Reworking the text formatting code.
It's a mess of indirection and over-abstraction, and this commit is the first step at cleaning that up. In addition to making the code easier to follow, it's also notably faster. The only downside is we've (temporarily) lost indentation continuation on line wrapping. But that can be added back without too much trouble later.
This commit is contained in:
parent
c3bfdf7a7f
commit
19adb08170
|
@ -29,26 +29,20 @@ pub trait LineFormatter {
|
||||||
/// Returns the 2d visual dimensions of the given text when formatted
|
/// Returns the 2d visual dimensions of the given text when formatted
|
||||||
/// by the formatter.
|
/// by the formatter.
|
||||||
/// The text to be formatted is passed as a grapheme iterator.
|
/// The text to be formatted is passed as a grapheme iterator.
|
||||||
fn dimensions<'a, T>(&'a self, g_iter: T) -> (usize, usize)
|
fn dimensions(&self, g_iter: RopeGraphemes) -> (usize, usize);
|
||||||
where
|
|
||||||
T: Iterator<Item = RopeSlice<'a>>;
|
|
||||||
|
|
||||||
/// Converts a char index within a text into a visual 2d position.
|
/// Converts a char index within a text into a visual 2d position.
|
||||||
/// The text to be formatted is passed as a grapheme iterator.
|
/// The text to be formatted is passed as a grapheme iterator.
|
||||||
fn index_to_v2d<'a, T>(&'a self, g_iter: T, char_idx: usize) -> (usize, usize)
|
fn index_to_v2d(&self, g_iter: RopeGraphemes, char_idx: usize) -> (usize, usize);
|
||||||
where
|
|
||||||
T: Iterator<Item = RopeSlice<'a>>;
|
|
||||||
|
|
||||||
/// Converts a visual 2d position into a char index within a text.
|
/// Converts a visual 2d position into a char index within a text.
|
||||||
/// The text to be formatted is passed as a grapheme iterator.
|
/// The text to be formatted is passed as a grapheme iterator.
|
||||||
fn v2d_to_index<'a, T>(
|
fn v2d_to_index(
|
||||||
&'a self,
|
&self,
|
||||||
g_iter: T,
|
g_iter: RopeGraphemes,
|
||||||
v2d: (usize, usize),
|
v2d: (usize, usize),
|
||||||
rounding: (RoundingBehavior, RoundingBehavior),
|
rounding: (RoundingBehavior, RoundingBehavior),
|
||||||
) -> usize
|
) -> usize;
|
||||||
where
|
|
||||||
T: Iterator<Item = RopeSlice<'a>>;
|
|
||||||
|
|
||||||
/// Converts from char index to the horizontal 2d char index.
|
/// Converts from char index to the horizontal 2d char index.
|
||||||
fn index_to_horizontal_v2d(&self, buf: &Buffer, char_idx: usize) -> usize {
|
fn index_to_horizontal_v2d(&self, buf: &Buffer, char_idx: usize) -> usize {
|
||||||
|
|
|
@ -16,7 +16,11 @@ pub fn is_line_ending(text: &str) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rope_slice_is_line_ending(text: &RopeSlice) -> bool {
|
pub fn rope_slice_is_line_ending(text: &RopeSlice) -> bool {
|
||||||
rope_slice_to_line_ending(text) != LineEnding::None
|
match text.char(0) {
|
||||||
|
c if (c >= '\u{000A}' && c <= '\u{000D}') => true,
|
||||||
|
'\u{0085}' | '\u{2028}' | '\u{2029}' => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_whitespace(text: &str) -> bool {
|
pub fn is_whitespace(text: &str) -> bool {
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
use std::cmp::max;
|
use std::{borrow::Cow, cmp::max};
|
||||||
|
|
||||||
use ropey::RopeSlice;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
formatter::{LineFormatter, RoundingBehavior},
|
formatter::{LineFormatter, RoundingBehavior},
|
||||||
string_utils::{rope_slice_is_line_ending, rope_slice_is_whitespace},
|
string_utils::{is_line_ending, is_whitespace},
|
||||||
utils::grapheme_width,
|
utils::{grapheme_width, RopeGraphemes},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub enum WrapType {
|
pub enum WrapType {
|
||||||
|
@ -49,27 +47,24 @@ impl ConsoleLineFormatter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter<'a, T>(&'a self, g_iter: T) -> ConsoleLineFormatterVisIter<'a, T>
|
pub fn iter<'a>(&self, g_iter: RopeGraphemes<'a>) -> FormattingIter<'a> {
|
||||||
where
|
FormattingIter {
|
||||||
T: Iterator<Item = RopeSlice<'a>>,
|
grapheme_itr: g_iter,
|
||||||
{
|
wrap_width: match self.wrap_type {
|
||||||
ConsoleLineFormatterVisIter::<'a, T> {
|
WrapType::WordWrap(w) => w,
|
||||||
grapheme_iter: g_iter,
|
WrapType::CharWrap(w) => w,
|
||||||
f: self,
|
WrapType::NoWrap => unreachable!(),
|
||||||
pos: (0, 0),
|
},
|
||||||
indent: 0,
|
tab_width: self.tab_width as usize,
|
||||||
indent_found: false,
|
|
||||||
word_buf: Vec::new(),
|
word_buf: Vec::new(),
|
||||||
word_i: 0,
|
word_i: 0,
|
||||||
|
pos: (0, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LineFormatter for ConsoleLineFormatter {
|
impl LineFormatter for ConsoleLineFormatter {
|
||||||
fn dimensions<'a, T>(&'a self, g_iter: T) -> (usize, usize)
|
fn dimensions(&self, g_iter: RopeGraphemes) -> (usize, usize) {
|
||||||
where
|
|
||||||
T: Iterator<Item = RopeSlice<'a>>,
|
|
||||||
{
|
|
||||||
let mut dim: (usize, usize) = (0, 0);
|
let mut dim: (usize, usize) = (0, 0);
|
||||||
|
|
||||||
for (_, pos, width) in self.iter(g_iter) {
|
for (_, pos, width) in self.iter(g_iter) {
|
||||||
|
@ -81,10 +76,7 @@ impl LineFormatter for ConsoleLineFormatter {
|
||||||
return dim;
|
return dim;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn index_to_v2d<'a, T>(&'a self, g_iter: T, char_idx: usize) -> (usize, usize)
|
fn index_to_v2d(&self, g_iter: RopeGraphemes, char_idx: usize) -> (usize, usize) {
|
||||||
where
|
|
||||||
T: Iterator<Item = RopeSlice<'a>>,
|
|
||||||
{
|
|
||||||
let mut pos = (0, 0);
|
let mut pos = (0, 0);
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
let mut last_width = 0;
|
let mut last_width = 0;
|
||||||
|
@ -102,15 +94,12 @@ impl LineFormatter for ConsoleLineFormatter {
|
||||||
return (pos.0, pos.1 + last_width);
|
return (pos.0, pos.1 + last_width);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn v2d_to_index<'a, T>(
|
fn v2d_to_index(
|
||||||
&'a self,
|
&self,
|
||||||
g_iter: T,
|
g_iter: RopeGraphemes,
|
||||||
v2d: (usize, usize),
|
v2d: (usize, usize),
|
||||||
_: (RoundingBehavior, RoundingBehavior),
|
_: (RoundingBehavior, RoundingBehavior),
|
||||||
) -> usize
|
) -> usize {
|
||||||
where
|
|
||||||
T: Iterator<Item = RopeSlice<'a>>,
|
|
||||||
{
|
|
||||||
// TODO: handle rounding modes
|
// TODO: handle rounding modes
|
||||||
let mut prev_i = 0;
|
let mut prev_i = 0;
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
|
@ -131,172 +120,97 @@ impl LineFormatter for ConsoleLineFormatter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================================================================
|
//--------------------------------------------------------------------------
|
||||||
// An iterator that iterates over the graphemes in a line in a
|
|
||||||
// manner consistent with the ConsoleFormatter.
|
|
||||||
// ===================================================================
|
|
||||||
pub struct ConsoleLineFormatterVisIter<'a, T>
|
|
||||||
where
|
|
||||||
T: Iterator<Item = RopeSlice<'a>>,
|
|
||||||
{
|
|
||||||
grapheme_iter: T,
|
|
||||||
f: &'a ConsoleLineFormatter,
|
|
||||||
pos: (usize, usize),
|
|
||||||
|
|
||||||
indent: usize,
|
/// An iterator over the visual printable characters of a piece of text,
|
||||||
indent_found: bool,
|
/// yielding the text of the character, its position in 2d space, and its
|
||||||
|
/// visial width.
|
||||||
|
///
|
||||||
|
/// TODO: handle maintaining indent, etc.
|
||||||
|
pub struct FormattingIter<'a> {
|
||||||
|
grapheme_itr: RopeGraphemes<'a>,
|
||||||
|
wrap_width: usize,
|
||||||
|
tab_width: usize,
|
||||||
|
|
||||||
word_buf: Vec<RopeSlice<'a>>,
|
word_buf: Vec<(Cow<'a, str>, usize)>, // Printable character and its width.
|
||||||
word_i: usize,
|
word_i: usize,
|
||||||
|
|
||||||
|
pos: (usize, usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> ConsoleLineFormatterVisIter<'a, T>
|
impl<'a> Iterator for FormattingIter<'a> {
|
||||||
where
|
type Item = (Cow<'a, str>, (usize, usize), usize);
|
||||||
T: Iterator<Item = RopeSlice<'a>>,
|
|
||||||
{
|
|
||||||
fn next_nowrap(&mut self, g: RopeSlice<'a>) -> Option<(RopeSlice<'a>, (usize, usize), usize)> {
|
|
||||||
let width = grapheme_vis_width_at_vis_pos(g, self.pos.1, self.f.tab_width as usize);
|
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
// 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 is_whitespace(&g) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.word_buf.len() == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to next line if necessary
|
||||||
|
if (self.pos.1 + word_width) > self.wrap_width {
|
||||||
|
if self.pos.1 > 0 {
|
||||||
|
self.pos = (self.pos.0 + 1, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = 0;
|
||||||
|
}
|
||||||
let pos = self.pos;
|
let pos = self.pos;
|
||||||
self.pos = (self.pos.0, self.pos.1 + width);
|
self.pos.1 += g_width;
|
||||||
return Some((g, pos, width));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_charwrap(
|
// Increment index and return.
|
||||||
&mut self,
|
self.word_i += 1;
|
||||||
g: RopeSlice<'a>,
|
return Some((g.clone(), pos, g_width));
|
||||||
wrap_width: usize,
|
|
||||||
) -> Option<(RopeSlice<'a>, (usize, usize), usize)> {
|
|
||||||
let width = grapheme_vis_width_at_vis_pos(g, self.pos.1, self.f.tab_width as usize);
|
|
||||||
|
|
||||||
if (self.pos.1 + width) > wrap_width {
|
|
||||||
if !self.indent_found {
|
|
||||||
self.indent = 0;
|
|
||||||
self.indent_found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.f.maintain_indent {
|
|
||||||
let pos = (self.pos.0 + 1, self.indent + self.f.wrap_additional_indent);
|
|
||||||
self.pos = (
|
|
||||||
self.pos.0 + 1,
|
|
||||||
self.indent + self.f.wrap_additional_indent + width,
|
|
||||||
);
|
|
||||||
return Some((g, pos, width));
|
|
||||||
} else {
|
|
||||||
let pos = (self.pos.0 + 1, self.f.wrap_additional_indent);
|
|
||||||
self.pos = (self.pos.0 + 1, self.f.wrap_additional_indent + width);
|
|
||||||
return Some((g, pos, width));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !self.indent_found {
|
|
||||||
if rope_slice_is_whitespace(&g) {
|
|
||||||
self.indent += width;
|
|
||||||
} else {
|
|
||||||
self.indent_found = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let pos = self.pos;
|
|
||||||
self.pos = (self.pos.0, self.pos.1 + width);
|
|
||||||
return Some((g, pos, width));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> Iterator for ConsoleLineFormatterVisIter<'a, T>
|
|
||||||
where
|
|
||||||
T: Iterator<Item = RopeSlice<'a>>,
|
|
||||||
{
|
|
||||||
type Item = (RopeSlice<'a>, (usize, usize), usize);
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<(RopeSlice<'a>, (usize, usize), usize)> {
|
|
||||||
match self.f.wrap_type {
|
|
||||||
WrapType::NoWrap => {
|
|
||||||
if let Some(g) = self.grapheme_iter.next() {
|
|
||||||
return self.next_nowrap(g);
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WrapType::CharWrap(wrap_width) => {
|
|
||||||
if let Some(g) = self.grapheme_iter.next() {
|
|
||||||
return self.next_charwrap(g, wrap_width);
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WrapType::WordWrap(wrap_width) => {
|
|
||||||
// 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_iter.next() {
|
|
||||||
self.word_buf.push(g);
|
|
||||||
let width = grapheme_vis_width_at_vis_pos(
|
|
||||||
g,
|
|
||||||
self.pos.1 + word_width,
|
|
||||||
self.f.tab_width as usize,
|
|
||||||
);
|
|
||||||
word_width += width;
|
|
||||||
if rope_slice_is_whitespace(&g) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.word_buf.len() == 0 {
|
|
||||||
return None;
|
|
||||||
} else if !self.indent_found && !rope_slice_is_whitespace(&self.word_buf[0]) {
|
|
||||||
self.indent_found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move to next line if necessary
|
|
||||||
if (self.pos.1 + word_width) > wrap_width {
|
|
||||||
if !self.indent_found {
|
|
||||||
self.indent = 0;
|
|
||||||
self.indent_found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.pos.1 > 0 {
|
|
||||||
if self.f.maintain_indent {
|
|
||||||
self.pos =
|
|
||||||
(self.pos.0 + 1, self.indent + self.f.wrap_additional_indent);
|
|
||||||
} else {
|
|
||||||
self.pos = (self.pos.0 + 1, self.f.wrap_additional_indent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.word_i = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate over the word
|
|
||||||
let g = self.word_buf[self.word_i];
|
|
||||||
self.word_i += 1;
|
|
||||||
return self.next_charwrap(g, wrap_width);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// Helper functions
|
|
||||||
// ===================================================================
|
|
||||||
|
|
||||||
/// Returns the visual width of a grapheme given a starting
|
/// Returns the visual width of a grapheme given a starting
|
||||||
/// position on a line.
|
/// position on a line.
|
||||||
fn grapheme_vis_width_at_vis_pos(g: RopeSlice, pos: usize, tab_width: usize) -> usize {
|
fn grapheme_vis_width_at_vis_pos(g: &str, pos: usize, tab_width: usize) -> usize {
|
||||||
if g == "\t" {
|
if g == "\t" {
|
||||||
let ending_pos = ((pos / tab_width) + 1) * tab_width;
|
let ending_pos = ((pos / tab_width) + 1) * tab_width;
|
||||||
return ending_pos - pos;
|
return ending_pos - pos;
|
||||||
} else if rope_slice_is_line_ending(&g) {
|
} else if is_line_ending(g) {
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
return grapheme_width(&g);
|
return grapheme_width(&g);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
#![allow(unused_imports)]
|
#![allow(unused_imports)]
|
||||||
|
|
|
@ -14,7 +14,7 @@ use crossterm::{
|
||||||
use crate::{
|
use crate::{
|
||||||
editor::Editor,
|
editor::Editor,
|
||||||
formatter::{block_count, block_index_and_range, char_range_from_block_index, LineFormatter},
|
formatter::{block_count, block_index_and_range, char_range_from_block_index, LineFormatter},
|
||||||
string_utils::{line_ending_to_str, rope_slice_is_line_ending, LineEnding},
|
string_utils::{is_line_ending, line_ending_to_str, LineEnding},
|
||||||
utils::{digit_count, RopeGraphemes, Timer},
|
utils::{digit_count, RopeGraphemes, Timer},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -568,7 +568,7 @@ impl TermUI {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actually print the character
|
// Actually print the character
|
||||||
if rope_slice_is_line_ending(&g) {
|
if is_line_ending(&g) {
|
||||||
if at_cursor {
|
if at_cursor {
|
||||||
self.screen
|
self.screen
|
||||||
.draw(px as usize, py as usize, " ", STYLE_CURSOR);
|
.draw(px as usize, py as usize, " ", STYLE_CURSOR);
|
||||||
|
@ -587,19 +587,9 @@ impl TermUI {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if at_cursor {
|
if at_cursor {
|
||||||
self.screen.draw_rope_slice(
|
self.screen.draw(px as usize, py as usize, &g, STYLE_CURSOR);
|
||||||
px as usize,
|
|
||||||
py as usize,
|
|
||||||
&g,
|
|
||||||
STYLE_CURSOR,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
self.screen.draw_rope_slice(
|
self.screen.draw(px as usize, py as usize, &g, STYLE_MAIN);
|
||||||
px as usize,
|
|
||||||
py as usize,
|
|
||||||
&g,
|
|
||||||
STYLE_MAIN,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,9 @@ use std::io;
|
||||||
use std::io::{BufWriter, Write};
|
use std::io::{BufWriter, Write};
|
||||||
|
|
||||||
use crossterm::{self, execute, queue};
|
use crossterm::{self, execute, queue};
|
||||||
use ropey::RopeSlice;
|
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
use unicode_width::UnicodeWidthStr;
|
|
||||||
|
|
||||||
use crate::utils::{grapheme_width, RopeGraphemes};
|
use crate::utils::grapheme_width;
|
||||||
|
|
||||||
use super::smallstring::SmallString;
|
use super::smallstring::SmallString;
|
||||||
|
|
||||||
|
@ -135,7 +133,7 @@ impl Screen {
|
||||||
let mut buf = self.buf.borrow_mut();
|
let mut buf = self.buf.borrow_mut();
|
||||||
let mut x = x;
|
let mut x = x;
|
||||||
for g in UnicodeSegmentation::graphemes(text, true) {
|
for g in UnicodeSegmentation::graphemes(text, true) {
|
||||||
let width = UnicodeWidthStr::width(g);
|
let width = grapheme_width(g);
|
||||||
if width > 0 {
|
if width > 0 {
|
||||||
if x < self.w {
|
if x < self.w {
|
||||||
buf[y * self.w + x] = Some((style, g.into()));
|
buf[y * self.w + x] = Some((style, g.into()));
|
||||||
|
@ -152,28 +150,6 @@ impl Screen {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn draw_rope_slice(&self, x: usize, y: usize, text: &RopeSlice, style: Style) {
|
|
||||||
if y < self.h {
|
|
||||||
let mut buf = self.buf.borrow_mut();
|
|
||||||
let mut x = x;
|
|
||||||
for g in RopeGraphemes::new(&text) {
|
|
||||||
let width = grapheme_width(&g);
|
|
||||||
if width > 0 {
|
|
||||||
if x < self.w {
|
|
||||||
buf[y * self.w + x] = Some((style, SmallString::from_rope_slice(&g)));
|
|
||||||
}
|
|
||||||
x += 1;
|
|
||||||
for _ in 0..(width - 1) {
|
|
||||||
if x < self.w {
|
|
||||||
buf[y * self.w + x] = None;
|
|
||||||
}
|
|
||||||
x += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn hide_cursor(&self) {
|
pub(crate) fn hide_cursor(&self) {
|
||||||
let mut out = self.out.borrow_mut();
|
let mut out = self.out.borrow_mut();
|
||||||
execute!(out, crossterm::cursor::Hide).unwrap();
|
execute!(out, crossterm::cursor::Hide).unwrap();
|
||||||
|
|
10
src/utils.rs
10
src/utils.rs
|
@ -16,14 +16,8 @@ pub fn digit_count(mut n: u32, b: u32) -> u32 {
|
||||||
|
|
||||||
//=============================================================
|
//=============================================================
|
||||||
|
|
||||||
pub fn grapheme_width(slice: &RopeSlice) -> usize {
|
pub fn grapheme_width(g: &str) -> usize {
|
||||||
use crate::term_ui::smallstring::SmallString;
|
UnicodeWidthStr::width(g)
|
||||||
if let Some(text) = slice.as_str() {
|
|
||||||
return UnicodeWidthStr::width(text);
|
|
||||||
} else {
|
|
||||||
let text = SmallString::from_rope_slice(slice);
|
|
||||||
return UnicodeWidthStr::width(&text[..]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds the previous grapheme boundary before the given char position.
|
/// Finds the previous grapheme boundary before the given char position.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user