Skip to content

Commit

Permalink
wezterm: refactor close confirmations
Browse files Browse the repository at this point in the history
Make pane, tab, window close confirmations use the same core function.

Make that function accept mouse input so that closing the window with
a mouse click doesn't require switching to the keyboard to confirm
the close.

refs: #280
  • Loading branch information
wez committed Dec 23, 2020
1 parent 5787bdf commit 3d81740
Showing 1 changed file with 140 additions and 108 deletions.
248 changes: 140 additions & 108 deletions wezterm-gui/src/gui/overlay/confirm_close_pane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,91 @@ use mux::tab::TabId;
use mux::termwiztermtab::TermWizTerminal;
use mux::window::WindowId;
use mux::Mux;
use termwiz::cell::AttributeChange;
use termwiz::color::ColorAttribute;
use termwiz::input::{InputEvent, KeyCode, KeyEvent};
use termwiz::surface::{Change, Position};
use termwiz::input::{InputEvent, KeyCode, KeyEvent, MouseButtons, MouseEvent};
use termwiz::surface::{Change, CursorVisibility,Position};
use termwiz::terminal::Terminal;

pub fn confirm_close_pane(
pane_id: PaneId,
mut term: TermWizTerminal,
mux_window_id: WindowId,
) -> anyhow::Result<()> {
fn run_confirmation_app(message: &str, term: &mut TermWizTerminal) -> anyhow::Result<bool> {
term.set_raw_mode()?;

let changes = vec![
Change::ClearScreen(ColorAttribute::Default),
Change::CursorPosition {
x: Position::Absolute(0),
y: Position::Absolute(0),
},
Change::Text("Really kill this pane? [y/n]\r\n".to_string()),
];
let size = term.get_screen_size()?;

// Render 80% wide, centered
let text_width = size.cols * 80 / 100;
let x_pos = size.cols * 10 / 100;

// Fit text to the width
let wrapped = textwrap::fill(message, text_width);

let message_rows = wrapped.split("\n").count();
// Now we want to vertically center the prompt in the view.
// After the prompt there will be a blank line and then the "buttons",
// so we add two to the number of rows.
let top_row = (size.rows - (message_rows + 2)) / 2;

let button_row = top_row + message_rows + 1;
let mut active = ActiveButton::None;

#[derive(Copy, Clone, PartialEq, Eq)]
enum ActiveButton {
None,
Yes,
No,
}

term.render(&changes)?;
term.flush()?;
let render = |term: &mut TermWizTerminal, active: ActiveButton| -> anyhow::Result<()> {
let mut changes = vec![
Change::ClearScreen(ColorAttribute::Default),
Change::CursorVisibility(CursorVisibility::Hidden),
];

for (y, row) in wrapped.split("\n").enumerate() {
let row = row.trim_end();
changes.push(Change::CursorPosition {
x: Position::Absolute(x_pos),
y: Position::Absolute(top_row + y),
});
changes.push(Change::Text(row.to_string()));
}

changes.push(Change::CursorPosition {
x: Position::Absolute(x_pos),
y: Position::Absolute(button_row),
});

if active == ActiveButton::Yes {
changes.push(AttributeChange::Reverse(true).into());
}
changes.push(" Yes ".into());
if active == ActiveButton::Yes {
changes.push(AttributeChange::Reverse(false).into());
}

changes.push(" ".into());

if active == ActiveButton::No {
changes.push(AttributeChange::Reverse(true).into());
}
changes.push(" No ".into());
if active == ActiveButton::No {
changes.push(AttributeChange::Reverse(false).into());
}

term.render(&changes)?;
term.flush()
};

render(term, active)?;

while let Ok(Some(event)) = term.poll_input(None) {
match event {
InputEvent::Key(KeyEvent {
key: KeyCode::Char('y'),
..
}) => {
promise::spawn::spawn_into_main_thread(async move {
let mux = Mux::get().unwrap();
let tab = match mux.get_active_tab_for_window(mux_window_id) {
Some(tab) => tab,
None => return,
};
tab.kill_pane(pane_id);
})
.detach();
break;
return Ok(true);
}
InputEvent::Key(KeyEvent {
key: KeyCode::Char('n'),
Expand All @@ -52,10 +97,59 @@ pub fn confirm_close_pane(
key: KeyCode::Escape,
..
}) => {
break;
return Ok(false);
}
InputEvent::Mouse(MouseEvent {
x,
y,
mouse_buttons,
..
}) => {
let x = x as usize;
let y = y as usize;
if y == button_row && x >= x_pos && x <= x_pos + 5 {
active = ActiveButton::Yes;
if mouse_buttons == MouseButtons::LEFT {
return Ok(true);
}
} else if y == button_row && x >= x_pos + 14 && x <= x_pos + 20 {
active = ActiveButton::No;
if mouse_buttons == MouseButtons::LEFT {
return Ok(false);
}
} else {
active = ActiveButton::None;
}

if mouse_buttons != MouseButtons::NONE {
// Treat any other mouse button as cancel
return Ok(false);
}
}
_ => {}
}

render(term, active)?;
}

Ok(false)
}

pub fn confirm_close_pane(
pane_id: PaneId,
mut term: TermWizTerminal,
mux_window_id: WindowId,
) -> anyhow::Result<()> {
if run_confirmation_app("🛑 Really kill this pane?", &mut term)? {
promise::spawn::spawn_into_main_thread(async move {
let mux = Mux::get().unwrap();
let tab = match mux.get_active_tab_for_window(mux_window_id) {
Some(tab) => tab,
None => return,
};
tab.kill_pane(pane_id);
})
.detach();
}

Ok(())
Expand All @@ -66,45 +160,15 @@ pub fn confirm_close_tab(
mut term: TermWizTerminal,
_mux_window_id: WindowId,
) -> anyhow::Result<()> {
term.set_raw_mode()?;

let changes = vec![
Change::ClearScreen(ColorAttribute::Default),
Change::CursorPosition {
x: Position::Absolute(0),
y: Position::Absolute(0),
},
Change::Text("Really kill this tab and all contained panes? [y/n]\r\n".to_string()),
];

term.render(&changes)?;
term.flush()?;

while let Ok(Some(event)) = term.poll_input(None) {
match event {
InputEvent::Key(KeyEvent {
key: KeyCode::Char('y'),
..
}) => {
promise::spawn::spawn_into_main_thread(async move {
let mux = Mux::get().unwrap();
mux.remove_tab(tab_id);
})
.detach();
break;
}
InputEvent::Key(KeyEvent {
key: KeyCode::Char('n'),
..
})
| InputEvent::Key(KeyEvent {
key: KeyCode::Escape,
..
}) => {
break;
}
_ => {}
}
if run_confirmation_app(
"🛑 Really kill this tab and all contained panes?",
&mut term,
)? {
promise::spawn::spawn_into_main_thread(async move {
let mux = Mux::get().unwrap();
mux.remove_tab(tab_id);
})
.detach();
}

Ok(())
Expand All @@ -114,47 +178,15 @@ pub fn confirm_close_window(
mut term: TermWizTerminal,
mux_window_id: WindowId,
) -> anyhow::Result<()> {
term.set_raw_mode()?;

let changes = vec![
Change::ClearScreen(ColorAttribute::Default),
Change::CursorPosition {
x: Position::Absolute(0),
y: Position::Absolute(0),
},
Change::Text(
"Really kill this window and all contained tabs and panes? [y/n]\r\n".to_string(),
),
];

term.render(&changes)?;
term.flush()?;

while let Ok(Some(event)) = term.poll_input(None) {
match event {
InputEvent::Key(KeyEvent {
key: KeyCode::Char('y'),
..
}) => {
promise::spawn::spawn_into_main_thread(async move {
let mux = Mux::get().unwrap();
mux.kill_window(mux_window_id);
})
.detach();
break;
}
InputEvent::Key(KeyEvent {
key: KeyCode::Char('n'),
..
})
| InputEvent::Key(KeyEvent {
key: KeyCode::Escape,
..
}) => {
break;
}
_ => {}
}
if run_confirmation_app(
"🛑 Really kill this window and all contained tabs and panes?",
&mut term,
)? {
promise::spawn::spawn_into_main_thread(async move {
let mux = Mux::get().unwrap();
mux.kill_window(mux_window_id);
})
.detach();
}

Ok(())
Expand Down

0 comments on commit 3d81740

Please sign in to comment.