Skip to content

Commit 8c00238

Browse files
author
Stephan Dilly
committed
Merge branch 'master' into reset-hunk-#11
2 parents 25f7ea1 + 0cdaabf commit 8c00238

File tree

3 files changed

+109
-16
lines changed

3 files changed

+109
-16
lines changed

Diff for: CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
- file trees: `arrow-right` on expanded folder moves down into folder
1717
- better scrolling in diff ([#52](https://github.com/extrawurst/gitui/issues/52))
1818
- display current branch in status/log ([#115](https://github.com/extrawurst/gitui/issues/115))
19+
- commit msg popup: add cursor and more controls (`arrow-left/right`, `delete` & `backspace`) [@alistaircarscadden](https://github.com/alistaircarscadden)] ([#46](https://github.com/extrawurst/gitui/issues/46))
1920

2021
### Fixed
2122
- reset file inside folder failed when running `gitui` in a subfolder too ([#118](https://github.com/extrawurst/gitui/issues/118))

Diff for: src/components/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -205,4 +205,5 @@ where
205205
.border_style(theme.block(focused)),
206206
)
207207
.alignment(Alignment::Left)
208+
.wrap(true)
208209
}

Diff for: src/components/textinput.rs

+107-16
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@ use crate::{
88
};
99
use anyhow::Result;
1010
use crossterm::event::{Event, KeyCode, KeyModifiers};
11-
use std::borrow::Cow;
1211
use strings::commands;
1312
use tui::{
1413
backend::Backend,
1514
layout::Rect,
16-
style::Style,
15+
style::Modifier,
1716
widgets::{Clear, Text},
1817
Frame,
1918
};
@@ -25,6 +24,7 @@ pub struct TextInputComponent {
2524
msg: String,
2625
visible: bool,
2726
theme: Theme,
27+
cursor_position: usize,
2828
}
2929

3030
impl TextInputComponent {
@@ -40,25 +40,58 @@ impl TextInputComponent {
4040
theme: *theme,
4141
title: title.to_string(),
4242
default_msg: default_msg.to_string(),
43+
cursor_position: 0,
4344
}
4445
}
4546

46-
///
47+
/// Clear the `msg`.
4748
pub fn clear(&mut self) {
4849
self.msg.clear();
4950
}
5051

51-
///
52+
/// Get the `msg`.
5253
pub const fn get_text(&self) -> &String {
5354
&self.msg
5455
}
5556

56-
///
57+
/// Move the cursor right one char.
58+
fn incr_cursor(&mut self) {
59+
if let Some(pos) = self.next_char_position() {
60+
self.cursor_position = pos;
61+
}
62+
}
63+
64+
/// Move the cursor left one char.
65+
fn decr_cursor(&mut self) {
66+
let mut index = self.cursor_position.saturating_sub(1);
67+
while index > 0 && !self.msg.is_char_boundary(index) {
68+
index -= 1;
69+
}
70+
self.cursor_position = index;
71+
}
72+
73+
/// Get the position of the next char, or, if the cursor points
74+
/// to the last char, the `msg.len()`.
75+
/// Returns None when the cursor is already at `msg.len()`.
76+
fn next_char_position(&self) -> Option<usize> {
77+
if self.cursor_position >= self.msg.len() {
78+
return None;
79+
}
80+
let mut index = self.cursor_position.saturating_add(1);
81+
while index < self.msg.len()
82+
&& !self.msg.is_char_boundary(index)
83+
{
84+
index += 1;
85+
}
86+
Some(index)
87+
}
88+
89+
/// Set the `msg`.
5790
pub fn set_text(&mut self, msg: String) {
5891
self.msg = msg;
5992
}
6093

61-
///
94+
/// Set the `title`.
6295
pub fn set_title(&mut self, t: String) {
6396
self.title = t;
6497
}
@@ -71,16 +104,46 @@ impl DrawableComponent for TextInputComponent {
71104
_rect: Rect,
72105
) -> Result<()> {
73106
if self.visible {
74-
let txt = if self.msg.is_empty() {
75-
[Text::Styled(
76-
Cow::from(self.default_msg.as_str()),
107+
let mut txt: Vec<tui::widgets::Text> = Vec::new();
108+
109+
if self.msg.is_empty() {
110+
txt.push(Text::styled(
111+
self.default_msg.as_str(),
77112
self.theme.text(false, false),
78-
)]
113+
));
79114
} else {
80-
[Text::Styled(
81-
Cow::from(self.msg.clone()),
82-
Style::default(),
83-
)]
115+
let style = self.theme.text(true, false);
116+
117+
// the portion of the text before the cursor is added
118+
// if the cursor is not at the first character
119+
if self.cursor_position > 0 {
120+
txt.push(Text::styled(
121+
&self.msg[..self.cursor_position],
122+
style,
123+
));
124+
}
125+
126+
txt.push(Text::styled(
127+
if let Some(pos) = self.next_char_position() {
128+
&self.msg[self.cursor_position..pos]
129+
} else {
130+
// if the cursor is at the end of the msg
131+
// a whitespace is used to underline
132+
" "
133+
},
134+
style.modifier(Modifier::UNDERLINED),
135+
));
136+
137+
// the final portion of the text is added if there is
138+
// still remaining characters
139+
if let Some(pos) = self.next_char_position() {
140+
if pos < self.msg.len() {
141+
txt.push(Text::styled(
142+
&self.msg[pos..],
143+
style,
144+
));
145+
}
146+
}
84147
};
85148

86149
let area = ui::centered_rect(60, 20, f.size());
@@ -128,11 +191,39 @@ impl Component for TextInputComponent {
128191
return Ok(true);
129192
}
130193
KeyCode::Char(c) if !is_ctrl => {
131-
self.msg.push(c);
194+
self.msg.insert(self.cursor_position, c);
195+
self.incr_cursor();
196+
return Ok(true);
197+
}
198+
KeyCode::Delete => {
199+
if self.cursor_position < self.msg.len() {
200+
self.msg.remove(self.cursor_position);
201+
}
132202
return Ok(true);
133203
}
134204
KeyCode::Backspace => {
135-
self.msg.pop();
205+
if self.cursor_position > 0 {
206+
self.decr_cursor();
207+
if self.cursor_position < self.msg.len() {
208+
}
209+
self.msg.remove(self.cursor_position);
210+
}
211+
return Ok(true);
212+
}
213+
KeyCode::Left => {
214+
self.decr_cursor();
215+
return Ok(true);
216+
}
217+
KeyCode::Right => {
218+
self.incr_cursor();
219+
return Ok(true);
220+
}
221+
KeyCode::Home => {
222+
self.cursor_position = 0;
223+
return Ok(true);
224+
}
225+
KeyCode::End => {
226+
self.cursor_position = self.msg.len();
136227
return Ok(true);
137228
}
138229
_ => (),

0 commit comments

Comments
 (0)