summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorClement Tsang <34804052+ClementTsang@users.noreply.github.com>2022-11-10 00:40:04 -0500
committerGitHub <noreply@github.com>2022-11-10 00:40:04 -0500
commit938c4ccd5220ef7527e5ec1461f364a58d35d796 (patch)
tree5ce090b9b01adc31665a949640c27ceaa37785c6 /src
parent5f849e81e68c4df9143e834d976ef73776682eed (diff)
feature: search paste support (#881)
* feature: add pasting to search Supports pasting events to the search bar (e.g. shift-insert, ctrl-shift-v). * update docs * clippy * comment * Update process.md * remove keyboard event throttle * fix issues with cjk/flag characters
Diffstat (limited to 'src')
-rw-r--r--src/app.rs59
-rw-r--r--src/app/widgets/process_table.rs68
-rw-r--r--src/bin/main.rs13
-rw-r--r--src/lib.rs27
4 files changed, 142 insertions, 25 deletions
diff --git a/src/app.rs b/src/app.rs
index 4bab7b2a..ea34f259 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -4,7 +4,8 @@ use std::{
time::Instant,
};
-use unicode_segmentation::GraphemeCursor;
+use concat_string::concat_string;
+use unicode_segmentation::{GraphemeCursor, UnicodeSegmentation};
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
use typed_builder::*;
@@ -35,7 +36,7 @@ pub mod widgets;
use frozen_state::FrozenState;
-const MAX_SEARCH_LENGTH: usize = 200;
+const MAX_SEARCH_LENGTH: usize = 200; // FIXME: Remove this limit, it's unnecessary.
#[derive(Debug, Clone)]
pub enum AxisScaling {
@@ -2714,4 +2715,58 @@ impl App {
1 + self.app_config_fields.table_gap
}
}
+
+ /// A quick and dirty way to handle paste events.
+ pub fn handle_paste(&mut self, paste: String) {
+ // Partially copy-pasted from the single-char variant; should probably clean up this process in the future.
+ // In particular, encapsulate this entire logic and add some tests to make it less potentially error-prone.
+ let is_in_search_widget = self.is_in_search_widget();
+ if let Some(proc_widget_state) = self
+ .proc_state
+ .widget_states
+ .get_mut(&(self.current_widget.widget_id - 1))
+ {
+ let curr_width = UnicodeWidthStr::width(
+ proc_widget_state
+ .proc_search
+ .search_state
+ .current_search_query
+ .as_str(),
+ );
+ let paste_width = UnicodeWidthStr::width(paste.as_str());
+ let num_runes = UnicodeSegmentation::graphemes(paste.as_str(), true).count();
+
+ if is_in_search_widget
+ && proc_widget_state.is_search_enabled()
+ && curr_width + paste_width <= MAX_SEARCH_LENGTH
+ {
+ let paste_char_width = paste.len();
+ let left_bound = proc_widget_state.get_search_cursor_position();
+
+ let curr_query = &mut proc_widget_state
+ .proc_search
+ .search_state
+ .current_search_query;
+ let (left, right) = curr_query.split_at(left_bound);
+ *curr_query = concat_string!(left, paste, right);
+
+ proc_widget_state.proc_search.search_state.grapheme_cursor =
+ GraphemeCursor::new(left_bound, curr_query.len(), true);
+
+ for _ in 0..num_runes {
+ let cursor = proc_widget_state.get_search_cursor_position();
+ proc_widget_state.search_walk_forward(cursor);
+ }
+
+ proc_widget_state
+ .proc_search
+ .search_state
+ .char_cursor_position += paste_char_width;
+
+ proc_widget_state.update_query();
+ proc_widget_state.proc_search.search_state.cursor_direction =
+ CursorDirection::Right;
+ }
+ }
+ }
}
diff --git a/src/app/widgets/process_table.rs b/src/app/widgets/process_table.rs
index bb268356..970e1cc5 100644
--- a/src/app/widgets/process_table.rs
+++ b/src/app/widgets/process_table.rs
@@ -26,6 +26,7 @@ pub use proc_widget_data::*;
mod sort_table;
use sort_table::SortTableColumn;
+use unicode_segmentation::GraphemeIncomplete;
/// ProcessSearchState only deals with process' search's current settings and state.
pub struct ProcessSearchState {
@@ -775,25 +776,68 @@ impl ProcWidget {
}
pub fn search_walk_forward(&mut self, start_position: usize) {
- self.proc_search
+ // TODO: Add tests for this.
+ let chunk = &self.proc_search.search_state.current_search_query[start_position..];
+
+ match self
+ .proc_search
.search_state
.grapheme_cursor
- .next_boundary(
- &self.proc_search.search_state.current_search_query[start_position..],
- start_position,
- )
- .unwrap();
+ .next_boundary(chunk, start_position)
+ {
+ Ok(_) => {}
+ Err(err) => match err {
+ GraphemeIncomplete::PreContext(ctx) => {
+ // Provide the entire string as context. Not efficient but should resolve failures.
+ self.proc_search
+ .search_state
+ .grapheme_cursor
+ .provide_context(
+ &self.proc_search.search_state.current_search_query[0..ctx],
+ 0,
+ );
+
+ self.proc_search
+ .search_state
+ .grapheme_cursor
+ .next_boundary(chunk, start_position)
+ .unwrap();
+ }
+ _ => Err(err).unwrap(),
+ },
+ }
}
pub fn search_walk_back(&mut self, start_position: usize) {
- self.proc_search
+ // TODO: Add tests for this.
+ let chunk = &self.proc_search.search_state.current_search_query[..start_position];
+ match self
+ .proc_search
.search_state
.grapheme_cursor
- .prev_boundary(
- &self.proc_search.search_state.current_search_query[..start_position],
- 0,
- )
- .unwrap();
+ .prev_boundary(chunk, 0)
+ {
+ Ok(_) => {}
+ Err(err) => match err {
+ GraphemeIncomplete::PreContext(ctx) => {
+ // Provide the entire string as context. Not efficient but should resolve failures.
+ self.proc_search
+ .search_state
+ .grapheme_cursor
+ .provide_context(
+ &self.proc_search.search_state.current_search_query[0..ctx],
+ 0,
+ );
+
+ self.proc_search
+ .search_state
+ .grapheme_cursor
+ .prev_boundary(chunk, 0)
+ .unwrap();
+ }
+ _ => Err(err).unwrap(),
+ },
+ }
}
/// Returns the number of columns *enabled*. Note this differs from *visible* - a column may be enabled but not
diff --git a/src/bin/main.rs b/src/bin/main.rs
index a6a8a1a5..e615c5db 100644
--- a/src/bin/main.rs
+++ b/src/bin/main.rs
@@ -26,7 +26,7 @@ use std::{
use anyhow::{Context, Result};
use crossterm::{
- event::EnableMouseCapture,
+ event::{EnableBracketedPaste, EnableMouseCapture},
execute,
terminal::{enable_raw_mode, EnterAlternateScreen},
};
@@ -120,7 +120,12 @@ fn main() -> Result<()> {
// Set up up tui and crossterm
let mut stdout_val = stdout();
- execute!(stdout_val, EnterAlternateScreen, EnableMouseCapture)?;
+ execute!(
+ stdout_val,
+ EnterAlternateScreen,
+ EnableMouseCapture,
+ EnableBracketedPaste
+ )?;
enable_raw_mode()?;
let mut terminal = Terminal::new(CrosstermBackend::new(stdout_val))?;
@@ -151,6 +156,10 @@ fn main() -> Result<()> {
handle_mouse_event(event, &mut app);
update_data(&mut app);
}
+ BottomEvent::PasteEvent(paste) => {
+ app.handle_paste(paste);
+ update_data(&mut app);
+ }
BottomEvent::Update(data) => {
app.data_collection.eat_data(data);
diff --git a/src/lib.rs b/src/lib.rs
index 0ac83582..844a1efe 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -28,8 +28,8 @@ use std::{
use crossterm::{
event::{
- poll, read, DisableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent,
- MouseEventKind,
+ poll, read, DisableBracketedPaste, DisableMouseCapture, Event, KeyCode, KeyEvent,
+ KeyModifiers, MouseEvent, MouseEventKind,
},
execute,
style::Print,
@@ -71,6 +71,7 @@ pub type Pid = libc::pid_t;
pub enum BottomEvent<I, J> {
KeyInput(I),
MouseInput(J),
+ PasteEvent(String),
Update(Box<data_harvester::Data>),
Clean,
}
@@ -273,6 +274,7 @@ pub fn cleanup_terminal(
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
+ DisableBracketedPaste,
DisableMouseCapture,
LeaveAlternateScreen
)?;
@@ -311,7 +313,13 @@ pub fn panic_hook(panic_info: &PanicInfo<'_>) {
let stacktrace: String = format!("{:?}", backtrace::Backtrace::new());
disable_raw_mode().unwrap();
- execute!(stdout, DisableMouseCapture, LeaveAlternateScreen).unwrap();
+ execute!(
+ stdout,
+ DisableBracketedPaste,
+ DisableMouseCapture,
+ LeaveAlternateScreen
+ )
+ .unwrap();
// Print stack trace. Must be done after!
execute!(
@@ -410,7 +418,6 @@ pub fn create_input_thread(
) -> JoinHandle<()> {
thread::spawn(move || {
let mut mouse_timer = Instant::now();
- let mut keyboard_timer = Instant::now();
loop {
if let Ok(is_terminated) = termination_ctrl_lock.try_lock() {
@@ -425,12 +432,14 @@ pub fn create_input_thread(
if let Ok(event) = read() {
// FIXME: Handle all other event cases.
match event {
+ Event::Paste(paste) => {
+ if sender.send(BottomEvent::PasteEvent(paste)).is_err() {
+ break;
+ }
+ }
Event::Key(key) => {
- if Instant::now().duration_since(keyboard_timer).as_millis() >= 20 {
- if sender.send(BottomEvent::KeyInput(key)).is_err() {
- break;
- }
- keyboard_timer = Instant::now();
+ if sender.send(BottomEvent::KeyInput(key)).is_err() {
+ break;
}
}
Event::Mouse(mouse) => {