Added opener
This commit is contained in:
parent
58a8c5c9e5
commit
d352d04030
9 changed files with 315 additions and 61 deletions
16
README.md
16
README.md
|
|
@ -2,17 +2,17 @@
|
|||
|
||||
## Features
|
||||
|
||||
- [ ] Buffers
|
||||
- [ ] Buffer switching
|
||||
- [ ] Prompt
|
||||
- [ ] Cursor selection
|
||||
- [ ] Basic cursor movement
|
||||
- [ ] Multiple panes
|
||||
- [ ] Pane creation/deletion
|
||||
- [x] Buffers
|
||||
- [x] Buffer switching
|
||||
- [x] Prompt
|
||||
- [x] Cursor selection
|
||||
- [x] Basic cursor movement
|
||||
- [x] Multiple panes
|
||||
- [x] Pane creation/deletion
|
||||
- [x] Opener
|
||||
|
||||
## Todo
|
||||
|
||||
- [ ] Opener
|
||||
- [ ] Find
|
||||
- [ ] Replace
|
||||
- [ ] Project search
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::{state::BufferId, terminal::TerminalEvent};
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Dir {
|
||||
|
|
@ -24,9 +25,11 @@ pub enum Action {
|
|||
No, // A binary confirmation is answered 'no'
|
||||
Quit, // Quit the application
|
||||
OpenPrompt, // Open the command prompt
|
||||
OpenSwitcher, // Open the buffer switcher
|
||||
Show(Option<String>, String), // Display an optionally titled informational text box to the user
|
||||
OpenSwitcher, // Open the buffer switcher
|
||||
OpenOpener(PathBuf), // Open the file opener
|
||||
SwitchBuffer(BufferId), // Switch the current pane to the given buffer
|
||||
OpenFile(PathBuf), // Open the file and switch the current pane to it
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -201,6 +204,22 @@ impl RawEvent {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn to_open_opener(&self, path: PathBuf) -> Option<Action> {
|
||||
if matches!(
|
||||
&self.0,
|
||||
TerminalEvent::Key(KeyEvent {
|
||||
code: KeyCode::Char('o'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
kind: KeyEventKind::Press,
|
||||
..
|
||||
})
|
||||
) {
|
||||
Some(Action::OpenOpener(path))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_go(&self) -> Option<Action> {
|
||||
if matches!(
|
||||
&self.0,
|
||||
|
|
|
|||
49
src/state.rs
49
src/state.rs
|
|
@ -1,6 +1,10 @@
|
|||
use crate::{Args, Dir, Error, theme};
|
||||
use slotmap::{HopSlotMap, new_key_type};
|
||||
use std::{io, ops::Range, path::PathBuf};
|
||||
use std::{
|
||||
io,
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
new_key_type! {
|
||||
pub struct BufferId;
|
||||
|
|
@ -103,6 +107,7 @@ impl Text {
|
|||
|
||||
#[derive(Default)]
|
||||
pub struct Buffer {
|
||||
pub dir: Option<PathBuf>,
|
||||
pub path: Option<PathBuf>,
|
||||
pub text: Text,
|
||||
pub cursors: HopSlotMap<CursorId, Cursor>,
|
||||
|
|
@ -110,19 +115,35 @@ pub struct Buffer {
|
|||
|
||||
impl Buffer {
|
||||
pub fn from_file(path: PathBuf) -> Result<Self, Error> {
|
||||
let chars = match std::fs::read_to_string(&path) {
|
||||
Ok(s) => s.chars().collect(),
|
||||
let (dir, chars) = match std::fs::read_to_string(&path) {
|
||||
Ok(s) => {
|
||||
let mut path = path.canonicalize()?;
|
||||
path.pop();
|
||||
(Some(path), s.chars().collect())
|
||||
}
|
||||
// If the file doesn't exist, create a new file
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => Vec::new(),
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => {
|
||||
(path.parent().map(Path::to_owned), Vec::new())
|
||||
}
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
Ok(Self {
|
||||
dir,
|
||||
path: Some(path),
|
||||
text: Text { chars },
|
||||
cursors: HopSlotMap::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Option<&str> {
|
||||
Some(
|
||||
match self.path.as_ref()?.file_name().and_then(|n| n.to_str()) {
|
||||
Some(name) => name,
|
||||
None => "<error>",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.text.chars.clear();
|
||||
// Reset cursors
|
||||
|
|
@ -183,28 +204,34 @@ impl Buffer {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, pos: usize, c: char) {
|
||||
self.text.chars.insert(pos.min(self.text.chars.len()), c);
|
||||
pub fn insert(&mut self, pos: usize, chars: impl IntoIterator<Item = char>) {
|
||||
let mut n = 0;
|
||||
for c in chars {
|
||||
self.text
|
||||
.chars
|
||||
.insert((pos + n).min(self.text.chars.len()), c);
|
||||
n += 1;
|
||||
}
|
||||
self.cursors.values_mut().for_each(|cursor| {
|
||||
if cursor.base >= pos {
|
||||
cursor.base += 1;
|
||||
cursor.base += n;
|
||||
}
|
||||
if cursor.pos >= pos {
|
||||
cursor.pos += 1;
|
||||
cursor.pos += n;
|
||||
cursor.reset_desired_col(&self.text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn enter(&mut self, cursor_id: CursorId, c: char) {
|
||||
pub fn enter(&mut self, cursor_id: CursorId, chars: impl IntoIterator<Item = char>) {
|
||||
let Some(cursor) = self.cursors.get(cursor_id) else {
|
||||
return;
|
||||
};
|
||||
if let Some(selection) = cursor.selection() {
|
||||
self.remove(selection);
|
||||
self.enter(cursor_id, c);
|
||||
self.enter(cursor_id, chars);
|
||||
} else {
|
||||
self.insert(cursor.pos, c);
|
||||
self.insert(cursor.pos, chars);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ pub struct Theme {
|
|||
pub focus_border: BorderTheme,
|
||||
pub text: Color,
|
||||
pub whitespace: Color,
|
||||
pub option_dir: Color,
|
||||
pub option_file: Color,
|
||||
pub option_new: Color,
|
||||
}
|
||||
|
||||
impl Default for Theme {
|
||||
|
|
@ -59,6 +62,9 @@ impl Default for Theme {
|
|||
},
|
||||
text: Color::Reset,
|
||||
whitespace: Color::AnsiValue(245),
|
||||
option_dir: Color::AnsiValue(178),
|
||||
option_file: Color::Reset,
|
||||
option_new: Color::AnsiValue(148),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use super::*;
|
||||
use crate::{
|
||||
state::{BufferId, CursorId},
|
||||
state::{Buffer, BufferId, CursorId},
|
||||
terminal::CursorStyle,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Doc {
|
||||
|
|
@ -25,13 +25,6 @@ impl Doc {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn title(&self, state: &State) -> Option<String> {
|
||||
let Some(buffer) = state.buffers.get(self.buffer) else {
|
||||
return None;
|
||||
};
|
||||
Some(buffer.path.as_ref()?.display().to_string())
|
||||
}
|
||||
|
||||
pub fn close(self, state: &mut State) {
|
||||
for (buffer, cursor) in self.cursors {
|
||||
let Some(buffer) = state.buffers.get_mut(buffer) else {
|
||||
|
|
@ -40,6 +33,19 @@ impl Doc {
|
|||
buffer.end_session(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
fn switch_buffer(&mut self, state: &mut State, buffer: BufferId) {
|
||||
self.buffer = buffer;
|
||||
let Some(buffer) = state.buffers.get_mut(self.buffer) else {
|
||||
return;
|
||||
};
|
||||
// Start a new cursor session for this buffer if one doesn't exist
|
||||
let cursor_id = *self
|
||||
.cursors
|
||||
.entry(self.buffer)
|
||||
.or_insert_with(|| buffer.start_session());
|
||||
self.input.refocus(buffer, cursor_id);
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Doc {
|
||||
|
|
@ -48,21 +54,25 @@ impl Element for Doc {
|
|||
return Err(event);
|
||||
};
|
||||
|
||||
match event.to_action(|e| e.to_open_switcher()) {
|
||||
let open_path = buffer.dir.to_owned().unwrap_or(PathBuf::from("/"));
|
||||
|
||||
match event.to_action(|e| e.to_open_switcher().or_else(|| e.to_open_opener(open_path))) {
|
||||
action @ Some(Action::OpenSwitcher) => Ok(Resp::handled(action.map(Into::into))),
|
||||
action @ Some(Action::OpenOpener(_)) => Ok(Resp::handled(action.map(Into::into))),
|
||||
Some(Action::SwitchBuffer(new_buffer)) => {
|
||||
self.buffer = new_buffer;
|
||||
let Some(buffer) = state.buffers.get_mut(self.buffer) else {
|
||||
return Err(event);
|
||||
};
|
||||
// Start a new cursor session for this buffer if one doesn't exist
|
||||
let cursor_id = *self
|
||||
.cursors
|
||||
.entry(self.buffer)
|
||||
.or_insert_with(|| buffer.start_session());
|
||||
self.input.refocus(buffer, cursor_id);
|
||||
self.switch_buffer(state, new_buffer);
|
||||
Ok(Resp::handled(None))
|
||||
}
|
||||
Some(Action::OpenFile(path)) => match Buffer::from_file(path) {
|
||||
Ok(buffer) => {
|
||||
let buffer_id = state.buffers.insert(buffer);
|
||||
self.switch_buffer(state, buffer_id);
|
||||
Ok(Resp::handled(None))
|
||||
}
|
||||
Err(err) => Ok(Resp::handled(Some(
|
||||
Action::Show(Some(format!("Could not open file")), format!("{err}")).into(),
|
||||
))),
|
||||
},
|
||||
_ => {
|
||||
let Some(buffer) = state.buffers.get_mut(self.buffer) else {
|
||||
return Err(event);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ enum Mode {
|
|||
#[default]
|
||||
Doc,
|
||||
Prompt,
|
||||
Filter,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
|
|
@ -28,6 +29,13 @@ impl Input {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn filter() -> Self {
|
||||
Self {
|
||||
mode: Mode::Filter,
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refocus(&mut self, buffer: &mut Buffer, cursor_id: CursorId) {
|
||||
let Some(cursor) = buffer.cursors.get(cursor_id) else {
|
||||
return;
|
||||
|
|
@ -54,7 +62,7 @@ impl Input {
|
|||
} else if c == '\x7F' {
|
||||
buffer.delete(cursor_id);
|
||||
} else {
|
||||
buffer.enter(cursor_id, c);
|
||||
buffer.enter(cursor_id, [c]);
|
||||
}
|
||||
self.refocus(buffer, cursor_id);
|
||||
Ok(Resp::handled(None))
|
||||
|
|
@ -80,11 +88,7 @@ impl Input {
|
|||
cursor_id: CursorId,
|
||||
frame: &mut Rect,
|
||||
) {
|
||||
let title = if let Some(path) = &buffer.path {
|
||||
Some(path.display().to_string())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let title = buffer.name();
|
||||
|
||||
// Add frame
|
||||
let mut frame = frame.with_border(
|
||||
|
|
@ -104,6 +108,7 @@ impl Input {
|
|||
let line_num_w = buffer.text.lines().count().max(1).ilog10() as usize + 1;
|
||||
let margin_w = match self.mode {
|
||||
Mode::Prompt => 2,
|
||||
Mode::Filter => 0,
|
||||
Mode::Doc => line_num_w + 2,
|
||||
};
|
||||
|
||||
|
|
@ -125,6 +130,7 @@ impl Input {
|
|||
{
|
||||
// Margin
|
||||
match self.mode {
|
||||
Mode::Filter => frame.rect([0, 0], frame.size()),
|
||||
Mode::Prompt => frame
|
||||
.rect([0, i], [1, 1])
|
||||
.with_bg(state.theme.margin_bg)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ pub use self::{
|
|||
doc::Doc,
|
||||
input::Input,
|
||||
panes::Panes,
|
||||
prompt::{Confirm, Prompt, Show, Switcher},
|
||||
prompt::{Confirm, Opener, Prompt, Show, Switcher},
|
||||
root::Root,
|
||||
status::Status,
|
||||
};
|
||||
|
|
@ -120,6 +120,15 @@ impl<T> Options<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn set_options<F: FnMut(&T) -> Option<u32>>(
|
||||
&mut self,
|
||||
options: impl IntoIterator<Item = T>,
|
||||
mut f: F,
|
||||
) {
|
||||
self.options = options.into_iter().collect();
|
||||
self.apply_scoring(f);
|
||||
}
|
||||
|
||||
pub fn apply_scoring<F: FnMut(&T) -> Option<u32>>(&mut self, mut f: F) {
|
||||
let mut ranking = self
|
||||
.options
|
||||
|
|
@ -127,8 +136,9 @@ impl<T> Options<T> {
|
|||
.enumerate()
|
||||
.filter_map(|(i, o)| Some((i, f(o)?)))
|
||||
.collect::<Vec<_>>();
|
||||
ranking.sort_by_key(|(_, score)| *score);
|
||||
ranking.sort_by_key(|(_, score)| std::cmp::Reverse(*score));
|
||||
self.ranking = ranking.into_iter().map(|(i, _)| i).collect();
|
||||
self.selected = 0;
|
||||
}
|
||||
|
||||
pub fn requested_height(&self) -> usize {
|
||||
|
|
@ -136,7 +146,7 @@ impl<T> Options<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> Element<T> for Options<T> {
|
||||
impl<T: Clone> Element<T> for Options<T> {
|
||||
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<T>, Event> {
|
||||
match event.to_action(|e| e.to_go().or_else(|| e.to_move())) {
|
||||
Some(Action::Move(Dir::Up, false, _)) => {
|
||||
|
|
@ -150,7 +160,7 @@ impl<T> Element<T> for Options<T> {
|
|||
Some(Action::Go) => {
|
||||
if self.selected < self.ranking.len() {
|
||||
Ok(Resp::end_with(
|
||||
self.options.remove(self.ranking[self.selected]),
|
||||
self.options[self.ranking[self.selected]].clone(),
|
||||
None,
|
||||
))
|
||||
} else {
|
||||
|
|
|
|||
188
src/ui/prompt.rs
188
src/ui/prompt.rs
|
|
@ -1,5 +1,6 @@
|
|||
use super::*;
|
||||
use crate::state::{Buffer, BufferId, CursorId};
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
pub struct Prompt {
|
||||
buffer: Buffer,
|
||||
|
|
@ -188,7 +189,7 @@ impl Switcher {
|
|||
options: Options::new(buffers),
|
||||
cursor_id: buffer.start_session(),
|
||||
buffer,
|
||||
input: Input::prompt(),
|
||||
input: Input::filter(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -215,10 +216,13 @@ impl Element<()> for Switcher {
|
|||
let Some(buffer) = state.buffers.get(*b) else {
|
||||
return None;
|
||||
};
|
||||
match buffer.path.as_ref() {
|
||||
Some(path) if path.display().to_string().contains(&filter) => Some(1),
|
||||
Some(_) => None,
|
||||
None => Some(0),
|
||||
let name = buffer.name()?;
|
||||
if name.starts_with(&filter) {
|
||||
Some(2)
|
||||
} else if name.contains(&filter) {
|
||||
Some(1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
res
|
||||
|
|
@ -244,10 +248,174 @@ impl Visual for BufferId {
|
|||
let Some(buffer) = state.buffers.get(*self) else {
|
||||
return;
|
||||
};
|
||||
let buffer_name = match &buffer.path {
|
||||
Some(path) => path.display().to_string(),
|
||||
None => format!("<Untitled>"),
|
||||
};
|
||||
frame.text([0, 0], buffer_name.chars());
|
||||
frame.text([0, 0], buffer.name().unwrap_or("<unknown>").chars());
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Opener {
|
||||
pub options: Options<FileOption>,
|
||||
// Filter
|
||||
pub buffer: Buffer,
|
||||
pub cursor_id: CursorId,
|
||||
pub input: Input,
|
||||
}
|
||||
|
||||
impl Opener {
|
||||
pub fn new(path: PathBuf) -> Self {
|
||||
let mut buffer = Buffer::default();
|
||||
let cursor_id = buffer.start_session();
|
||||
match path.display().to_string().as_str() {
|
||||
s @ "/" => buffer.enter(cursor_id, s.chars()),
|
||||
s => buffer.enter(cursor_id, s.chars().chain(['/'])),
|
||||
}
|
||||
let mut this = Self {
|
||||
options: Options::new([]),
|
||||
cursor_id,
|
||||
buffer,
|
||||
input: Input::filter(),
|
||||
};
|
||||
this.update_completions();
|
||||
this
|
||||
}
|
||||
|
||||
pub fn requested_height(&self) -> usize {
|
||||
self.options.requested_height() + 3
|
||||
}
|
||||
|
||||
fn set_string(&mut self, s: &str) {
|
||||
self.buffer.clear();
|
||||
self.buffer.enter(self.cursor_id, s.chars());
|
||||
self.update_completions();
|
||||
}
|
||||
|
||||
fn update_completions(&mut self) {
|
||||
let path_str = self.buffer.text.to_string();
|
||||
let (dir, filter) = match path_str.rsplit_once('/') {
|
||||
Some(("", filter)) => ("/", filter),
|
||||
Some((dir, filter)) => (dir, filter),
|
||||
None => ("/", path_str.as_str()),
|
||||
};
|
||||
let filter = filter.to_lowercase();
|
||||
match fs::read_dir(dir) {
|
||||
Ok(entries) => {
|
||||
let options = entries
|
||||
.filter_map(|e| e.ok())
|
||||
.filter_map(|entry| {
|
||||
Some(FileOption {
|
||||
path: entry.path(),
|
||||
kind: if entry.file_type().ok()?.is_dir() {
|
||||
FileKind::Dir
|
||||
} else if entry.file_type().ok()?.is_file() {
|
||||
FileKind::File
|
||||
} else {
|
||||
FileKind::Unknown
|
||||
},
|
||||
is_link: entry.file_type().ok()?.is_symlink(),
|
||||
})
|
||||
})
|
||||
.chain([FileOption {
|
||||
path: [dir, &filter].into_iter().collect(),
|
||||
kind: FileKind::New,
|
||||
is_link: false,
|
||||
}]);
|
||||
// TODO
|
||||
self.options.set_options(options, |e| {
|
||||
let name = e.path.file_name()?.to_str()?.to_lowercase();
|
||||
if matches!(e.kind, FileKind::New) {
|
||||
// Special-case: the 'new file' entry always matches last
|
||||
Some(0)
|
||||
} else if name == filter {
|
||||
Some(3)
|
||||
} else if name.starts_with(&filter) {
|
||||
Some(2)
|
||||
} else if name.contains(&filter) {
|
||||
Some(1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
Err(err) => self.options.set_options(Vec::new(), |_| None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Element<()> for Opener {
|
||||
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<()>, Event> {
|
||||
let path_str = self.buffer.text.to_string();
|
||||
match event.to_action(|e| e.to_cancel().or_else(|| e.to_char().map(Action::Char))) {
|
||||
Some(Action::Cancel) => Ok(Resp::end(None)),
|
||||
// Backspace removes the entire path segment!
|
||||
Some(Action::Char('\x08')) if path_str.ends_with("/") => {
|
||||
if path_str != "/" {
|
||||
self.set_string(
|
||||
path_str
|
||||
.trim_end_matches("/")
|
||||
.trim_end_matches(|c| c != '/'),
|
||||
);
|
||||
}
|
||||
Ok(Resp::handled(None))
|
||||
}
|
||||
_ => match self.options.handle(state, event).map(Resp::into_ended) {
|
||||
// Selecting a directory enters the directory
|
||||
Ok(Some(file)) if matches!(file.kind, FileKind::Dir) => {
|
||||
self.set_string(&format!("{}/", file.path.display()));
|
||||
Ok(Resp::handled(None))
|
||||
}
|
||||
Ok(Some(file)) => Ok(Resp::end(Some(Action::OpenFile(file.path).into()))),
|
||||
Ok(None) => Ok(Resp::handled(None)),
|
||||
Err(event) => {
|
||||
let res = self
|
||||
.input
|
||||
.handle(&mut self.buffer, self.cursor_id, event)
|
||||
.map(Resp::into_can_end);
|
||||
self.update_completions();
|
||||
res
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum FileKind {
|
||||
Unknown,
|
||||
Dir,
|
||||
File,
|
||||
New,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FileOption {
|
||||
pub path: PathBuf,
|
||||
pub kind: FileKind,
|
||||
pub is_link: bool,
|
||||
}
|
||||
|
||||
impl Visual for FileOption {
|
||||
fn render(&mut self, state: &State, frame: &mut Rect) {
|
||||
let name = match self.path.file_name().and_then(|n| n.to_str()) {
|
||||
Some(name) if matches!(self.kind, FileKind::Dir) => format!("{}/", name),
|
||||
Some(name) => name.to_string(),
|
||||
None => format!("<unknown>"),
|
||||
};
|
||||
frame
|
||||
.with_fg(match self.kind {
|
||||
FileKind::Dir => state.theme.option_dir,
|
||||
FileKind::File | FileKind::Unknown => state.theme.option_file,
|
||||
FileKind::New => state.theme.option_new,
|
||||
})
|
||||
.text([0, 0], name.chars());
|
||||
}
|
||||
}
|
||||
|
||||
impl Visual for Opener {
|
||||
fn render(&mut self, state: &State, frame: &mut Rect) {
|
||||
frame
|
||||
.rect([0, 0], [frame.size()[0], frame.size()[1].saturating_sub(3)])
|
||||
.with(|f| self.options.render(state, f));
|
||||
frame
|
||||
.rect([0, frame.size()[1].saturating_sub(3)], [frame.size()[0], 3])
|
||||
.with(|f| self.input.render(state, &self.buffer, self.cursor_id, f));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ pub enum Task {
|
|||
Show(Show),
|
||||
Confirm(Confirm),
|
||||
Switcher(Switcher),
|
||||
Opener(Opener),
|
||||
}
|
||||
|
||||
impl Task {
|
||||
|
|
@ -21,6 +22,7 @@ impl Task {
|
|||
Self::Show(s) => s.requested_height(),
|
||||
Self::Confirm(c) => c.requested_height(),
|
||||
Self::Switcher(s) => s.requested_height(),
|
||||
Self::Opener(o) => o.requested_height(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -55,6 +57,7 @@ impl Element<()> for Root {
|
|||
Task::Show(s) => s.handle(state, event),
|
||||
Task::Confirm(c) => c.handle(state, event),
|
||||
Task::Switcher(s) => s.handle(state, event),
|
||||
Task::Opener(o) => o.handle(state, event),
|
||||
};
|
||||
|
||||
match res {
|
||||
|
|
@ -83,10 +86,14 @@ impl Element<()> for Root {
|
|||
self.tasks.push(Task::Prompt(Prompt::new()));
|
||||
}
|
||||
Action::OpenSwitcher => {
|
||||
self.tasks.clear(); // Prompt overrides all
|
||||
self.tasks.clear(); // Overrides all
|
||||
self.tasks
|
||||
.push(Task::Switcher(Switcher::new(state.buffers.keys())));
|
||||
}
|
||||
Action::OpenOpener(path) => {
|
||||
self.tasks.clear(); // Overrides all
|
||||
self.tasks.push(Task::Opener(Opener::new(path)));
|
||||
}
|
||||
Action::Cancel => self.tasks.push(Task::Confirm(Confirm {
|
||||
label: Label("Are you sure you wish to quit? (y/n)".to_string()),
|
||||
action: Action::Quit,
|
||||
|
|
@ -132,6 +139,7 @@ impl Visual for Root {
|
|||
Task::Show(s) => s.render(state, frame),
|
||||
Task::Confirm(c) => c.render(state, frame),
|
||||
Task::Switcher(s) => s.render(state, frame),
|
||||
Task::Opener(o) => o.render(state, frame),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue