diff --git a/src/cmd_line.rs b/src/cmd_line.rs index 67dd089..94277dd 100644 --- a/src/cmd_line.rs +++ b/src/cmd_line.rs @@ -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)>>, append: bool) { + fn replace_line(&mut self, lines: Vec, Vec)>>, 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, String)>>, - ) -> Vec, Vec)>> { - 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, 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)>>, + lines: Vec, Vec)>>, max_width: i32, render_state: &shell::RenderState, ) -> Self { @@ -144,17 +133,16 @@ fn prompt_lines( firstc: &str, prompt: &str, indent: u64, -) -> (usize, Vec<(Option, Vec)>) { - let prompt: Vec<(Option, Vec)> = if !firstc.is_empty() { +) -> (usize, Vec<(Option, Vec)>) { + let prompt: Vec<(Option, Vec)> = 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, Vec)> = 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)>>, + lines: Vec, Vec)>>, prompt_offset: usize, } + +trait ToAttributedModelContent { + fn to_attributed_content(&self) -> Vec, Vec)>>; +} + +impl ToAttributedModelContent for Vec, String)>> { + fn to_attributed_content(&self) -> Vec, Vec)>> { + 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)> { + fn to_attributed_content(&self) -> Vec, Vec)>> { + vec![ + self.iter() + .map(|c| { + ( + Some(Attrs::from_value_map(&c.0)), + c.1.graphemes(true).map(|g| g.to_owned()).collect(), + ) + }) + .collect(), + ] + } +} diff --git a/src/main.rs b/src/main.rs index 1477aeb..265e702 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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] diff --git a/src/ui_model/cell.rs b/src/ui_model/cell.rs index 9d528db..5e409b6 100644 --- a/src/ui_model/cell.rs +++ b/src/ui_model/cell.rs @@ -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; } diff --git a/src/ui_model/line.rs b/src/ui_model/line.rs index ca835f4..259ab9d 100644 --- a/src/ui_model/line.rs +++ b/src/ui_model/line.rs @@ -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); diff --git a/src/ui_model/mod.rs b/src/ui_model/mod.rs index 60c5dbd..2e09658 100644 --- a/src/ui_model/mod.rs +++ b/src/ui_model/mod.rs @@ -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); diff --git a/src/ui_model/model_layout.rs b/src/ui_model/model_layout.rs index 82c92d2..b680ccf 100644 --- a/src/ui_model/model_layout.rs +++ b/src/ui_model/model_layout.rs @@ -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);