Replace char with string to support graphemes

fix #38
This commit is contained in:
daa 2018-04-08 12:49:36 +03:00
parent 2b818d0847
commit 3e15e5fa2a
6 changed files with 78 additions and 59 deletions

View File

@ -1,3 +1,4 @@
use std::iter;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::Arc;
@ -9,6 +10,8 @@ use gtk::prelude::*;
use cairo;
use pango;
use unicode_segmentation::UnicodeSegmentation;
use neovim_lib::Value;
use nvim::{self, NeovimClient};
@ -55,7 +58,7 @@ impl Level {
level
}
fn replace_line(&mut self, lines: Vec<Vec<(Option<Attrs>, Vec<char>)>>, append: bool) {
fn replace_line(&mut self, lines: Vec<Vec<(Option<Attrs>, Vec<String>)>>, append: bool) {
if append {
self.model_layout.layout_append(lines);
} else {
@ -77,34 +80,20 @@ impl Level {
self.preferred_height = (line_height * rows as f64) as i32;
}
fn to_attributed_content(
content: &Vec<Vec<(HashMap<String, Value>, String)>>,
) -> Vec<Vec<(Option<Attrs>, Vec<char>)>> {
content
.iter()
.map(|line_chars| {
line_chars
.iter()
.map(|c| (Some(Attrs::from_value_map(&c.0)), c.1.chars().collect()))
.collect()
})
.collect()
}
pub fn from_multiline_content(
content: &Vec<Vec<(HashMap<String, Value>, String)>>,
max_width: i32,
render_state: &shell::RenderState,
) -> Self {
Level::from_lines(
Level::to_attributed_content(content),
content.to_attributed_content(),
max_width,
render_state,
)
}
pub fn from_lines(
lines: Vec<Vec<(Option<Attrs>, Vec<char>)>>,
lines: Vec<Vec<(Option<Attrs>, Vec<String>)>>,
max_width: i32,
render_state: &shell::RenderState,
) -> Self {
@ -144,17 +133,16 @@ fn prompt_lines(
firstc: &str,
prompt: &str,
indent: u64,
) -> (usize, Vec<(Option<Attrs>, Vec<char>)>) {
let prompt: Vec<(Option<Attrs>, Vec<char>)> = if !firstc.is_empty() {
) -> (usize, Vec<(Option<Attrs>, Vec<String>)>) {
let prompt: Vec<(Option<Attrs>, Vec<String>)> = if !firstc.is_empty() {
if firstc.len() >= indent as usize {
vec![(None, firstc.chars().collect())]
vec![(None, vec![firstc.to_owned()])]
} else {
vec![
(
None,
firstc
.chars()
.chain((firstc.len()..indent as usize).map(|_| ' '))
iter::once(firstc.to_owned())
.chain((firstc.len()..indent as usize).map(|_| " ".to_owned()))
.collect(),
),
]
@ -162,7 +150,7 @@ fn prompt_lines(
} else if !prompt.is_empty() {
prompt
.lines()
.map(|l| (None, l.chars().collect()))
.map(|l| (None, l.graphemes(true).map(|g| g.to_owned()).collect()))
.collect()
} else {
vec![]
@ -446,13 +434,10 @@ impl CmdLine {
let mut state = self.state.borrow_mut();
let render_state = state.render_state.clone();
{
let attr_content = content
.iter()
.map(|c| (Some(Attrs::from_value_map(&c.0)), c.1.chars().collect()))
.collect();
let attr_content = content.to_attributed_content();
let block = state.block.as_mut().unwrap();
block.replace_line(vec![attr_content], true);
block.replace_line(attr_content, true);
block.update_preferred_size(&*render_state.borrow());
block.update_cache(&*render_state.borrow());
}
@ -594,18 +579,15 @@ pub struct CmdLineContext<'a> {
impl<'a> CmdLineContext<'a> {
fn get_lines(&self) -> LineContent {
let content_line: Vec<(Option<Attrs>, Vec<char>)> = self.content
.iter()
.map(|c| (Some(Attrs::from_value_map(&c.0)), c.1.chars().collect()))
.collect();
let mut content_line = self.content.to_attributed_content();
let (prompt_offset, prompt_lines) = prompt_lines(&self.firstc, &self.prompt, self.indent);
let mut content: Vec<_> = prompt_lines.into_iter().map(|line| vec![line]).collect();
if content.is_empty() {
content.push(content_line);
content.push(content_line.remove(0));
} else {
content.last_mut().map(|line| line.extend(content_line));
content.last_mut().map(|line| line.extend(content_line.remove(0)));
}
LineContent {
@ -616,6 +598,43 @@ impl<'a> CmdLineContext<'a> {
}
struct LineContent {
lines: Vec<Vec<(Option<Attrs>, Vec<char>)>>,
lines: Vec<Vec<(Option<Attrs>, Vec<String>)>>,
prompt_offset: usize,
}
trait ToAttributedModelContent {
fn to_attributed_content(&self) -> Vec<Vec<(Option<Attrs>, Vec<String>)>>;
}
impl ToAttributedModelContent for Vec<Vec<(HashMap<String, Value>, String)>> {
fn to_attributed_content(&self) -> Vec<Vec<(Option<Attrs>, Vec<String>)>> {
self.iter()
.map(|line_chars| {
line_chars
.iter()
.map(|c| {
(
Some(Attrs::from_value_map(&c.0)),
c.1.graphemes(true).map(|g| g.to_owned()).collect(),
)
})
.collect()
})
.collect()
}
}
impl ToAttributedModelContent for Vec<(HashMap<String, Value>, String)> {
fn to_attributed_content(&self) -> Vec<Vec<(Option<Attrs>, Vec<String>)>> {
vec![
self.iter()
.map(|c| {
(
Some(Attrs::from_value_map(&c.0)),
c.1.graphemes(true).map(|g| g.to_owned()).collect(),
)
})
.collect(),
]
}
}

View File

@ -26,6 +26,7 @@ extern crate phf;
extern crate rmpv;
extern crate regex;
extern crate unicode_width;
extern crate unicode_segmentation;
extern crate serde;
#[macro_use]

View File

@ -76,8 +76,6 @@ impl Attrs {
}
}
const EMPTY_STRING: String = String::new();
#[derive(Clone)]
pub struct Cell {
pub attrs: Attrs,
@ -87,7 +85,7 @@ pub struct Cell {
impl Cell {
pub fn new_empty() -> Cell {
Cell::new(EMPTY_STRING)
Cell::new(" ".to_owned())
}
pub fn new(ch: String) -> Cell {
@ -99,7 +97,7 @@ impl Cell {
}
pub fn clear(&mut self) {
self.ch = EMPTY_STRING;
self.ch = " ".to_owned();
self.attrs.clear();
self.dirty = true;
}

View File

@ -402,9 +402,9 @@ mod tests {
#[test]
fn test_styled_line() {
let mut line = Line::new(3);
line[0].ch = 'a';
line[1].ch = 'b';
line[2].ch = 'c';
line[0].ch = "a".to_owned();
line[1].ch = "b".to_owned();
line[2].ch = "c".to_owned();
let styled_line = StyledLine::from(&line, &color::ColorModel::new());
assert_eq!("abc", styled_line.line_str);

View File

@ -305,7 +305,7 @@ mod tests {
model.set_cursor(1, 1);
let rect = model.put(' ', false, None);
let rect = model.put(" ".to_owned(), false, None);
assert_eq!(1, rect.top);
assert_eq!(1, rect.left);

View File

@ -1,6 +1,6 @@
use std::cmp::max;
use unicode_width::UnicodeWidthChar;
use unicode_width::UnicodeWidthStr;
use ui_model::{Attrs, UiModel};
@ -94,6 +94,7 @@ impl ModelLayout {
if cur_col < col_idx + chars.len() {
let col_sub_idx = cur_col - col_idx;
chars.insert(col_sub_idx, ch);
break;
} else {
col_idx += chars.len();
}
@ -117,7 +118,7 @@ impl ModelLayout {
for content in lines {
for &(ref attr, ref ch_list) in content {
for ch in ch_list {
let ch_width = ch.width().unwrap_or(1);
let ch_width = max(1, ch.width());
if col_idx + ch_width > self.model.columns {
col_idx = 0;
@ -125,9 +126,9 @@ impl ModelLayout {
}
self.model.set_cursor(row_idx, col_idx as usize);
self.model.put(*ch, false, attr.as_ref());
self.model.put(ch.clone(), false, attr.as_ref());
if ch_width > 1 {
self.model.put(' ', true, attr.as_ref());
self.model.put(" ".to_owned(), true, attr.as_ref());
}
if max_col_idx < col_idx {
@ -169,7 +170,7 @@ mod tests {
#[test]
fn test_count_lines() {
let lines = vec![vec![(None, vec!['a'; 5])]];
let lines = vec![vec![(None, vec!["a".to_owned(); 5])]];
let rows = ModelLayout::count_lines(&lines, 4);
assert_eq!(2, rows);
@ -177,7 +178,7 @@ mod tests {
#[test]
fn test_resize() {
let lines = vec![vec![(None, vec!['a'; 5])]; ModelLayout::ROWS_STEP];
let lines = vec![vec![(None, vec!["a".to_owned(); 5])]; ModelLayout::ROWS_STEP];
let mut model = ModelLayout::new(5);
model.layout(lines.clone());
@ -194,14 +195,14 @@ mod tests {
#[test]
fn test_cols_filled() {
let lines = vec![vec![(None, vec!['a'; 3])]; 1];
let lines = vec![vec![(None, vec!["a".to_owned(); 3])]; 1];
let mut model = ModelLayout::new(5);
model.layout(lines);
let (cols, _) = model.size();
assert_eq!(4, cols); // size is 3 and 4 - is with cursor position
let lines = vec![vec![(None, vec!['a'; 2])]; 1];
let lines = vec![vec![(None, vec!["a".to_owned(); 2])]; 1];
model.layout_append(lines);
let (cols, _) = model.size();
@ -210,35 +211,35 @@ mod tests {
#[test]
fn test_insert_shift() {
let lines = vec![vec![(None, vec!['a'; 3])]; 1];
let lines = vec![vec![(None, vec!["a".to_owned(); 3])]; 1];
let mut model = ModelLayout::new(5);
model.layout(lines);
model.set_cursor(1);
model.insert_char("b", true);
model.insert_char("b".to_owned(), true);
let (cols, _) = model.size();
assert_eq!(4, cols);
assert_eq!('b', model.model.model()[0].line[1].ch);
assert_eq!("b", model.model.model()[0].line[1].ch);
}
#[test]
fn test_insert_no_shift() {
let lines = vec![vec![(None, vec!['a'; 3])]; 1];
let lines = vec![vec![(None, vec!["a".to_owned(); 3])]; 1];
let mut model = ModelLayout::new(5);
model.layout(lines);
model.set_cursor(1);
model.insert_char("b", false);
model.insert_char("b".to_owned(), false);
let (cols, _) = model.size();
assert_eq!(3, cols);
assert_eq!('b', model.model.model()[0].line[1].ch);
assert_eq!("b", model.model.model()[0].line[1].ch);
}
#[test]
fn test_double_width() {
let lines = vec![vec![(None, vec!['あ'; 3])]; 1];
let lines = vec![vec![(None, vec!["".to_owned(); 3])]; 1];
let mut model = ModelLayout::new(7);
model.layout(lines);
model.set_cursor(1);