Skip to content

Commit b9ec27d

Browse files
author
Stephan Dilly
committed
fix newline visualization in commit msg editor (closes #169)
1 parent 26f734c commit b9ec27d

File tree

3 files changed

+112
-45
lines changed

3 files changed

+112
-45
lines changed

CHANGELOG.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
### Fixed
1010
- switch deprecated transitive dependency `net2`->`socket2` [in `crossterm`->`mio`] ([#66](https://github.com/extrawurst/gitui/issues/66))
11-
- crash diffing stash created on command line ([#178](https://github.com/extrawurst/gitui/issues/178))
12-
- delta file size diff on untracked binary files ([#171](https://github.com/extrawurst/gitui/issues/171))
11+
- crash diffing a stash that was created via cli ([#178](https://github.com/extrawurst/gitui/issues/178))
12+
- zero delta file size in diff of untracked binary file ([#171](https://github.com/extrawurst/gitui/issues/171))
13+
- newlines not visualized correctly in commit editor ([#169](https://github.com/extrawurst/gitui/issues/169))
14+
15+
![](assets/newlines.gif)
1316

1417
## [0.8.0] - 2020-07-06
1518

assets/newlines.gif

100 KB
Loading

src/components/textinput.rs

Lines changed: 107 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,13 @@ impl TextInputComponent {
8686
Some(index)
8787
}
8888

89+
fn backspace(&mut self) {
90+
if self.cursor_position > 0 {
91+
self.decr_cursor();
92+
self.msg.remove(self.cursor_position);
93+
}
94+
}
95+
8996
/// Set the `msg`.
9097
pub fn set_text(&mut self, msg: String) {
9198
self.msg = msg;
@@ -96,6 +103,54 @@ impl TextInputComponent {
96103
pub fn set_title(&mut self, t: String) {
97104
self.title = t;
98105
}
106+
107+
fn get_draw_text(&self) -> Vec<Text> {
108+
let style = self.theme.text(true, false);
109+
110+
let mut txt = Vec::new();
111+
112+
// the portion of the text before the cursor is added
113+
// if the cursor is not at the first character
114+
if self.cursor_position > 0 {
115+
txt.push(Text::styled(
116+
&self.msg[..self.cursor_position],
117+
style,
118+
));
119+
}
120+
121+
let cursor_str = if let Some(pos) = self.next_char_position()
122+
{
123+
&self.msg[self.cursor_position..pos]
124+
} else {
125+
// if the cursor is at the end of the msg
126+
// a whitespace is used to underline
127+
" "
128+
};
129+
130+
if cursor_str == "\n" {
131+
txt.push(Text::styled(
132+
"\u{21b5}",
133+
self.theme
134+
.text(false, false)
135+
.modifier(Modifier::UNDERLINED),
136+
));
137+
}
138+
139+
txt.push(Text::styled(
140+
cursor_str,
141+
style.modifier(Modifier::UNDERLINED),
142+
));
143+
144+
// the final portion of the text is added if there is
145+
// still remaining characters
146+
if let Some(pos) = self.next_char_position() {
147+
if pos < self.msg.len() {
148+
txt.push(Text::styled(&self.msg[pos..], style));
149+
}
150+
}
151+
152+
txt
153+
}
99154
}
100155

101156
impl DrawableComponent for TextInputComponent {
@@ -105,46 +160,13 @@ impl DrawableComponent for TextInputComponent {
105160
_rect: Rect,
106161
) -> Result<()> {
107162
if self.visible {
108-
let mut txt: Vec<tui::widgets::Text> = Vec::new();
109-
110-
if self.msg.is_empty() {
111-
txt.push(Text::styled(
163+
let txt = if self.msg.is_empty() {
164+
vec![Text::styled(
112165
self.default_msg.as_str(),
113166
self.theme.text(false, false),
114-
));
167+
)]
115168
} else {
116-
let style = self.theme.text(true, false);
117-
118-
// the portion of the text before the cursor is added
119-
// if the cursor is not at the first character
120-
if self.cursor_position > 0 {
121-
txt.push(Text::styled(
122-
&self.msg[..self.cursor_position],
123-
style,
124-
));
125-
}
126-
127-
txt.push(Text::styled(
128-
if let Some(pos) = self.next_char_position() {
129-
&self.msg[self.cursor_position..pos]
130-
} else {
131-
// if the cursor is at the end of the msg
132-
// a whitespace is used to underline
133-
" "
134-
},
135-
style.modifier(Modifier::UNDERLINED),
136-
));
137-
138-
// the final portion of the text is added if there is
139-
// still remaining characters
140-
if let Some(pos) = self.next_char_position() {
141-
if pos < self.msg.len() {
142-
txt.push(Text::styled(
143-
&self.msg[pos..],
144-
style,
145-
));
146-
}
147-
}
169+
self.get_draw_text()
148170
};
149171

150172
let area = ui::centered_rect(60, 20, f.size());
@@ -203,12 +225,7 @@ impl Component for TextInputComponent {
203225
return Ok(true);
204226
}
205227
KeyCode::Backspace => {
206-
if self.cursor_position > 0 {
207-
self.decr_cursor();
208-
if self.cursor_position < self.msg.len() {
209-
}
210-
self.msg.remove(self.cursor_position);
211-
}
228+
self.backspace();
212229
return Ok(true);
213230
}
214231
KeyCode::Left => {
@@ -248,3 +265,50 @@ impl Component for TextInputComponent {
248265
Ok(())
249266
}
250267
}
268+
269+
#[cfg(test)]
270+
mod tests {
271+
use super::*;
272+
273+
#[test]
274+
fn test_smoke() {
275+
let mut comp =
276+
TextInputComponent::new(SharedTheme::default(), "", "");
277+
278+
comp.set_text(String::from("a\nb"));
279+
280+
assert_eq!(comp.cursor_position, 0);
281+
282+
comp.incr_cursor();
283+
assert_eq!(comp.cursor_position, 1);
284+
285+
comp.decr_cursor();
286+
assert_eq!(comp.cursor_position, 0);
287+
}
288+
289+
fn get_text<'a>(t: &'a Text) -> Option<&'a str> {
290+
if let Text::Styled(c, _) = t {
291+
Some(c.as_ref())
292+
} else {
293+
None
294+
}
295+
}
296+
297+
#[test]
298+
fn test_visualize_newline() {
299+
let mut comp =
300+
TextInputComponent::new(SharedTheme::default(), "", "");
301+
302+
comp.set_text(String::from("a\nb"));
303+
304+
comp.incr_cursor();
305+
306+
let txt = comp.get_draw_text();
307+
308+
assert_eq!(txt.len(), 4);
309+
assert_eq!(get_text(&txt[0]), Some("a"));
310+
assert_eq!(get_text(&txt[1]), Some("\u{21b5}"));
311+
assert_eq!(get_text(&txt[2]), Some("\n"));
312+
assert_eq!(get_text(&txt[3]), Some("b"));
313+
}
314+
}

0 commit comments

Comments
 (0)