aboutsummaryrefslogtreecommitdiff
path: root/src/input.rs
diff options
context:
space:
mode:
authorChristian Duerr <chrisduerr@users.noreply.github.com>2019-02-07 22:36:45 +0000
committerGitHub <noreply@github.com>2019-02-07 22:36:45 +0000
commit35efb4619c4b7a77b3c30de763856bc4441e236e (patch)
tree99aa70b58f97bd57c2a654e3177eae0ea0b572fa /src/input.rs
parente561ae373393e919cf3dbbf123a98e0aad215dbc (diff)
downloadalacritty-35efb4619c4b7a77b3c30de763856bc4441e236e.tar.gz
alacritty-35efb4619c4b7a77b3c30de763856bc4441e236e.zip
Dynamically resize terminal for errors/warnings
The warning and error messages now don't overwrite other terminal content anymore but instead resize the terminal to make sure that text can always be read. Instead of just showing that there is a new error and pointing to the log, errors will now be displayed fully in multiple lines of text, assuming that there is enough space left in the terminal. Explicit mouse click handling has also been added to the message bar, which made it possible to add a simple `close` button in the form of `[X]`. Alacritty's log file location is now stored in the `$ALACRITTY_LOG` environment variable which the shell inherits automatically. Previously there were some issues with the log file only being deleted when certain methods for closing Alacritty were used (like typing `exit`). This has been reworked and now Ctrl+D, exit and signals should all work properly. Before the config is reloaded, all current messages are now dropped. This should help with multiple terminals all getting clogged up at the same time when the config is broken. When one message is removed, all other duplicate messages are automatically removed too.
Diffstat (limited to 'src/input.rs')
-rw-r--r--src/input.rs233
1 files changed, 151 insertions, 82 deletions
diff --git a/src/input.rs b/src/input.rs
index c77a9f49..26a12de6 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -29,10 +29,12 @@ use crate::config::{self, Key};
use crate::grid::Scroll;
use crate::event::{ClickState, Mouse};
use crate::index::{Line, Column, Side, Point};
-use crate::term::SizeInfo;
+use crate::term::{Term, SizeInfo, Search};
use crate::term::mode::TermMode;
use crate::util::fmt::Red;
use crate::util::start_daemon;
+use crate::message_bar;
+use crate::ansi::{Handler, ClearMode};
pub const FONT_SIZE_STEP: f32 = 0.5;
@@ -54,7 +56,6 @@ pub struct Processor<'a, A: 'a> {
pub trait ActionContext {
fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, _: B);
- fn terminal_mode(&self) -> TermMode;
fn size_info(&self) -> SizeInfo;
fn copy_selection(&self, _: ClipboardBuffer);
fn clear_selection(&mut self);
@@ -69,13 +70,10 @@ pub trait ActionContext {
fn received_count(&mut self) -> &mut usize;
fn suppress_chars(&mut self) -> &mut bool;
fn last_modifiers(&mut self) -> &mut ModifiersState;
- fn change_font_size(&mut self, delta: f32);
- fn reset_font_size(&mut self);
fn scroll(&mut self, scroll: Scroll);
- fn clear_history(&mut self);
fn hide_window(&mut self);
- fn url(&self, _: Point<usize>) -> Option<String>;
- fn clear_log(&mut self);
+ fn terminal(&self) -> &Term;
+ fn terminal_mut(&mut self) -> &mut Term;
fn spawn_new_instance(&mut self);
}
@@ -296,17 +294,16 @@ impl Action {
ctx.hide_window();
},
Action::Quit => {
- // FIXME should do a more graceful shutdown
- ::std::process::exit(0);
+ ctx.terminal_mut().exit();
},
Action::IncreaseFontSize => {
- ctx.change_font_size(FONT_SIZE_STEP);
+ ctx.terminal_mut().change_font_size(FONT_SIZE_STEP);
},
Action::DecreaseFontSize => {
- ctx.change_font_size(-FONT_SIZE_STEP);
+ ctx.terminal_mut().change_font_size(-FONT_SIZE_STEP);
}
Action::ResetFontSize => {
- ctx.reset_font_size();
+ ctx.terminal_mut().reset_font_size();
},
Action::ScrollPageUp => {
ctx.scroll(Scroll::PageUp);
@@ -321,10 +318,10 @@ impl Action {
ctx.scroll(Scroll::Bottom);
},
Action::ClearHistory => {
- ctx.clear_history();
+ ctx.terminal_mut().clear_screen(ClearMode::Saved);
},
Action::ClearLogNotice => {
- ctx.clear_log();
+ ctx.terminal_mut().message_buffer_mut().pop();
},
Action::SpawnNewInstance => {
ctx.spawn_new_instance();
@@ -334,7 +331,7 @@ impl Action {
}
fn paste<A: ActionContext>(&self, ctx: &mut A, contents: &str) {
- if ctx.terminal_mode().contains(TermMode::BRACKETED_PASTE) {
+ if ctx.terminal().mode().contains(TermMode::BRACKETED_PASTE) {
ctx.write_to_pty(&b"\x1b[200~"[..]);
ctx.write_to_pty(contents.replace("\x1b","").into_bytes());
ctx.write_to_pty(&b"\x1b[201~"[..]);
@@ -396,8 +393,13 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
self.ctx.mouse_mut().block_url_launcher = true;
}
+ // Ignore motions over the message bar
+ if self.mouse_over_message_bar(point) {
+ return;
+ }
+
if self.ctx.mouse().left_button_state == ElementState::Pressed
- && (modifiers.shift || !self.ctx.terminal_mode().intersects(report_mode))
+ && (modifiers.shift || !self.ctx.terminal().mode().intersects(report_mode))
{
self.ctx.update_selection(
Point {
@@ -406,7 +408,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
},
cell_side,
);
- } else if self.ctx.terminal_mode().intersects(motion_mode)
+ } else if self.ctx.terminal().mode().intersects(motion_mode)
// Only report motion when changing cells
&& (prev_line != self.ctx.mouse().line || prev_col != self.ctx.mouse().column)
&& size_info.contains_point(x, y)
@@ -417,7 +419,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
self.mouse_report(33, ElementState::Pressed, modifiers);
} else if self.ctx.mouse().right_button_state == ElementState::Pressed {
self.mouse_report(34, ElementState::Pressed, modifiers);
- } else if self.ctx.terminal_mode().contains(TermMode::MOUSE_MOTION) {
+ } else if self.ctx.terminal().mode().contains(TermMode::MOUSE_MOTION) {
self.mouse_report(35, ElementState::Pressed, modifiers);
}
}
@@ -430,7 +432,8 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
let cell_x = x.saturating_sub(size_info.padding_x as usize) % size_info.cell_width as usize;
let half_cell_width = (size_info.cell_width / 2.0) as usize;
- let additional_padding = (size_info.width - size_info.padding_x * 2.) % size_info.cell_width;
+ let additional_padding =
+ (size_info.width - size_info.padding_x * 2.) % size_info.cell_width;
let end_of_grid = size_info.width - size_info.padding_x - additional_padding;
if cell_x > half_cell_width
@@ -485,7 +488,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
}
// Report mouse events
- if self.ctx.terminal_mode().contains(TermMode::SGR_MOUSE) {
+ if self.ctx.terminal().mode().contains(TermMode::SGR_MOUSE) {
self.sgr_mouse_report(button + mods, state);
} else if let ElementState::Released = state {
self.normal_mouse_report(3 + mods);
@@ -494,19 +497,24 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
}
}
- pub fn on_mouse_double_click(&mut self, button: MouseButton) {
- if let (Some(point) , MouseButton::Left) = (self.ctx.mouse_coords(), button) {
+ pub fn on_mouse_double_click(&mut self, button: MouseButton, point: Point) {
+ if button == MouseButton::Left {
self.ctx.semantic_selection(point);
}
}
- pub fn on_mouse_triple_click(&mut self, button: MouseButton) {
- if let (Some(point), MouseButton::Left) = (self.ctx.mouse_coords(), button) {
+ pub fn on_mouse_triple_click(&mut self, button: MouseButton, point: Point) {
+ if button == MouseButton::Left {
self.ctx.line_selection(point);
}
}
- pub fn on_mouse_press(&mut self, button: MouseButton, modifiers: ModifiersState) {
+ pub fn on_mouse_press(
+ &mut self,
+ button: MouseButton,
+ modifiers: ModifiersState,
+ point: Point,
+ ) {
let now = Instant::now();
let elapsed = self.ctx.mouse().last_click_timestamp.elapsed();
self.ctx.mouse_mut().last_click_timestamp = now;
@@ -518,14 +526,14 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
if !button_changed && elapsed < self.mouse_config.double_click.threshold =>
{
self.ctx.mouse_mut().block_url_launcher = true;
- self.on_mouse_double_click(button);
+ self.on_mouse_double_click(button, point);
ClickState::DoubleClick
},
ClickState::DoubleClick
if !button_changed && elapsed < self.mouse_config.triple_click.threshold =>
{
self.ctx.mouse_mut().block_url_launcher = true;
- self.on_mouse_triple_click(button);
+ self.on_mouse_triple_click(button, point);
ClickState::TripleClick
},
_ => {
@@ -535,20 +543,20 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
self.ctx.clear_selection();
// Start new empty selection
- if let Some(point) = self.ctx.mouse_coords() {
- let side = self.ctx.mouse().cell_side;
- self.ctx.simple_selection(point, side);
- }
-
- let report_modes = TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION;
- if !modifiers.shift && self.ctx.terminal_mode().intersects(report_modes) {
- match button {
- MouseButton::Left => self.mouse_report(0, ElementState::Pressed, modifiers),
- MouseButton::Middle => self.mouse_report(1, ElementState::Pressed, modifiers),
- MouseButton::Right => self.mouse_report(2, ElementState::Pressed, modifiers),
+ let side = self.ctx.mouse().cell_side;
+ self.ctx.simple_selection(point, side);
+
+ let report_modes =
+ TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION;
+ if !modifiers.shift && self.ctx.terminal().mode().intersects(report_modes) {
+ let code = match button {
+ MouseButton::Left => 0,
+ MouseButton::Middle => 1,
+ MouseButton::Right => 2,
// Can't properly report more than three buttons.
- MouseButton::Other(_) => (),
+ MouseButton::Other(_) => return,
};
+ self.mouse_report(code, ElementState::Pressed, modifiers);
return;
}
@@ -557,38 +565,40 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
};
}
- pub fn on_mouse_release(&mut self, button: MouseButton, modifiers: ModifiersState) {
- let report_modes = TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION;
- if !modifiers.shift && self.ctx.terminal_mode().intersects(report_modes)
- {
- match button {
- MouseButton::Left => self.mouse_report(0, ElementState::Released, modifiers),
- MouseButton::Middle => self.mouse_report(1, ElementState::Released, modifiers),
- MouseButton::Right => self.mouse_report(2, ElementState::Released, modifiers),
+ pub fn on_mouse_release(
+ &mut self,
+ button: MouseButton,
+ modifiers: ModifiersState,
+ point: Point,
+ ) {
+ let report_modes =
+ TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION;
+ if !modifiers.shift && self.ctx.terminal().mode().intersects(report_modes) {
+ let code = match button {
+ MouseButton::Left => 0,
+ MouseButton::Middle => 1,
+ MouseButton::Right => 2,
// Can't properly report more than three buttons.
- MouseButton::Other(_) => (),
+ MouseButton::Other(_) => return,
};
+ self.mouse_report(code, ElementState::Released, modifiers);
return;
} else if button == MouseButton::Left {
- self.launch_url(modifiers);
+ self.launch_url(modifiers, point);
}
- if self.save_to_clipboard {
- self.ctx.copy_selection(ClipboardBuffer::Primary);
- }
- self.ctx.copy_selection(ClipboardBuffer::Selection);
+ self.copy_selection();
}
// Spawn URL launcher when clicking on URLs
- fn launch_url(&self, modifiers: ModifiersState) -> Option<()> {
+ fn launch_url(&self, modifiers: ModifiersState, point: Point) -> Option<()> {
if !self.mouse_config.url.modifiers.relaxed_eq(modifiers)
|| self.ctx.mouse().block_url_launcher
{
return None;
}
- let point = self.ctx.mouse_coords()?;
- let text = self.ctx.url(point.into())?;
+ let text = self.ctx.terminal().url_search(point.into())?;
let launcher = self.mouse_config.url.launcher.as_ref()?;
let mut args = launcher.args().to_vec();
@@ -602,7 +612,12 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
Some(())
}
- pub fn on_mouse_wheel(&mut self, delta: MouseScrollDelta, phase: TouchPhase, modifiers: ModifiersState) {
+ pub fn on_mouse_wheel(
+ &mut self,
+ delta: MouseScrollDelta,
+ phase: TouchPhase,
+ modifiers: ModifiersState,
+ ) {
match delta {
MouseScrollDelta::LineDelta(_columns, lines) => {
let new_scroll_px = lines * self.ctx.size_info().cell_height;
@@ -634,7 +649,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
.faux_scrollback_lines
.unwrap_or(self.scrolling_config.faux_multiplier as usize);
- if self.ctx.terminal_mode().intersects(mouse_modes) {
+ if self.ctx.terminal().mode().intersects(mouse_modes) {
self.ctx.mouse_mut().scroll_px += new_scroll_px;
let code = if new_scroll_px > 0 { 64 } else { 65 };
@@ -643,7 +658,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
for _ in 0..lines {
self.mouse_report(code, ElementState::Pressed, modifiers);
}
- } else if self.ctx.terminal_mode().contains(TermMode::ALT_SCREEN)
+ } else if self.ctx.terminal().mode().contains(TermMode::ALT_SCREEN)
&& faux_multiplier > 0
&& !modifiers.shift
{
@@ -672,7 +687,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
}
pub fn on_focus_change(&mut self, is_focused: bool) {
- if self.ctx.terminal_mode().contains(TermMode::FOCUS_IN_OUT) {
+ if self.ctx.terminal().mode().contains(TermMode::FOCUS_IN_OUT) {
let chr = if is_focused {
"I"
} else {
@@ -684,7 +699,12 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
}
}
- pub fn mouse_input(&mut self, state: ElementState, button: MouseButton, modifiers: ModifiersState) {
+ pub fn mouse_input(
+ &mut self,
+ state: ElementState,
+ button: MouseButton,
+ modifiers: ModifiersState
+ ) {
match button {
MouseButton::Left => self.ctx.mouse_mut().left_button_state = state,
MouseButton::Middle => self.ctx.mouse_mut().middle_button_state = state,
@@ -692,12 +712,22 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
_ => (),
}
- match state {
- ElementState::Pressed => {
- self.process_mouse_bindings(modifiers, button);
- self.on_mouse_press(button, modifiers);
- },
- ElementState::Released => self.on_mouse_release(button, modifiers),
+ let point = match self.ctx.mouse_coords() {
+ Some(point) => point,
+ None => return,
+ };
+
+ // Skip normal mouse events if the message bar has been clicked
+ if self.mouse_over_message_bar(point) {
+ self.on_message_bar_click(state, point);
+ } else {
+ match state {
+ ElementState::Pressed => {
+ self.process_mouse_bindings(modifiers, button);
+ self.on_mouse_press(button, modifiers, point);
+ },
+ ElementState::Released => self.on_mouse_release(button, modifiers, point),
+ }
}
self.ctx.mouse_mut().last_button = button;
@@ -759,14 +789,19 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
for binding in self.key_bindings {
let is_triggered = match binding.trigger {
Key::Scancode(_) => binding.is_triggered_by(
- self.ctx.terminal_mode(),
+ *self.ctx.terminal().mode(),
input.modifiers,
&Key::Scancode(input.scancode),
false,
),
_ => if let Some(key) = input.virtual_keycode {
let key = Key::from_glutin_input(key);
- binding.is_triggered_by(self.ctx.terminal_mode(), input.modifiers, &key, false)
+ binding.is_triggered_by(
+ *self.ctx.terminal().mode(),
+ input.modifiers,
+ &key,
+ false
+ )
} else {
false
},
@@ -791,9 +826,9 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
fn process_mouse_bindings(&mut self, mods: ModifiersState, button: MouseButton) -> bool {
let mut has_binding = false;
for binding in self.mouse_bindings {
- if binding.is_triggered_by(self.ctx.terminal_mode(), mods, &button, true) {
+ if binding.is_triggered_by(*self.ctx.terminal().mode(), mods, &button, true) {
// binding was triggered; run the action
- let mouse_mode = !mods.shift && self.ctx.terminal_mode().intersects(
+ let mouse_mode = !mods.shift && self.ctx.terminal().mode().intersects(
TermMode::MOUSE_REPORT_CLICK
| TermMode::MOUSE_DRAG
| TermMode::MOUSE_MOTION
@@ -805,6 +840,43 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
has_binding
}
+
+ /// Check if a point is within the message bar
+ fn mouse_over_message_bar(&mut self, point: Point) -> bool {
+ if let Some(message) = self.ctx.terminal_mut().message_buffer_mut().message() {
+ let size = self.ctx.size_info();
+ point.line.0 >= size.lines().saturating_sub(message.text(&size).len())
+ } else {
+ false
+ }
+ }
+
+ /// Handle clicks on the message bar.
+ fn on_message_bar_click(&mut self, button_state: ElementState, point: Point) {
+ match button_state {
+ ElementState::Released => self.copy_selection(),
+ ElementState::Pressed => {
+ let size = self.ctx.size_info();
+ if let Some(message) = self.ctx.terminal_mut().message_buffer_mut().message() {
+ if point.col + message_bar::CLOSE_BUTTON_TEXT.len() >= size.cols()
+ && point.line == size.lines() - message.text(&size).len()
+ {
+ self.ctx.terminal_mut().message_buffer_mut().pop();
+ }
+ }
+
+ self.ctx.clear_selection();
+ }
+ }
+ }
+
+ /// Copy text selection.
+ fn copy_selection(&mut self) {
+ if self.save_to_clipboard {
+ self.ctx.copy_selection(ClipboardBuffer::Primary);
+ }
+ self.ctx.copy_selection(ClipboardBuffer::Selection);
+ }
}
#[cfg(test)]
@@ -820,6 +892,7 @@ mod tests {
use crate::index::{Point, Side};
use crate::selection::Selection;
use crate::grid::Scroll;
+ use crate::message_bar::MessageBuffer;
use super::{Action, Binding, Processor};
use copypasta::Buffer as ClipboardBuffer;
@@ -851,15 +924,15 @@ mod tests {
fn simple_selection(&mut self, _point: Point, _side: Side) {}
fn copy_selection(&self, _buffer: ClipboardBuffer) {}
fn clear_selection(&mut self) {}
- fn change_font_size(&mut self, _delta: f32) {}
- fn reset_font_size(&mut self) {}
- fn clear_history(&mut self) {}
- fn clear_log(&mut self) {}
fn hide_window(&mut self) {}
fn spawn_new_instance(&mut self) {}
- fn terminal_mode(&self) -> TermMode {
- *self.terminal.mode()
+ fn terminal(&self) -> &Term {
+ &self.terminal
+ }
+
+ fn terminal_mut(&mut self) -> &mut Term {
+ &mut self.terminal
}
fn size_info(&self) -> SizeInfo {
@@ -897,10 +970,6 @@ mod tests {
self.mouse
}
- fn url(&self, _: Point<usize>) -> Option<String> {
- None
- }
-
fn received_count(&mut self) -> &mut usize {
&mut self.received_count
}
@@ -936,7 +1005,7 @@ mod tests {
dpr: 1.0,
};
- let mut terminal = Term::new(&config, size);
+ let mut terminal = Term::new(&config, size, MessageBuffer::new());
let mut mouse = Mouse::default();
mouse.click_state = $initial_state;