parent
3c575c823d
commit
06b96449c1
|
@ -448,6 +448,7 @@ impl ThreadView {
|
|||
self.highlight_line(grid, dest_area, src_area, idx);
|
||||
if rows < visibles.len() {
|
||||
ScrollBar::draw(
|
||||
ScrollBar::default(),
|
||||
grid,
|
||||
(
|
||||
upper_left!(area),
|
||||
|
@ -500,6 +501,7 @@ impl ThreadView {
|
|||
self.highlight_line(grid, dest_area, src_area, entry_idx);
|
||||
if rows < visibles.len() {
|
||||
ScrollBar::draw(
|
||||
ScrollBar::default(),
|
||||
grid,
|
||||
(
|
||||
upper_left!(area),
|
||||
|
|
|
@ -561,12 +561,15 @@ pub struct StatusBar {
|
|||
container: Box<Component>,
|
||||
status: String,
|
||||
notifications: VecDeque<String>,
|
||||
ex_buffer: String,
|
||||
ex_buffer: Field,
|
||||
display_buffer: String,
|
||||
mode: UIMode,
|
||||
height: usize,
|
||||
dirty: bool,
|
||||
id: ComponentId,
|
||||
|
||||
auto_complete: AutoComplete,
|
||||
cmd_history: Vec<String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for StatusBar {
|
||||
|
@ -582,12 +585,15 @@ impl StatusBar {
|
|||
container,
|
||||
status: String::with_capacity(256),
|
||||
notifications: VecDeque::new(),
|
||||
ex_buffer: String::with_capacity(256),
|
||||
ex_buffer: Field::Text(UText::new(String::with_capacity(256)), None),
|
||||
display_buffer: String::with_capacity(8),
|
||||
dirty: true,
|
||||
mode: UIMode::Normal,
|
||||
height: 1,
|
||||
id: ComponentId::new_v4(),
|
||||
auto_complete: AutoComplete::new(Vec::new()),
|
||||
|
||||
cmd_history: Vec::new(),
|
||||
}
|
||||
}
|
||||
fn draw_status_bar(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
|
@ -628,7 +634,7 @@ impl StatusBar {
|
|||
fn draw_execute_bar(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
clear_area(grid, area);
|
||||
write_string_to_grid(
|
||||
&self.ex_buffer,
|
||||
self.ex_buffer.as_str(),
|
||||
grid,
|
||||
Color::Byte(219),
|
||||
Color::Byte(88),
|
||||
|
@ -683,6 +689,160 @@ impl Component for StatusBar {
|
|||
),
|
||||
context,
|
||||
);
|
||||
/* don't autocomplete for less than 3 characters */
|
||||
if self.ex_buffer.as_str().split_graphemes().len() <= 2 {
|
||||
self.container.set_dirty();
|
||||
return;
|
||||
}
|
||||
let suggestions: Vec<String> = self
|
||||
.cmd_history
|
||||
.iter()
|
||||
.filter_map(|h| {
|
||||
if h.starts_with(self.ex_buffer.as_str()) {
|
||||
Some(h.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
if suggestions.is_empty() {
|
||||
/* redraw self.container because we might have got ridden of an autocomplete
|
||||
* box, and it must be drawn over */
|
||||
self.container.set_dirty();
|
||||
return;
|
||||
}
|
||||
/* redraw self.container because we have less suggestions than before */
|
||||
if suggestions.len() < self.auto_complete.suggestions().len() {
|
||||
self.container.set_dirty();
|
||||
}
|
||||
|
||||
if self.auto_complete.set_suggestions(suggestions) {
|
||||
let len = self.auto_complete.suggestions().len() - 1;
|
||||
self.auto_complete.set_cursor(len);
|
||||
}
|
||||
let hist_height = std::cmp::min(15, self.auto_complete.suggestions().len());
|
||||
let hist_area = if height < self.auto_complete.suggestions().len() {
|
||||
let mut scrollbar = ScrollBar::default();
|
||||
scrollbar.set_show_arrows(false);
|
||||
scrollbar.set_block_character(Some('▌'));
|
||||
scrollbar.draw(
|
||||
grid,
|
||||
(
|
||||
(
|
||||
get_x(upper_left),
|
||||
get_y(bottom_right) - height - hist_height + 1,
|
||||
),
|
||||
set_y(bottom_right, get_y(bottom_right) - height),
|
||||
),
|
||||
self.auto_complete.cursor(),
|
||||
hist_height,
|
||||
self.auto_complete.suggestions().len(),
|
||||
);
|
||||
change_colors(
|
||||
grid,
|
||||
(
|
||||
(
|
||||
get_x(upper_left),
|
||||
get_y(bottom_right) - height - hist_height + 1,
|
||||
),
|
||||
set_y(bottom_right, get_y(bottom_right) - height),
|
||||
),
|
||||
Color::Byte(197), // DeepPink2,
|
||||
Color::Byte(174), //LightPink3
|
||||
);
|
||||
context.dirty_areas.push_back((
|
||||
(
|
||||
get_x(upper_left),
|
||||
get_y(bottom_right) - height - hist_height + 1,
|
||||
),
|
||||
set_y(bottom_right, get_y(bottom_right) - height),
|
||||
));
|
||||
(
|
||||
(
|
||||
get_x(upper_left) + 1,
|
||||
get_y(bottom_right) - height - hist_height + 1,
|
||||
),
|
||||
set_y(bottom_right, get_y(bottom_right) - height),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
(
|
||||
get_x(upper_left),
|
||||
get_y(bottom_right) - height - hist_height + 1,
|
||||
),
|
||||
set_y(bottom_right, get_y(bottom_right) - height),
|
||||
)
|
||||
};
|
||||
let offset = if hist_height
|
||||
> (self.auto_complete.suggestions().len() - self.auto_complete.cursor())
|
||||
{
|
||||
self.auto_complete.suggestions().len() - hist_height
|
||||
} else {
|
||||
self.auto_complete.cursor()
|
||||
};
|
||||
clear_area(grid, hist_area);
|
||||
change_colors(
|
||||
grid,
|
||||
hist_area,
|
||||
Color::Byte(88), // DarkRed,
|
||||
Color::Byte(174), //LightPink3
|
||||
);
|
||||
for (y_offset, s) in self
|
||||
.auto_complete
|
||||
.suggestions()
|
||||
.iter()
|
||||
.skip(offset)
|
||||
.take(hist_height)
|
||||
.enumerate()
|
||||
{
|
||||
write_string_to_grid(
|
||||
s.as_str(),
|
||||
grid,
|
||||
Color::Byte(88), // DarkRed,
|
||||
Color::Byte(174), //LightPink3
|
||||
(
|
||||
set_y(
|
||||
upper_left!(hist_area),
|
||||
get_y(bottom_right!(hist_area)) - hist_height + y_offset + 1,
|
||||
),
|
||||
bottom_right!(hist_area),
|
||||
),
|
||||
true,
|
||||
);
|
||||
if y_offset + offset == self.auto_complete.cursor() {
|
||||
change_colors(
|
||||
grid,
|
||||
(
|
||||
set_y(
|
||||
upper_left!(hist_area),
|
||||
get_y(bottom_right!(hist_area)) - hist_height + y_offset + 1,
|
||||
),
|
||||
set_y(
|
||||
bottom_right!(hist_area),
|
||||
get_y(bottom_right!(hist_area)) - hist_height + y_offset + 1,
|
||||
),
|
||||
),
|
||||
Color::Byte(88), // DarkRed,
|
||||
Color::Byte(173), //LightSalmon3
|
||||
);
|
||||
write_string_to_grid(
|
||||
&s.as_str()[self.ex_buffer.as_str().len()..],
|
||||
grid,
|
||||
Color::Byte(97), // MediumPurple3,
|
||||
Color::Byte(88), //LightPink3
|
||||
(
|
||||
(
|
||||
get_y(upper_left)
|
||||
+ self.ex_buffer.as_str().split_graphemes().len(),
|
||||
get_y(bottom_right) - height + 1,
|
||||
),
|
||||
set_y(bottom_right, get_y(bottom_right) - height + 1),
|
||||
),
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
context.dirty_areas.push_back(hist_area);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -721,9 +881,15 @@ impl Component for StatusBar {
|
|||
if !self.ex_buffer.is_empty() {
|
||||
context
|
||||
.replies
|
||||
.push_back(UIEvent::Command(self.ex_buffer.clone()));
|
||||
.push_back(UIEvent::Command(self.ex_buffer.as_str().to_string()));
|
||||
}
|
||||
self.ex_buffer.clear()
|
||||
if parse_command(&self.ex_buffer.as_str().as_bytes())
|
||||
.to_full_result()
|
||||
.is_ok()
|
||||
{
|
||||
self.cmd_history.push(self.ex_buffer.as_str().to_string());
|
||||
}
|
||||
self.ex_buffer.clear();
|
||||
}
|
||||
UIMode::Execute => {
|
||||
self.height = 2;
|
||||
|
@ -731,9 +897,20 @@ impl Component for StatusBar {
|
|||
_ => {}
|
||||
};
|
||||
}
|
||||
UIEvent::ExInput(Key::Char('\t')) => {
|
||||
if let Some(suggestion) = self.auto_complete.get_suggestion() {
|
||||
let mut utext = UText::new(suggestion);
|
||||
let len = utext.as_str().len();
|
||||
utext.set_cursor(len);
|
||||
self.container.set_dirty();
|
||||
self.set_dirty();
|
||||
self.ex_buffer = Field::Text(utext, None);
|
||||
}
|
||||
}
|
||||
UIEvent::ExInput(Key::Char(c)) => {
|
||||
self.dirty = true;
|
||||
self.ex_buffer.push(*c);
|
||||
self.ex_buffer
|
||||
.process_event(&mut UIEvent::InsertInput(Key::Char(*c)), context);
|
||||
return true;
|
||||
}
|
||||
UIEvent::ExInput(Key::Ctrl('u')) => {
|
||||
|
@ -741,11 +918,20 @@ impl Component for StatusBar {
|
|||
self.ex_buffer.clear();
|
||||
return true;
|
||||
}
|
||||
UIEvent::ExInput(Key::Backspace) | UIEvent::ExInput(Key::Ctrl('h')) => {
|
||||
UIEvent::ExInput(k @ Key::Backspace) | UIEvent::ExInput(k @ Key::Ctrl('h')) => {
|
||||
self.dirty = true;
|
||||
self.ex_buffer.pop();
|
||||
self.ex_buffer
|
||||
.process_event(&mut UIEvent::InsertInput(k.clone()), context);
|
||||
return true;
|
||||
}
|
||||
UIEvent::ExInput(Key::Up) => {
|
||||
self.auto_complete.dec_cursor();
|
||||
self.dirty = true;
|
||||
}
|
||||
UIEvent::ExInput(Key::Down) => {
|
||||
self.auto_complete.inc_cursor();
|
||||
self.dirty = true;
|
||||
}
|
||||
UIEvent::Resize => {
|
||||
self.dirty = true;
|
||||
}
|
||||
|
|
|
@ -36,12 +36,12 @@ use Field::*;
|
|||
|
||||
impl Default for Field {
|
||||
fn default() -> Field {
|
||||
Field::Text(UText::new(String::new()), None)
|
||||
Field::Text(UText::new(String::with_capacity(256)), None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Field {
|
||||
fn as_str(&self) -> &str {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Text(ref s, _) => s.as_str(),
|
||||
Choice(ref v, cursor) => {
|
||||
|
@ -53,6 +53,9 @@ impl Field {
|
|||
}
|
||||
}
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.as_str().is_empty()
|
||||
}
|
||||
|
||||
pub fn into_string(self) -> String {
|
||||
match self {
|
||||
|
@ -61,7 +64,14 @@ impl Field {
|
|||
}
|
||||
}
|
||||
|
||||
fn draw_cursor(
|
||||
pub fn clear(&mut self) {
|
||||
match self {
|
||||
Text(s, _) => s.clear(),
|
||||
Choice(_, _) => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_cursor(
|
||||
&mut self,
|
||||
grid: &mut CellBuffer,
|
||||
area: Area,
|
||||
|
@ -168,7 +178,7 @@ impl Component for Field {
|
|||
}
|
||||
}
|
||||
}
|
||||
UIEvent::InsertInput(Key::Backspace) => {
|
||||
UIEvent::InsertInput(Key::Backspace) | UIEvent::InsertInput(Key::Ctrl('h')) => {
|
||||
if let Text(ref mut s, auto_complete) = self {
|
||||
s.backspace();
|
||||
if let Some(ac) = auto_complete.as_mut() {
|
||||
|
@ -565,7 +575,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct AutoComplete {
|
||||
entries: Vec<String>,
|
||||
content: CellBuffer,
|
||||
|
@ -639,9 +649,9 @@ impl AutoComplete {
|
|||
ret
|
||||
}
|
||||
|
||||
pub fn set_suggestions(&mut self, entries: Vec<String>) {
|
||||
pub fn set_suggestions(&mut self, entries: Vec<String>) -> bool {
|
||||
if entries.len() == self.entries.len() && entries == self.entries {
|
||||
return;
|
||||
return false;;
|
||||
}
|
||||
|
||||
let mut content = CellBuffer::new(
|
||||
|
@ -671,6 +681,7 @@ impl AutoComplete {
|
|||
self.content = content;
|
||||
self.entries = entries;
|
||||
self.cursor = 0;
|
||||
true
|
||||
}
|
||||
|
||||
pub fn inc_cursor(&mut self) {
|
||||
|
@ -684,6 +695,15 @@ impl AutoComplete {
|
|||
self.set_dirty();
|
||||
}
|
||||
|
||||
pub fn cursor(&self) -> usize {
|
||||
self.cursor
|
||||
}
|
||||
|
||||
pub fn set_cursor(&mut self, val: usize) {
|
||||
debug_assert!(val < self.entries.len());
|
||||
self.cursor = val;
|
||||
}
|
||||
|
||||
pub fn get_suggestion(&mut self) -> Option<String> {
|
||||
if self.entries.is_empty() {
|
||||
return None;
|
||||
|
@ -694,20 +714,43 @@ impl AutoComplete {
|
|||
self.content.empty();
|
||||
Some(ret)
|
||||
}
|
||||
|
||||
pub fn suggestions(&self) -> &Vec<String> {
|
||||
&self.entries
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ScrollBar();
|
||||
#[derive(Default)]
|
||||
pub struct ScrollBar {
|
||||
show_arrows: bool,
|
||||
block_character: Option<char>,
|
||||
}
|
||||
|
||||
impl ScrollBar {
|
||||
pub fn draw(grid: &mut CellBuffer, area: Area, pos: usize, visible_rows: usize, length: usize) {
|
||||
pub fn set_show_arrows(&mut self, flag: bool) {
|
||||
self.show_arrows = flag;
|
||||
}
|
||||
pub fn set_block_character(&mut self, val: Option<char>) {
|
||||
self.block_character = val;
|
||||
}
|
||||
pub fn draw(
|
||||
self,
|
||||
grid: &mut CellBuffer,
|
||||
area: Area,
|
||||
pos: usize,
|
||||
visible_rows: usize,
|
||||
length: usize,
|
||||
) {
|
||||
if length == 0 {
|
||||
return;
|
||||
}
|
||||
let height = height!(area);
|
||||
let mut height = height!(area);
|
||||
if height < 3 {
|
||||
return;
|
||||
}
|
||||
let height = height - 2;
|
||||
if self.show_arrows {
|
||||
height = height - 2;
|
||||
}
|
||||
clear_area(grid, area);
|
||||
|
||||
let visible_ratio: f32 = (std::cmp::min(visible_rows, length) as f32) / (length as f32);
|
||||
|
@ -720,10 +763,12 @@ impl ScrollBar {
|
|||
temp
|
||||
}
|
||||
};
|
||||
let (upper_left, bottom_right) = area;
|
||||
let (mut upper_left, bottom_right) = area;
|
||||
|
||||
grid[upper_left].set_ch('▴');
|
||||
let upper_left = (upper_left.0, upper_left.1 + 1);
|
||||
if self.show_arrows {
|
||||
grid[upper_left].set_ch('▴');
|
||||
upper_left = (upper_left.0, upper_left.1 + 1);
|
||||
}
|
||||
|
||||
for y in get_y(upper_left)..(get_y(upper_left) + scrollbar_offset) {
|
||||
grid[set_y(upper_left, y)].set_ch(' ');
|
||||
|
@ -731,12 +776,14 @@ impl ScrollBar {
|
|||
for y in (get_y(upper_left) + scrollbar_offset)
|
||||
..=(get_y(upper_left) + scrollbar_offset + scrollbar_height)
|
||||
{
|
||||
grid[set_y(upper_left, y)].set_ch('█');
|
||||
grid[set_y(upper_left, y)].set_ch(self.block_character.unwrap_or('█'));
|
||||
}
|
||||
for y in (get_y(upper_left) + scrollbar_offset + scrollbar_height + 1)..get_y(bottom_right)
|
||||
{
|
||||
grid[set_y(upper_left, y)].set_ch(' ');
|
||||
}
|
||||
grid[set_x(bottom_right, get_x(upper_left))].set_ch('▾');
|
||||
if self.show_arrows {
|
||||
grid[set_x(bottom_right, get_x(upper_left))].set_ch('▾');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub struct UText {
|
||||
content: String,
|
||||
cursor_pos: usize,
|
||||
|
@ -17,7 +17,7 @@ impl UText {
|
|||
}
|
||||
|
||||
pub fn set_cursor(&mut self, cursor_pos: usize) {
|
||||
if cursor_pos >= self.content.len() {
|
||||
if cursor_pos > self.content.len() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,12 @@ impl UText {
|
|||
self.content.as_str()
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.content.clear();
|
||||
self.cursor_pos = 0;
|
||||
self.grapheme_cursor_pos = 0;
|
||||
}
|
||||
|
||||
pub fn into_string(self) -> String {
|
||||
self.content
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue