From cda125f20c83cb90c0725dcd4cb11df1ccf9e588 Mon Sep 17 00:00:00 2001 From: pm100 Date: Sat, 31 Oct 2020 15:30:57 -0700 Subject: [PATCH 1/8] fix cursor lost at end of line --- src/components/textinput.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/textinput.rs b/src/components/textinput.rs index 267c957887..11b25c8d67 100644 --- a/src/components/textinput.rs +++ b/src/components/textinput.rs @@ -141,7 +141,7 @@ impl TextInputComponent { .next_char_position() // if the cursor is at the end of the msg // a whitespace is used to underline - .map_or(" ".to_owned(), |pos| { + .map_or("_".to_owned(), |pos| { self.get_msg(self.cursor_position..pos) }); From 83f3c92a15bec51dd5bc3c47832ac9ad75d2442f Mon Sep 17 00:00:00 2001 From: pm100 Date: Sat, 31 Oct 2020 15:36:30 -0700 Subject: [PATCH 2/8] fix cursot lost at end of line (test broken) --- src/components/textinput.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/textinput.rs b/src/components/textinput.rs index 11b25c8d67..cd1becc342 100644 --- a/src/components/textinput.rs +++ b/src/components/textinput.rs @@ -373,7 +373,7 @@ mod tests { assert_eq!(txt.len(), 2); assert_eq!(get_text(&txt[0]), Some("a")); assert_eq!(get_style(&txt[0]), Some(¬_underlined)); - assert_eq!(get_text(&txt[1]), Some(" ")); + assert_eq!(get_text(&txt[1]), Some("_")); assert_eq!(get_style(&txt[1]), Some(&underlined)); } From be61f0de3104ad3ae6ab0bfeb513bad744c83297 Mon Sep 17 00:00:00 2001 From: pm100 Date: Sat, 31 Oct 2020 17:15:17 -0700 Subject: [PATCH 3/8] remove double underline --- src/components/textinput.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/components/textinput.rs b/src/components/textinput.rs index cd1becc342..909b7ec396 100644 --- a/src/components/textinput.rs +++ b/src/components/textinput.rs @@ -137,6 +137,10 @@ impl TextInputComponent { )); } + // this code with _ for the trailing cursor character needs to be revised once tui fixes + // https://github.com/fdehau/tui-rs/issues/404 + // it should be NBSP => const NBSP: &str = "\u{00a0}"; + // ... let cursor_str = self .next_char_position() // if the cursor is at the end of the msg @@ -153,12 +157,16 @@ impl TextInputComponent { .add_modifier(Modifier::UNDERLINED), )); } + // ... and this conditional underline needs to be removed - txt.push(Span::styled( - cursor_str, - style.add_modifier(Modifier::UNDERLINED), - )); - + if cursor_str == "_" { + txt.push(Span::styled(cursor_str, style)); + } else { + txt.push(Span::styled( + cursor_str, + style.add_modifier(Modifier::UNDERLINED), + )); + } // The final portion of the text is added if there are // still remaining characters. if let Some(pos) = self.next_char_position() { From 145b742e85ad8d1493429d2f99c8df8dc766c64e Mon Sep 17 00:00:00 2001 From: pm100 Date: Sat, 31 Oct 2020 17:30:08 -0700 Subject: [PATCH 4/8] cursor lost - fix test after suppressing underline --- src/components/textinput.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/textinput.rs b/src/components/textinput.rs index 909b7ec396..baab87b22f 100644 --- a/src/components/textinput.rs +++ b/src/components/textinput.rs @@ -367,7 +367,9 @@ mod tests { "", ); let theme = SharedTheme::default(); - let underlined = theme + + // retained for when tui trailing NBSP bug fixed + let _underlined = theme .text(true, false) .add_modifier(Modifier::UNDERLINED); @@ -382,7 +384,7 @@ mod tests { assert_eq!(get_text(&txt[0]), Some("a")); assert_eq!(get_style(&txt[0]), Some(¬_underlined)); assert_eq!(get_text(&txt[1]), Some("_")); - assert_eq!(get_style(&txt[1]), Some(&underlined)); + assert_eq!(get_style(&txt[1]), Some(¬_underlined)); } #[test] From 225b972ec0dfb9a509eebae723ddd91e0a9d2381 Mon Sep 17 00:00:00 2001 From: pm100 Date: Mon, 2 Nov 2020 16:48:10 -0800 Subject: [PATCH 5/8] implement full multi line support in textinput --- src/app.rs | 2 +- src/components/commit.rs | 6 +- src/components/cred.rs | 12 +- src/components/mod.rs | 4 +- src/components/rename_branch.rs | 2 +- src/components/reset.rs | 7 +- src/components/stashmsg.rs | 6 +- src/components/tag_commit.rs | 2 +- src/components/textinput.rs | 319 +++++++++++++++++++++----------- 9 files changed, 229 insertions(+), 131 deletions(-) diff --git a/src/app.rs b/src/app.rs index 70e803c740..ca58f5021a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -518,7 +518,7 @@ impl App { } InternalEvent::RenameBranch(branch_ref, cur_name) => { self.rename_branch_popup - .open(branch_ref, cur_name)?; + .open(branch_ref, &cur_name)?; } InternalEvent::SelectBranch => { self.select_branch_popup.open()?; diff --git a/src/components/commit.rs b/src/components/commit.rs index 708aeb1c40..0c1feacacb 100644 --- a/src/components/commit.rs +++ b/src/components/commit.rs @@ -183,14 +183,14 @@ impl CommitComponent { let message = message.trim().to_string(); - self.input.set_text(message); + self.input.set_text(&message); self.input.show()?; Ok(()) } fn commit(&mut self) -> Result<()> { - self.commit_msg(self.input.get_text().clone()) + self.commit_msg(self.input.get_text()) } fn commit_msg(&mut self, msg: String) -> Result<()> { @@ -272,7 +272,7 @@ impl CommitComponent { .set_title(strings::commit_title_amend(&self.key_config)); if let Some(msg) = details.message { - self.input.set_text(msg.combine()); + self.input.set_text(&msg.combine()); } Ok(()) diff --git a/src/components/cred.rs b/src/components/cred.rs index 8f0d526aa6..8f1d451de3 100644 --- a/src/components/cred.rs +++ b/src/components/cred.rs @@ -111,11 +111,7 @@ impl Component for CredComponent { } else if e == self.key_config.enter { if self.input_username.is_visible() { self.cred = BasicAuthCredential::new( - Some( - self.input_username - .get_text() - .to_owned(), - ), + Some(self.input_username.get_text()), None, ); self.input_username.hide(); @@ -123,11 +119,7 @@ impl Component for CredComponent { } else if self.input_password.is_visible() { self.cred = BasicAuthCredential::new( self.cred.username.clone(), - Some( - self.input_password - .get_text() - .to_owned(), - ), + Some(self.input_password.get_text()), ); self.input_password.hide(); self.input_password.clear(); diff --git a/src/components/mod.rs b/src/components/mod.rs index aa73b68661..4f8aa2b0ea 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -207,11 +207,11 @@ fn dialog_paragraph<'a>( fn popup_paragraph<'a>( title: &'a str, - content: Vec>, + content: Vec>, theme: &Theme, focused: bool, ) -> Paragraph<'a> { - Paragraph::new(Spans::from(content)) + Paragraph::new(content) .block( Block::default() .title(Span::styled(title, theme.title(focused))) diff --git a/src/components/rename_branch.rs b/src/components/rename_branch.rs index 81a9cda78b..801720c64a 100644 --- a/src/components/rename_branch.rs +++ b/src/components/rename_branch.rs @@ -112,7 +112,7 @@ impl RenameBranchComponent { pub fn open( &mut self, branch_ref: String, - cur_name: String, + cur_name: &str, ) -> Result<()> { self.branch_ref = None; self.branch_ref = Some(branch_ref); diff --git a/src/components/reset.rs b/src/components/reset.rs index da275c5948..220e979db3 100644 --- a/src/components/reset.rs +++ b/src/components/reset.rs @@ -11,7 +11,8 @@ use anyhow::Result; use crossterm::event::Event; use std::borrow::Cow; use tui::{ - backend::Backend, layout::Rect, text::Span, widgets::Clear, Frame, + backend::Backend, layout::Rect, text::Span, text::Spans, + widgets::Clear, Frame, }; use ui::style::SharedTheme; @@ -33,10 +34,10 @@ impl DrawableComponent for ResetComponent { if self.visible { let (title, msg) = self.get_text(); - let txt = vec![Span::styled( + let txt = vec![Spans::from(Span::styled( Cow::from(msg), self.theme.text_danger(), - )]; + ))]; let area = ui::centered_rect(30, 20, f.size()); f.render_widget(Clear, area); diff --git a/src/components/stashmsg.rs b/src/components/stashmsg.rs index 8b3e01ff7e..05cb69b1e9 100644 --- a/src/components/stashmsg.rs +++ b/src/components/stashmsg.rs @@ -62,12 +62,14 @@ impl Component for StashMsgComponent { if let Event::Key(e) = ev { if e == self.key_config.enter { + let msg = self.input.get_text(); + match sync::stash_save( CWD, - if self.input.get_text().is_empty() { + if msg.is_empty() { None } else { - Some(self.input.get_text().as_str()) + Some(&msg) }, self.options.stash_untracked, self.options.keep_index, diff --git a/src/components/tag_commit.rs b/src/components/tag_commit.rs index 063871b393..50bf84c1e9 100644 --- a/src/components/tag_commit.rs +++ b/src/components/tag_commit.rs @@ -119,7 +119,7 @@ impl TagCommitComponent { /// pub fn tag(&mut self) { if let Some(commit_id) = self.commit_id { - match sync::tag(CWD, &commit_id, self.input.get_text()) { + match sync::tag(CWD, &commit_id, &self.input.get_text()) { Ok(_) => { self.input.clear(); self.hide(); diff --git a/src/components/textinput.rs b/src/components/textinput.rs index 959bbc0752..8520ab4c4d 100644 --- a/src/components/textinput.rs +++ b/src/components/textinput.rs @@ -14,7 +14,7 @@ use itertools::Itertools; use std::ops::Range; use tui::{ backend::Backend, layout::Rect, style::Modifier, text::Span, - widgets::Clear, Frame, + text::Spans, widgets::Clear, Frame, }; #[derive(PartialEq)] @@ -28,12 +28,15 @@ pub enum InputType { pub struct TextInputComponent { title: String, default_msg: String, - msg: String, + msg: Vec, visible: bool, theme: SharedTheme, key_config: SharedKeyConfig, cursor_position: usize, + current_line: usize, input_type: InputType, + num_lines: usize, + display_off: usize, } impl TextInputComponent { @@ -45,14 +48,17 @@ impl TextInputComponent { default_msg: &str, ) -> Self { Self { - msg: String::default(), + msg: vec![String::default()], visible: false, theme, key_config, title: title.to_string(), default_msg: default_msg.to_string(), cursor_position: 0, + current_line: 0, input_type: InputType::Multiline, + num_lines: 3, + display_off: 0, } } @@ -66,58 +72,122 @@ impl TextInputComponent { /// Clear the `msg`. pub fn clear(&mut self) { - self.msg.clear(); + self.msg = vec![String::default()]; self.cursor_position = 0; + self.current_line = 0; } /// Get the `msg`. - pub const fn get_text(&self) -> &String { - &self.msg + pub fn get_text(&self) -> String { + self.msg.join("\n") } /// Move the cursor right one char. fn incr_cursor(&mut self) { if let Some(pos) = self.next_char_position() { self.cursor_position = pos; + } else { + self.next_line(0); + } + } + fn prev_line(&mut self, cursor: usize) { + if self.current_line > 0 { + self.current_line -= 1; + self.cursor_position = std::cmp::min( + cursor, + self.msg[self.current_line].len(), + ); + if self.current_line == self.display_off + && self.display_off > 0 + { + self.display_off -= 1; + } + } + } + fn next_line(&mut self, cursor: usize) { + if self.current_line < self.msg.len() - 1 { + self.current_line += 1; + self.cursor_position = std::cmp::min( + cursor, + self.msg[self.current_line].len(), + ); + if self.current_line > self.num_lines - 1 { + self.display_off += 1; + } } } - /// Move the cursor left one char. fn decr_cursor(&mut self) { - let mut index = self.cursor_position.saturating_sub(1); - while index > 0 && !self.msg.is_char_boundary(index) { - index -= 1; + if self.cursor_position == 0 { + self.prev_line(std::usize::MAX); + } else { + let mut index = self.cursor_position.saturating_sub(1); + while index > 0 + && !self.msg[self.current_line] + .is_char_boundary(index) + { + index -= 1; + } + self.cursor_position = index; } - self.cursor_position = index; } /// Get the position of the next char, or, if the cursor points /// to the last char, the `msg.len()`. /// Returns None when the cursor is already at `msg.len()`. fn next_char_position(&self) -> Option { - if self.cursor_position >= self.msg.len() { + if self.cursor_position >= self.msg[self.current_line].len() { return None; } let mut index = self.cursor_position.saturating_add(1); while index < self.msg.len() - && !self.msg.is_char_boundary(index) + && !self.msg[self.current_line].is_char_boundary(index) { index += 1; } Some(index) } + fn line_up(&mut self) { + self.prev_line(self.cursor_position); + } + fn line_down(&mut self) { + self.next_line(self.cursor_position); + } + + fn split_line(&mut self) { + self.msg[self.current_line] + .insert(self.cursor_position, '\n'); + let cl = self.current_line; + self.set_text(&self.get_text()); + self.current_line = cl; + self.next_line(0); + } + + fn delete_char(&mut self) { + if self.cursor_position < self.msg[self.current_line].len() { + self.msg[self.current_line].remove(self.cursor_position); + } else if self.msg.len() + 1 > self.current_line { + let next_line = self.msg[self.current_line + 1].clone(); + self.msg.remove(self.current_line + 1); + self.msg[self.current_line].push_str(&next_line); + } + } fn backspace(&mut self) { if self.cursor_position > 0 { self.decr_cursor(); - self.msg.remove(self.cursor_position); + self.msg[self.current_line].remove(self.cursor_position); } } /// Set the `msg`. - pub fn set_text(&mut self, msg: String) { - self.msg = msg; + pub fn set_text(&mut self, msg: &str) { + self.msg = msg.split('\n').map(ToString::to_string).collect(); + if self.msg.is_empty() { + self.msg.push(String::default()); + } self.cursor_position = 0; + self.current_line = 0; } /// Set the `title`. @@ -125,67 +195,86 @@ impl TextInputComponent { self.title = t; } - fn get_draw_text(&self) -> Vec { + fn get_draw_text(&self) -> Vec { let style = self.theme.text(true, false); + let mut spans = Vec::new(); + // let start = { + // if self.msg.len() > self.num_lines { + // self.msg.len() - self.num_lines + // } else { + // 0 + // } + // }; + for i in self.display_off + ..std::cmp::min( + self.num_lines + self.display_off, + self.msg.len(), + ) + { + let mut txt = Vec::new(); + + if i == self.current_line { + // The portion of the text before the cursor is added + // if the cursor is not at the first character. + if self.cursor_position > 0 { + txt.push(Span::styled( + self.get_msg(0..self.cursor_position), + style, + )); + } - let mut txt = Vec::new(); - // The portion of the text before the cursor is added - // if the cursor is not at the first character. - if self.cursor_position > 0 { - txt.push(Span::styled( - self.get_msg(0..self.cursor_position), - style, - )); - } - - // this code with _ for the trailing cursor character needs to be revised once tui fixes - // https://github.com/fdehau/tui-rs/issues/404 - // it should be NBSP => const NBSP: &str = "\u{00a0}"; - // ... - let cursor_str = self - .next_char_position() - // if the cursor is at the end of the msg - // a whitespace is used to underline - .map_or("_".to_owned(), |pos| { - self.get_msg(self.cursor_position..pos) - }); - - if cursor_str == "\n" { - txt.push(Span::styled( - "\u{21b5}", - self.theme - .text(false, false) - .add_modifier(Modifier::UNDERLINED), - )); - } - // ... and this conditional underline needs to be removed - - if cursor_str == "_" { - txt.push(Span::styled(cursor_str, style)); - } else { - txt.push(Span::styled( - cursor_str, - style.add_modifier(Modifier::UNDERLINED), - )); - } - // The final portion of the text is added if there are - // still remaining characters. - if let Some(pos) = self.next_char_position() { - if pos < self.msg.len() { - txt.push(Span::styled( - self.get_msg(pos..self.msg.len()), - style, - )); + // this code with _ for the trailing cursor character needs to be revised once tui fixes + // https://github.com/fdehau/tui-rs/issues/404 + // it should be NBSP => const NBSP: &str = "\u{00a0}"; + // ... + let cursor_str = self + .next_char_position() + // if the cursor is at the end of the msg + // a whitespace is used to underline + .map_or("_".to_owned(), |pos| { + self.get_msg(self.cursor_position..pos) + }); + + if cursor_str == "\n" { + txt.push(Span::styled( + "\u{21b5}", + self.theme + .text(false, false) + .add_modifier(Modifier::UNDERLINED), + )); + } + // ... and this conditional underline needs to be removed + + if cursor_str == "_" { + txt.push(Span::styled(cursor_str, style)); + } else { + txt.push(Span::styled( + cursor_str, + style.add_modifier(Modifier::UNDERLINED), + )); + } + // The final portion of the text is added if there are + // still remaining characters. + if let Some(pos) = self.next_char_position() { + if pos < self.msg[i].len() { + txt.push(Span::styled( + self.get_msg(pos..self.msg[i].len()), + style, + )); + } + } + } else { + txt = vec![Span::raw(self.msg[i].clone())]; } + spans.push(Spans::from(txt)); } - - txt + spans } fn get_msg(&self, range: Range) -> String { match self.input_type { InputType::Password => range.map(|_| "*").join(""), - _ => self.msg[range].to_owned(), + _ => self.msg[self.current_line][range].to_owned(), } } } @@ -198,10 +287,10 @@ impl DrawableComponent for TextInputComponent { ) -> Result<()> { if self.visible { let txt = if self.msg.is_empty() { - vec![Span::styled( + vec![Spans::from(Span::styled( self.default_msg.as_str(), self.theme.text(false, false), - )] + ))] } else { self.get_draw_text() }; @@ -217,7 +306,6 @@ impl DrawableComponent for TextInputComponent { } _ => ui::centered_rect_absolute(32, 3, f.size()), }; - f.render_widget(Clear, area); f.render_widget( popup_paragraph( @@ -254,24 +342,26 @@ impl Component for TextInputComponent { fn event(&mut self, ev: Event) -> Result { if self.visible { if let Event::Key(e) = ev { + let is_ctrl = + e.modifiers.contains(KeyModifiers::CONTROL); + if e.code == KeyCode::Enter && is_ctrl { + self.split_line(); + return Ok(true); + } if e == self.key_config.exit_popup { self.hide(); return Ok(true); } - let is_ctrl = - e.modifiers.contains(KeyModifiers::CONTROL); - match e.code { KeyCode::Char(c) if !is_ctrl => { - self.msg.insert(self.cursor_position, c); + self.msg[self.current_line] + .insert(self.cursor_position, c); self.incr_cursor(); return Ok(true); } KeyCode::Delete => { - if self.cursor_position < self.msg.len() { - self.msg.remove(self.cursor_position); - } + self.delete_char(); return Ok(true); } KeyCode::Backspace => { @@ -286,12 +376,22 @@ impl Component for TextInputComponent { self.incr_cursor(); return Ok(true); } + KeyCode::Up => { + self.line_up(); + return Ok(true); + } + KeyCode::Down => { + self.line_down(); + return Ok(true); + } + KeyCode::Home => { self.cursor_position = 0; return Ok(true); } KeyCode::End => { - self.cursor_position = self.msg.len(); + self.cursor_position = + self.msg[self.current_line].len(); return Ok(true); } _ => (), @@ -330,7 +430,7 @@ mod tests { "", ); - comp.set_text(String::from("a\nb")); + comp.set_text("a\nb"); assert_eq!(comp.cursor_position, 0); @@ -354,13 +454,13 @@ mod tests { .text(true, false) .add_modifier(Modifier::UNDERLINED); - comp.set_text(String::from("a")); + comp.set_text("a"); - let txt = comp.get_draw_text(); + let txt = &comp.get_draw_text()[0]; - assert_eq!(txt.len(), 1); - assert_eq!(get_text(&txt[0]), Some("a")); - assert_eq!(get_style(&txt[0]), Some(&underlined)); + assert_eq!(txt.width(), 1); + assert_eq!(get_text(&txt.0[0]), Some("a")); + assert_eq!(get_style(&txt.0[0]), Some(&underlined)); } #[test] @@ -380,16 +480,16 @@ mod tests { let not_underlined = Style::default(); - comp.set_text(String::from("a")); + comp.set_text("a"); comp.incr_cursor(); - let txt = comp.get_draw_text(); + let txt = &comp.get_draw_text()[0]; - assert_eq!(txt.len(), 2); - assert_eq!(get_text(&txt[0]), Some("a")); - assert_eq!(get_style(&txt[0]), Some(¬_underlined)); - assert_eq!(get_text(&txt[1]), Some("_")); - assert_eq!(get_style(&txt[1]), Some(¬_underlined)); + assert_eq!(txt.width(), 2); + assert_eq!(get_text(&txt.0[0]), Some("a")); + assert_eq!(get_style(&txt.0[0]), Some(¬_underlined)); + assert_eq!(get_text(&txt.0[1]), Some("_")); + assert_eq!(get_style(&txt.0[1]), Some(¬_underlined)); } #[test] @@ -402,25 +502,26 @@ mod tests { ); let theme = SharedTheme::default(); - let underlined = theme + let _underlined = theme .text(false, false) .add_modifier(Modifier::UNDERLINED); - comp.set_text(String::from("a\nb")); + comp.set_text("a\nb"); comp.incr_cursor(); - let txt = comp.get_draw_text(); + let txt = &comp.get_draw_text(); - assert_eq!(txt.len(), 4); - assert_eq!(get_text(&txt[0]), Some("a")); - assert_eq!(get_text(&txt[1]), Some("\u{21b5}")); - assert_eq!(get_style(&txt[1]), Some(&underlined)); - assert_eq!(get_text(&txt[2]), Some("\n")); - assert_eq!(get_text(&txt[3]), Some("b")); + assert_eq!(txt.len(), 2); + let l1_spans = &txt[0].0; + let l2_spans = &txt[1].0; + assert_eq!(get_text(&l1_spans[0]), Some("a")); + assert_eq!(get_text(&l1_spans[1]), Some("_")); + //assert_eq!(get_style(&l1_spans[1]), Some(&underlined)); + assert_eq!(get_text(&l2_spans[0]), Some("b")); } #[test] - fn test_invisable_newline() { + fn test_invisible_newline() { let mut comp = TextInputComponent::new( SharedTheme::default(), SharedKeyConfig::default(), @@ -433,14 +534,16 @@ mod tests { .text(true, false) .add_modifier(Modifier::UNDERLINED); - comp.set_text(String::from("a\nb")); - - let txt = comp.get_draw_text(); + comp.set_text("a\nb"); + let txt = &comp.get_draw_text(); assert_eq!(txt.len(), 2); - assert_eq!(get_text(&txt[0]), Some("a")); - assert_eq!(get_style(&txt[0]), Some(&underlined)); - assert_eq!(get_text(&txt[1]), Some("\nb")); + let l1_spans = &txt[0].0; + let l2_spans = &txt[1].0; + + assert_eq!(get_text(&l1_spans[0]), Some("a")); + assert_eq!(get_style(&l1_spans[0]), Some(&underlined)); + assert_eq!(get_text(&l2_spans[0]), Some("b")); } fn get_text<'a>(t: &'a Span) -> Option<&'a str> { From d36cf1c18090cace5d1f81eb051b2287dead9668 Mon Sep 17 00:00:00 2001 From: pm100 Date: Mon, 2 Nov 2020 17:38:06 -0800 Subject: [PATCH 6/8] fixed BS to merge and windows height calc --- src/components/textinput.rs | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/components/textinput.rs b/src/components/textinput.rs index 8520ab4c4d..a371d8b6aa 100644 --- a/src/components/textinput.rs +++ b/src/components/textinput.rs @@ -12,6 +12,7 @@ use anyhow::Result; use crossterm::event::{Event, KeyCode, KeyModifiers}; use itertools::Itertools; use std::ops::Range; +use std::cell::Cell; use tui::{ backend::Backend, layout::Rect, style::Modifier, text::Span, text::Spans, widgets::Clear, Frame, @@ -35,7 +36,7 @@ pub struct TextInputComponent { cursor_position: usize, current_line: usize, input_type: InputType, - num_lines: usize, + num_lines: Cell, display_off: usize, } @@ -57,7 +58,7 @@ impl TextInputComponent { cursor_position: 0, current_line: 0, input_type: InputType::Multiline, - num_lines: 3, + num_lines: Cell::new(0), display_off: 0, } } @@ -111,7 +112,7 @@ impl TextInputComponent { cursor, self.msg[self.current_line].len(), ); - if self.current_line > self.num_lines - 1 { + if self.current_line > self.num_lines.get() - 1 { self.display_off += 1; } } @@ -163,29 +164,31 @@ impl TextInputComponent { self.current_line = cl; self.next_line(0); } - + fn merge_lines(&mut self) { + let next_line = self.msg[self.current_line + 1].clone(); + self.msg.remove(self.current_line + 1); + self.msg[self.current_line].push_str(&next_line); + } fn delete_char(&mut self) { if self.cursor_position < self.msg[self.current_line].len() { self.msg[self.current_line].remove(self.cursor_position); } else if self.msg.len() + 1 > self.current_line { - let next_line = self.msg[self.current_line + 1].clone(); - self.msg.remove(self.current_line + 1); - self.msg[self.current_line].push_str(&next_line); + self.merge_lines(); } } fn backspace(&mut self) { if self.cursor_position > 0 { self.decr_cursor(); self.msg[self.current_line].remove(self.cursor_position); + } else if self.current_line > 0 { + self.prev_line(std::usize::MAX); + self.merge_lines(); } } /// Set the `msg`. pub fn set_text(&mut self, msg: &str) { self.msg = msg.split('\n').map(ToString::to_string).collect(); - if self.msg.is_empty() { - self.msg.push(String::default()); - } self.cursor_position = 0; self.current_line = 0; } @@ -198,16 +201,10 @@ impl TextInputComponent { fn get_draw_text(&self) -> Vec { let style = self.theme.text(true, false); let mut spans = Vec::new(); - // let start = { - // if self.msg.len() > self.num_lines { - // self.msg.len() - self.num_lines - // } else { - // 0 - // } - // }; + for i in self.display_off ..std::cmp::min( - self.num_lines + self.display_off, + self.num_lines.get() + self.display_off, self.msg.len(), ) { @@ -306,6 +303,7 @@ impl DrawableComponent for TextInputComponent { } _ => ui::centered_rect_absolute(32, 3, f.size()), }; + self.num_lines.set(area.height as usize - 2); f.render_widget(Clear, area); f.render_widget( popup_paragraph( From 9528e055c15f38fd1c676664905a269603d729bf Mon Sep 17 00:00:00 2001 From: pm100 Date: Mon, 2 Nov 2020 17:50:47 -0800 Subject: [PATCH 7/8] cargo fmt and fix unix ctrl enter --- src/components/textinput.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/textinput.rs b/src/components/textinput.rs index a371d8b6aa..5cd1b63da6 100644 --- a/src/components/textinput.rs +++ b/src/components/textinput.rs @@ -11,8 +11,8 @@ use crate::{ use anyhow::Result; use crossterm::event::{Event, KeyCode, KeyModifiers}; use itertools::Itertools; -use std::ops::Range; use std::cell::Cell; +use std::ops::Range; use tui::{ backend::Backend, layout::Rect, style::Modifier, text::Span, text::Spans, widgets::Clear, Frame, @@ -58,7 +58,8 @@ impl TextInputComponent { cursor_position: 0, current_line: 0, input_type: InputType::Multiline, - num_lines: Cell::new(0), + // tests need a few lines + num_lines: Cell::new(5), display_off: 0, } } @@ -342,7 +343,7 @@ impl Component for TextInputComponent { if let Event::Key(e) = ev { let is_ctrl = e.modifiers.contains(KeyModifiers::CONTROL); - if e.code == KeyCode::Enter && is_ctrl { + if (e.code == KeyCode::Enter || e.code == KeyCode::Char('j')) && is_ctrl { self.split_line(); return Ok(true); } From 894f28ef68fbf181a4c6a2fdff01522524132a93 Mon Sep 17 00:00:00 2001 From: pm100 Date: Mon, 2 Nov 2020 17:51:51 -0800 Subject: [PATCH 8/8] cargo fmt again --- src/components/textinput.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/textinput.rs b/src/components/textinput.rs index 5cd1b63da6..9ec0396680 100644 --- a/src/components/textinput.rs +++ b/src/components/textinput.rs @@ -343,7 +343,10 @@ impl Component for TextInputComponent { if let Event::Key(e) = ev { let is_ctrl = e.modifiers.contains(KeyModifiers::CONTROL); - if (e.code == KeyCode::Enter || e.code == KeyCode::Char('j')) && is_ctrl { + if (e.code == KeyCode::Enter + || e.code == KeyCode::Char('j')) + && is_ctrl + { self.split_line(); return Ok(true); }