Added buffer switching
This commit is contained in:
parent
8c0a033f3c
commit
ebc4d97dbc
8 changed files with 232 additions and 91 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{state::State, terminal::TerminalEvent};
|
use crate::{state::BufferId, terminal::TerminalEvent};
|
||||||
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -11,15 +11,19 @@ pub enum Dir {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
Char(char), // Insert a character
|
Char(char), // Insert a character
|
||||||
Backspace, // Backspace a character
|
Backspace, // Backspace a character
|
||||||
Move(Dir), // Move the cursor
|
Move(Dir), // Move the cursor
|
||||||
PaneMove(Dir), // Move panes
|
PaneMove(Dir), // Move panes
|
||||||
Cancel, // Cancels the current context
|
Cancel, // Cancels the current context
|
||||||
Go, // Search, accept, or select the current option
|
Go, // Search, accept, or select the current option
|
||||||
Quit, // Quit the application
|
Yes, // A binary confirmation is answered 'yes'
|
||||||
OpenPrompt, // Open the command prompt
|
No, // A binary confirmation is answered 'no'
|
||||||
Show(String), // Display some arbitrary text to the user
|
Quit, // Quit the application
|
||||||
|
OpenPrompt, // Open the command prompt
|
||||||
|
OpenSwitcher, // Open the buffer switcher
|
||||||
|
Show(String), // Display some arbitrary text to the user
|
||||||
|
SwitchBuffer(BufferId), // Switch the current pane to the given buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
|
|
@ -117,8 +121,8 @@ impl RawEvent {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_prompt(&self) -> bool {
|
pub fn to_open(&self) -> Option<Action> {
|
||||||
matches!(
|
if matches!(
|
||||||
&self.0,
|
&self.0,
|
||||||
TerminalEvent::Key(KeyEvent {
|
TerminalEvent::Key(KeyEvent {
|
||||||
code: KeyCode::Enter,
|
code: KeyCode::Enter,
|
||||||
|
|
@ -126,11 +130,49 @@ impl RawEvent {
|
||||||
kind: KeyEventKind::Press,
|
kind: KeyEventKind::Press,
|
||||||
..
|
..
|
||||||
})
|
})
|
||||||
)
|
) {
|
||||||
|
Some(Action::OpenPrompt)
|
||||||
|
} else if matches!(
|
||||||
|
&self.0,
|
||||||
|
TerminalEvent::Key(KeyEvent {
|
||||||
|
code: KeyCode::Char('b'),
|
||||||
|
modifiers: KeyModifiers::CONTROL,
|
||||||
|
kind: KeyEventKind::Press,
|
||||||
|
..
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
Some(Action::OpenSwitcher)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_cancel(&self) -> bool {
|
pub fn to_go(&self) -> Option<Action> {
|
||||||
matches!(
|
if matches!(
|
||||||
|
&self.0,
|
||||||
|
TerminalEvent::Key(KeyEvent {
|
||||||
|
code: KeyCode::Enter,
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
kind: KeyEventKind::Press,
|
||||||
|
..
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
Some(Action::Go)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_yes(&self) -> Option<Action> {
|
||||||
|
if matches!(self.to_char(), Some('y' | 'Y')) {
|
||||||
|
Some(Action::Yes)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_cancel(&self) -> Option<Action> {
|
||||||
|
if matches!(
|
||||||
&self.0,
|
&self.0,
|
||||||
TerminalEvent::Key(KeyEvent {
|
TerminalEvent::Key(KeyEvent {
|
||||||
code: KeyCode::Esc,
|
code: KeyCode::Esc,
|
||||||
|
|
@ -138,6 +180,18 @@ impl RawEvent {
|
||||||
kind: KeyEventKind::Press,
|
kind: KeyEventKind::Press,
|
||||||
..
|
..
|
||||||
})
|
})
|
||||||
)
|
) {
|
||||||
|
Some(Action::Cancel)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_no(&self) -> Option<Action> {
|
||||||
|
if matches!(self.to_char(), Some('n' | 'N')) {
|
||||||
|
Some(Action::No)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,14 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn begin_session(&mut self) -> CursorId {
|
||||||
|
self.cursors.insert(Cursor::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end_session(&mut self, cursor: CursorId) {
|
||||||
|
self.cursors.remove(cursor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct State {
|
pub struct State {
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ impl<'a> Rect<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_border(&mut self, theme: &theme::BorderTheme) -> Rect {
|
pub fn with_border(&mut self, theme: &theme::BorderTheme, title: Option<&str>) -> Rect {
|
||||||
let edge = self.size().map(|e| e.saturating_sub(1));
|
let edge = self.size().map(|e| e.saturating_sub(1));
|
||||||
for col in 0..edge[0] {
|
for col in 0..edge[0] {
|
||||||
self.get_mut([col, 0]).map(|c| {
|
self.get_mut([col, 0]).map(|c| {
|
||||||
|
|
@ -112,6 +112,19 @@ impl<'a> Rect<'a> {
|
||||||
c.c = theme.bottom_right;
|
c.c = theme.bottom_right;
|
||||||
c.fg = theme.fg;
|
c.fg = theme.fg;
|
||||||
});
|
});
|
||||||
|
if let Some(title) = title {
|
||||||
|
for (i, c) in [theme.join_right, ' ']
|
||||||
|
.into_iter()
|
||||||
|
.chain(title.chars())
|
||||||
|
.chain([' ', theme.join_left])
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
self.get_mut([2 + i, 0]).map(|cell| {
|
||||||
|
cell.fg = theme.fg;
|
||||||
|
cell.c = c
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
self.rect([1, 1], self.size().map(|e| e.saturating_sub(2)))
|
self.rect([1, 1], self.size().map(|e| e.saturating_sub(2)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ pub struct BorderTheme {
|
||||||
pub top_right: char,
|
pub top_right: char,
|
||||||
pub bottom_left: char,
|
pub bottom_left: char,
|
||||||
pub bottom_right: char,
|
pub bottom_right: char,
|
||||||
|
pub join_left: char,
|
||||||
|
pub join_right: char,
|
||||||
pub fg: Color,
|
pub fg: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,6 +25,8 @@ impl Default for BorderTheme {
|
||||||
top_right: '╮',
|
top_right: '╮',
|
||||||
bottom_left: '╰',
|
bottom_left: '╰',
|
||||||
bottom_right: '╯',
|
bottom_right: '╯',
|
||||||
|
join_left: '├',
|
||||||
|
join_right: '┤',
|
||||||
fg: Color::DarkGrey,
|
fg: Color::DarkGrey,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -30,7 +34,7 @@ impl Default for BorderTheme {
|
||||||
|
|
||||||
pub struct Theme {
|
pub struct Theme {
|
||||||
pub ui_bg: Color,
|
pub ui_bg: Color,
|
||||||
pub status_bg: Color,
|
pub select_bg: Color,
|
||||||
pub border: BorderTheme,
|
pub border: BorderTheme,
|
||||||
pub focus_border: BorderTheme,
|
pub focus_border: BorderTheme,
|
||||||
}
|
}
|
||||||
|
|
@ -39,7 +43,7 @@ impl Default for Theme {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
ui_bg: Color::AnsiValue(235),
|
ui_bg: Color::AnsiValue(235),
|
||||||
status_bg: Color::AnsiValue(23),
|
select_bg: Color::AnsiValue(23),
|
||||||
border: BorderTheme::default(),
|
border: BorderTheme::default(),
|
||||||
focus_border: BorderTheme {
|
focus_border: BorderTheme {
|
||||||
fg: Color::White,
|
fg: Color::White,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ mod status;
|
||||||
pub use self::{
|
pub use self::{
|
||||||
input::Input,
|
input::Input,
|
||||||
panes::Panes,
|
panes::Panes,
|
||||||
prompt::{Confirm, Prompt, Show},
|
prompt::{Confirm, Prompt, Show, Switcher},
|
||||||
root::Root,
|
root::Root,
|
||||||
status::Status,
|
status::Status,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,8 @@ impl Doc {
|
||||||
pub fn new(state: &mut State, buffer: BufferId) -> Self {
|
pub fn new(state: &mut State, buffer: BufferId) -> Self {
|
||||||
Self {
|
Self {
|
||||||
buffer,
|
buffer,
|
||||||
cursor: state.buffers[buffer].cursors.insert(Cursor::default()),
|
// TODO: Don't index directly
|
||||||
|
cursor: state.buffers[buffer].begin_session(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -34,6 +35,15 @@ impl Element for Doc {
|
||||||
.or_else(|| e.to_move().map(Action::Move))
|
.or_else(|| e.to_move().map(Action::Move))
|
||||||
.or_else(|| e.to_pane_move().map(Action::PaneMove))
|
.or_else(|| e.to_pane_move().map(Action::PaneMove))
|
||||||
}) {
|
}) {
|
||||||
|
Some(Action::SwitchBuffer(new_buffer)) => {
|
||||||
|
buffer.end_session(self.cursor);
|
||||||
|
self.buffer = new_buffer;
|
||||||
|
let Some(buffer) = state.buffers.get_mut(self.buffer) else {
|
||||||
|
return Err(event);
|
||||||
|
};
|
||||||
|
self.cursor = buffer.begin_session();
|
||||||
|
Ok(Resp::handled(None))
|
||||||
|
}
|
||||||
Some(Action::Char(c)) => {
|
Some(Action::Char(c)) => {
|
||||||
buffer.insert(cursor.pos, c);
|
buffer.insert(cursor.pos, c);
|
||||||
Ok(Resp::handled(None))
|
Ok(Resp::handled(None))
|
||||||
|
|
@ -70,6 +80,19 @@ pub enum Pane {
|
||||||
Doc(Doc),
|
Doc(Doc),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Pane {
|
||||||
|
fn title(&self, state: &State) -> Option<String> {
|
||||||
|
match self {
|
||||||
|
Self::Doc(doc) => {
|
||||||
|
let Some(buffer) = state.buffers.get(doc.buffer) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(buffer.path.display().to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Panes {
|
pub struct Panes {
|
||||||
selected: usize,
|
selected: usize,
|
||||||
panes: Vec<Pane>,
|
panes: Vec<Pane>,
|
||||||
|
|
@ -127,9 +150,11 @@ impl Visual for Panes {
|
||||||
} else {
|
} else {
|
||||||
&state.theme.border
|
&state.theme.border
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Draw pane contents
|
||||||
frame
|
frame
|
||||||
.rect([x0, 0], [x1 - x0, frame.size()[1]])
|
.rect([x0, 0], [x1 - x0, frame.size()[1]])
|
||||||
.with_border(border_theme)
|
.with_border(border_theme, pane.title(state).as_deref())
|
||||||
.with_focus(is_selected)
|
.with_focus(is_selected)
|
||||||
.with(|frame| match pane {
|
.with(|frame| match pane {
|
||||||
Pane::Doc(doc) => doc.render(state, frame),
|
Pane::Doc(doc) => doc.render(state, frame),
|
||||||
|
|
|
||||||
131
src/ui/prompt.rs
131
src/ui/prompt.rs
|
|
@ -1,24 +1,7 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::state::BufferId;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
pub enum Command {
|
|
||||||
Quit,
|
|
||||||
Help,
|
|
||||||
Version,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Command {
|
|
||||||
type Err = ();
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s {
|
|
||||||
"q" | "quit" => Ok(Command::Quit),
|
|
||||||
"version" => Ok(Command::Version),
|
|
||||||
"?" | "help" => Ok(Command::Help),
|
|
||||||
_ => Err(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Prompt {
|
pub struct Prompt {
|
||||||
pub input: Input,
|
pub input: Input,
|
||||||
}
|
}
|
||||||
|
|
@ -26,15 +9,23 @@ pub struct Prompt {
|
||||||
impl Prompt {
|
impl Prompt {
|
||||||
pub fn get_action(&self) -> Option<Action> {
|
pub fn get_action(&self) -> Option<Action> {
|
||||||
match self.input.get_text().as_str() {
|
match self.input.get_text().as_str() {
|
||||||
"quit" => Some(Action::Quit),
|
// The root sees 'cancel' as an initiator for quitting
|
||||||
|
"q" | "quit" => Some(Action::Cancel),
|
||||||
"version" => Some(Action::Show(format!(
|
"version" => Some(Action::Show(format!(
|
||||||
"{} {}",
|
"{} {}",
|
||||||
env!("CARGO_PKG_NAME"),
|
env!("CARGO_PKG_NAME"),
|
||||||
env!("CARGO_PKG_VERSION")
|
env!("CARGO_PKG_VERSION")
|
||||||
))),
|
))),
|
||||||
"help" => Some(Action::Show(format!(
|
"?" | "help" => Some(Action::Show(format!(
|
||||||
"Temporary help info:\n- quit\n- version\n- help"
|
"Temporary help info:\n\
|
||||||
|
- quit\n\
|
||||||
|
- version\n\
|
||||||
|
- pane_move_left\n\
|
||||||
|
- pane_move_right\n\
|
||||||
|
- help"
|
||||||
))),
|
))),
|
||||||
|
"pane_move_left" => Some(Action::PaneMove(Dir::Left)),
|
||||||
|
"pane_move_right" => Some(Action::PaneMove(Dir::Right)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -42,18 +33,8 @@ impl Prompt {
|
||||||
|
|
||||||
impl Element<CanEnd> for Prompt {
|
impl Element<CanEnd> for Prompt {
|
||||||
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<CanEnd>, Event> {
|
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<CanEnd>, Event> {
|
||||||
match event.to_action(|e| {
|
match event.to_action(|e| e.to_go().or_else(|| e.to_cancel())) {
|
||||||
if e.is_cancel() {
|
Some(Action::Cancel) => Ok(Resp::end(None)),
|
||||||
Some(Action::Cancel)
|
|
||||||
} else if e.is_go() {
|
|
||||||
Some(Action::Go)
|
|
||||||
} else if e.is_prompt() {
|
|
||||||
Some(Action::OpenPrompt)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Some(Action::Cancel /*| Action::Prompt*/) => Ok(Resp::end(None)),
|
|
||||||
Some(Action::Go) => {
|
Some(Action::Go) => {
|
||||||
if let Some(action) = self.get_action() {
|
if let Some(action) = self.get_action() {
|
||||||
Ok(Resp::end(action))
|
Ok(Resp::end(action))
|
||||||
|
|
@ -81,13 +62,7 @@ pub struct Show {
|
||||||
|
|
||||||
impl Element<CanEnd> for Show {
|
impl Element<CanEnd> for Show {
|
||||||
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<CanEnd>, Event> {
|
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<CanEnd>, Event> {
|
||||||
match event.to_action(|e| {
|
match event.to_action(|e| e.to_cancel()) {
|
||||||
if e.is_cancel() {
|
|
||||||
Some(Action::Cancel)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Some(Action::Cancel) => Ok(Resp::end(None)),
|
Some(Action::Cancel) => Ok(Resp::end(None)),
|
||||||
_ => Err(event),
|
_ => Err(event),
|
||||||
}
|
}
|
||||||
|
|
@ -114,18 +89,11 @@ pub struct Confirm {
|
||||||
|
|
||||||
impl Element<CanEnd> for Confirm {
|
impl Element<CanEnd> for Confirm {
|
||||||
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<CanEnd>, Event> {
|
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<CanEnd>, Event> {
|
||||||
match event.to_action(|e| {
|
match event.to_action(|e| e.to_yes().or_else(|| e.to_no()).or_else(|| e.to_cancel())) {
|
||||||
if e.is_cancel() || e.to_char() == Some('n') {
|
Some(Action::Yes) => Ok(Resp::end(Some(self.action.clone()))),
|
||||||
Some(Action::Cancel)
|
Some(Action::No | Action::Cancel) => Ok(Resp::end(None)),
|
||||||
} else if e.to_char() == Some('y') {
|
// All other events get swallowed
|
||||||
Some(Action::Go)
|
_ => Ok(Resp::handled(None)),
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Some(Action::Go) => Ok(Resp::end(Some(self.action.clone()))),
|
|
||||||
Some(Action::Cancel) => Ok(Resp::end(None)),
|
|
||||||
_ => Err(event),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -142,3 +110,62 @@ impl Visual for Confirm {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Switcher {
|
||||||
|
pub selected: usize,
|
||||||
|
pub options: Vec<BufferId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Element<CanEnd> for Switcher {
|
||||||
|
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<CanEnd>, Event> {
|
||||||
|
match event.to_action(|e| {
|
||||||
|
e.to_cancel()
|
||||||
|
.or_else(|| e.to_go())
|
||||||
|
.or_else(|| e.to_move().map(Action::Move))
|
||||||
|
}) {
|
||||||
|
Some(Action::Move(Dir::Up)) => {
|
||||||
|
self.selected = (self.selected + self.options.len() - 1) % self.options.len();
|
||||||
|
Ok(Resp::handled(None))
|
||||||
|
}
|
||||||
|
Some(Action::Move(Dir::Down)) => {
|
||||||
|
self.selected = (self.selected + 1) % self.options.len();
|
||||||
|
Ok(Resp::handled(None))
|
||||||
|
}
|
||||||
|
Some(Action::Go) => Ok(Resp::end(
|
||||||
|
if let Some(buffer) = self.options.get(self.selected) {
|
||||||
|
Some(Action::SwitchBuffer(*buffer))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
Some(Action::Cancel) => Ok(Resp::end(None)),
|
||||||
|
// All other events get swallowed
|
||||||
|
_ => Ok(Resp::handled(None)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Visual for Switcher {
|
||||||
|
fn render(&self, state: &State, frame: &mut Rect) {
|
||||||
|
for (i, buffer) in self.options.iter().enumerate() {
|
||||||
|
let Some(buffer) = state.buffers.get(*buffer) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
frame
|
||||||
|
.rect(
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
frame.size()[1].saturating_sub(3 + self.options.len()) + i,
|
||||||
|
],
|
||||||
|
[frame.size()[0], 1],
|
||||||
|
)
|
||||||
|
.with_bg(if self.selected == i {
|
||||||
|
state.theme.select_bg
|
||||||
|
} else {
|
||||||
|
state.theme.ui_bg
|
||||||
|
})
|
||||||
|
.fill(' ')
|
||||||
|
.text([0, 0], buffer.path.display().to_string().chars());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ pub enum Task {
|
||||||
Prompt(Prompt),
|
Prompt(Prompt),
|
||||||
Show(Show),
|
Show(Show),
|
||||||
Confirm(Confirm),
|
Confirm(Confirm),
|
||||||
|
Switcher(Switcher),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Root {
|
impl Root {
|
||||||
|
|
@ -33,15 +34,7 @@ impl Element<CanEnd> for Root {
|
||||||
None => {
|
None => {
|
||||||
break match self.panes.handle(state, event) {
|
break match self.panes.handle(state, event) {
|
||||||
Ok(resp) => resp.action,
|
Ok(resp) => resp.action,
|
||||||
Err(event) => event.to_action(|e| {
|
Err(event) => event.to_action(|e| e.to_open().or_else(|| e.to_cancel())),
|
||||||
if e.is_prompt() {
|
|
||||||
Some(Action::OpenPrompt)
|
|
||||||
} else if e.is_cancel() {
|
|
||||||
Some(Action::Cancel)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -50,6 +43,7 @@ impl Element<CanEnd> for Root {
|
||||||
Task::Prompt(p) => p.handle(state, event),
|
Task::Prompt(p) => p.handle(state, event),
|
||||||
Task::Show(s) => s.handle(state, event),
|
Task::Show(s) => s.handle(state, event),
|
||||||
Task::Confirm(c) => c.handle(state, event),
|
Task::Confirm(c) => c.handle(state, event),
|
||||||
|
Task::Switcher(s) => s.handle(state, event),
|
||||||
};
|
};
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
|
|
@ -76,13 +70,25 @@ impl Element<CanEnd> for Root {
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
Action::OpenSwitcher => {
|
||||||
|
self.tasks.clear(); // Prompt overrides all
|
||||||
|
self.tasks.push(Task::Switcher(Switcher {
|
||||||
|
selected: 0,
|
||||||
|
options: state.buffers.keys().collect(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
Action::Cancel => self.tasks.push(Task::Confirm(Confirm {
|
Action::Cancel => self.tasks.push(Task::Confirm(Confirm {
|
||||||
label: Label("Are you sure you wish to quit? (y/n)".to_string()),
|
label: Label("Are you sure you wish to quit? (y/n)".to_string()),
|
||||||
action: Action::Quit,
|
action: Action::Quit,
|
||||||
})),
|
})),
|
||||||
Action::Show(text) => self.tasks.push(Task::Show(Show { label: Label(text) })),
|
Action::Show(text) => self.tasks.push(Task::Show(Show { label: Label(text) })),
|
||||||
Action::Quit => return Ok(Resp::end(None)),
|
Action::Quit => return Ok(Resp::end(None)),
|
||||||
action => todo!("Unhandled action {action:?}"),
|
action => {
|
||||||
|
return self
|
||||||
|
.panes
|
||||||
|
.handle(state, Event::Action(action))
|
||||||
|
.map(|r| r.into_can_end());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,16 +101,19 @@ impl Visual for Root {
|
||||||
fn render(&self, state: &State, frame: &mut Rect) {
|
fn render(&self, state: &State, frame: &mut Rect) {
|
||||||
frame.fill(' ');
|
frame.fill(' ');
|
||||||
|
|
||||||
let task_has_focus = self.tasks.last().is_some();
|
let task_has_focus = matches!(self.tasks.last(), Some(Task::Prompt(_)));
|
||||||
|
|
||||||
// Display status bar
|
// Display status bar
|
||||||
frame
|
frame
|
||||||
.rect([0, frame.size()[1].saturating_sub(3)], [frame.size()[0], 3])
|
.rect([0, frame.size()[1].saturating_sub(3)], [frame.size()[0], 3])
|
||||||
.with_border(if task_has_focus {
|
.with_border(
|
||||||
&state.theme.focus_border
|
if task_has_focus {
|
||||||
} else {
|
&state.theme.focus_border
|
||||||
&state.theme.border
|
} else {
|
||||||
})
|
&state.theme.border
|
||||||
|
},
|
||||||
|
Some("Prompt (press alt + enter)"),
|
||||||
|
)
|
||||||
.with(|frame| {
|
.with(|frame| {
|
||||||
if let Some(Task::Prompt(p)) = self.tasks.last() {
|
if let Some(Task::Prompt(p)) = self.tasks.last() {
|
||||||
p.render(state, frame);
|
p.render(state, frame);
|
||||||
|
|
@ -120,9 +129,10 @@ impl Visual for Root {
|
||||||
|
|
||||||
if let Some(task) = self.tasks.last() {
|
if let Some(task) = self.tasks.last() {
|
||||||
match task {
|
match task {
|
||||||
|
Task::Prompt(_) => {} // Prompt isn't rendered, it's always rendered above
|
||||||
Task::Show(s) => s.render(state, frame),
|
Task::Show(s) => s.render(state, frame),
|
||||||
Task::Confirm(c) => c.render(state, frame),
|
Task::Confirm(c) => c.render(state, frame),
|
||||||
_ => {}
|
Task::Switcher(s) => s.render(state, frame),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue