Added goto line command
This commit is contained in:
parent
d352d04030
commit
846cc31174
8 changed files with 117 additions and 48 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -1,6 +1,6 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ pub enum Action {
|
|||
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
|
||||
CommandStart(&'static str), // Start a new command
|
||||
GotoLine(isize), // Go to the specified file line
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -220,6 +222,22 @@ impl RawEvent {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn to_command_start(&self) -> Option<Action> {
|
||||
if matches!(
|
||||
&self.0,
|
||||
TerminalEvent::Key(KeyEvent {
|
||||
code: KeyCode::Char('l'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
kind: KeyEventKind::Press,
|
||||
..
|
||||
})
|
||||
) {
|
||||
Some(Action::CommandStart("goto_line"))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_go(&self) -> Option<Action> {
|
||||
if matches!(
|
||||
&self.0,
|
||||
|
|
|
|||
16
src/state.rs
16
src/state.rs
|
|
@ -123,7 +123,12 @@ impl Buffer {
|
|||
}
|
||||
// If the file doesn't exist, create a new file
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => {
|
||||
(path.parent().map(Path::to_owned), Vec::new())
|
||||
let dir = path
|
||||
.parent()
|
||||
.filter(|p| p.to_str() != Some(""))
|
||||
.map(Path::to_owned)
|
||||
.or_else(|| std::env::current_dir().ok());
|
||||
(dir, Vec::new())
|
||||
}
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
|
@ -152,6 +157,15 @@ impl Buffer {
|
|||
});
|
||||
}
|
||||
|
||||
pub fn goto_line_cursor(&mut self, cursor_id: CursorId, line: isize) {
|
||||
let Some(cursor) = self.cursors.get_mut(cursor_id) else {
|
||||
return;
|
||||
};
|
||||
cursor.pos = self.text.to_pos([0, line]);
|
||||
cursor.reset_desired_col(&self.text);
|
||||
cursor.base = cursor.pos;
|
||||
}
|
||||
|
||||
pub fn move_cursor(
|
||||
&mut self,
|
||||
cursor_id: CursorId,
|
||||
|
|
|
|||
|
|
@ -185,12 +185,8 @@ impl<'a> Rect<'a> {
|
|||
self.rect([0, 0], self.size())
|
||||
}
|
||||
|
||||
pub fn text<C: Borrow<char>>(
|
||||
&mut self,
|
||||
origin: [isize; 2],
|
||||
text: impl IntoIterator<Item = C>,
|
||||
) -> Rect {
|
||||
for (idx, c) in text.into_iter().enumerate() {
|
||||
pub fn text(&mut self, origin: [isize; 2], text: &str) -> Rect {
|
||||
for (idx, c) in text.chars().enumerate() {
|
||||
if (0..self.size()[0] as isize).contains(&(origin[0] + idx as isize)) && origin[1] >= 0
|
||||
{
|
||||
let cell = Cell {
|
||||
|
|
|
|||
|
|
@ -77,6 +77,11 @@ impl Input {
|
|||
self.refocus(buffer, cursor_id);
|
||||
Ok(Resp::handled(None))
|
||||
}
|
||||
Some(Action::GotoLine(line)) => {
|
||||
buffer.goto_line_cursor(cursor_id, line);
|
||||
self.refocus(buffer, cursor_id);
|
||||
Ok(Resp::handled(None))
|
||||
}
|
||||
_ => Err(event),
|
||||
}
|
||||
}
|
||||
|
|
@ -136,13 +141,13 @@ impl Input {
|
|||
.with_bg(state.theme.margin_bg)
|
||||
.with_fg(state.theme.margin_line_num)
|
||||
.fill(' ')
|
||||
.text([0, 0], ">".chars()),
|
||||
.text([0, 0], ">"),
|
||||
Mode::Doc => frame
|
||||
.rect([0, i], [margin_w, 1])
|
||||
.with_bg(state.theme.margin_bg)
|
||||
.with_fg(state.theme.margin_line_num)
|
||||
.fill(' ')
|
||||
.text([1, 0], format!("{:>line_num_w$}", line_num + 1).chars()),
|
||||
.text([1, 0], &format!("{:>line_num_w$}", line_num + 1)),
|
||||
};
|
||||
|
||||
// Line
|
||||
|
|
@ -166,7 +171,7 @@ impl Input {
|
|||
state.theme.unfocus_select_bg
|
||||
})
|
||||
.with_fg(fg)
|
||||
.text([i as isize, 0], &[c]);
|
||||
.text([i as isize, 0], c.encode_utf8(&mut [0; 4]));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ impl Visual for Label {
|
|||
fn render(&mut self, state: &State, frame: &mut Rect) {
|
||||
frame.with(|frame| {
|
||||
for (idx, line) in self.lines().enumerate() {
|
||||
frame.text([0, idx as isize], line.chars());
|
||||
frame.text([0, idx as isize], &line);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,24 +9,29 @@ pub struct Prompt {
|
|||
}
|
||||
|
||||
impl Prompt {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(init: &str) -> Self {
|
||||
let mut buffer = Buffer::default();
|
||||
let cursor_id = buffer.start_session();
|
||||
buffer.enter(cursor_id, init.chars());
|
||||
Self {
|
||||
cursor_id: buffer.start_session(),
|
||||
buffer,
|
||||
cursor_id,
|
||||
input: Input::prompt(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_action(&self) -> Option<Action> {
|
||||
match self.buffer.text.to_string().as_str() {
|
||||
pub fn parse_action(&self) -> Result<Action, String> {
|
||||
let cmd = self.buffer.text.to_string();
|
||||
let mut args = cmd.as_str().split_whitespace();
|
||||
|
||||
match args.next() {
|
||||
// The root sees 'cancel' as an initiator for quitting
|
||||
"q" | "quit" => Some(Action::Cancel),
|
||||
"version" => Some(Action::Show(
|
||||
Some("q" | "quit") => Ok(Action::Cancel),
|
||||
Some("version") => Ok(Action::Show(
|
||||
Some(format!("Version")),
|
||||
format!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")),
|
||||
)),
|
||||
"?" | "help" => Some(Action::Show(
|
||||
Some("?" | "help") => Ok(Action::Show(
|
||||
Some(format!("Help")),
|
||||
format!(
|
||||
"Temporary help info:\n\
|
||||
|
|
@ -37,9 +42,20 @@ impl Prompt {
|
|||
- help"
|
||||
),
|
||||
)),
|
||||
"pane_move_left" => Some(Action::PaneMove(Dir::Left)),
|
||||
"pane_move_right" => Some(Action::PaneMove(Dir::Right)),
|
||||
_ => None,
|
||||
Some("pane_move_left") => Ok(Action::PaneMove(Dir::Left)),
|
||||
Some("pane_move_right") => Ok(Action::PaneMove(Dir::Right)),
|
||||
Some("goto_line") => {
|
||||
// Subtract 1 due to zero indexing
|
||||
let line = args
|
||||
.next()
|
||||
.ok_or_else(|| "Expected argument".to_string())?
|
||||
.parse::<isize>()
|
||||
.map_err(|_| "Expected integer".to_string())?
|
||||
- 1;
|
||||
Ok(Action::GotoLine(line))
|
||||
}
|
||||
Some(cmd) => Err(format!("Unknown command `{cmd}`")),
|
||||
None => Err(format!("No command entered")),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -50,22 +66,20 @@ impl Prompt {
|
|||
|
||||
impl Element<()> for Prompt {
|
||||
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<()>, Event> {
|
||||
match event.to_action(|e| e.to_go().or_else(|| e.to_cancel())) {
|
||||
match event.to_action(|e| {
|
||||
e.to_go()
|
||||
.or_else(|| e.to_cancel().or_else(|| e.to_command_start()))
|
||||
}) {
|
||||
Some(Action::Cancel) => Ok(Resp::end(None)),
|
||||
Some(Action::Go) => {
|
||||
if let Some(action) = self.get_action() {
|
||||
Some(Action::Go) => match self.parse_action() {
|
||||
Ok(action) => {
|
||||
self.buffer.clear();
|
||||
Ok(Resp::end(Some(action.into())))
|
||||
} else {
|
||||
Ok(Resp::handled(Some(
|
||||
Action::Show(
|
||||
Some(format!("Error")),
|
||||
format!("unknown command `{}`", self.buffer.text.to_string()),
|
||||
)
|
||||
.into(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
Err(err) => Ok(Resp::handled(Some(
|
||||
Action::Show(Some(format!("Error")), err).into(),
|
||||
))),
|
||||
},
|
||||
_ => self
|
||||
.input
|
||||
.handle(&mut self.buffer, self.cursor_id, event)
|
||||
|
|
@ -248,7 +262,7 @@ impl Visual for BufferId {
|
|||
let Some(buffer) = state.buffers.get(*self) else {
|
||||
return;
|
||||
};
|
||||
frame.text([0, 0], buffer.name().unwrap_or("<unknown>").chars());
|
||||
frame.text([0, 0], buffer.name().unwrap_or("<unknown>"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -313,11 +327,15 @@ impl Opener {
|
|||
is_link: entry.file_type().ok()?.is_symlink(),
|
||||
})
|
||||
})
|
||||
.chain([FileOption {
|
||||
path: [dir, &filter].into_iter().collect(),
|
||||
kind: FileKind::New,
|
||||
is_link: false,
|
||||
}]);
|
||||
.chain(if filter != "" {
|
||||
Some(FileOption {
|
||||
path: [dir, &filter].into_iter().collect(),
|
||||
kind: FileKind::New,
|
||||
is_link: false,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
});
|
||||
// TODO
|
||||
self.options.set_options(options, |e| {
|
||||
let name = e.path.file_name()?.to_str()?.to_lowercase();
|
||||
|
|
@ -395,9 +413,15 @@ pub struct FileOption {
|
|||
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>"),
|
||||
Some(name) if matches!(self.kind, FileKind::Dir) => format!("{name}/"),
|
||||
Some(name) => format!("{name}"),
|
||||
None => format!("Unknown"),
|
||||
};
|
||||
let desc = match self.kind {
|
||||
FileKind::Dir => "Directory",
|
||||
FileKind::Unknown => "Unknown filesystem item",
|
||||
FileKind::File => "File",
|
||||
FileKind::New => "Create new file",
|
||||
};
|
||||
frame
|
||||
.with_fg(match self.kind {
|
||||
|
|
@ -405,7 +429,10 @@ impl Visual for FileOption {
|
|||
FileKind::File | FileKind::Unknown => state.theme.option_file,
|
||||
FileKind::New => state.theme.option_new,
|
||||
})
|
||||
.text([0, 0], name.chars());
|
||||
.text([0, 0], &name);
|
||||
frame.with_fg(state.theme.margin_line_num).with(|f| {
|
||||
f.text([f.size()[0] as isize / 2, 0], &desc);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,13 +77,17 @@ impl Element<()> for Root {
|
|||
};
|
||||
|
||||
// Handle 'top-level' actions
|
||||
if let Some(action) =
|
||||
action.and_then(|e| e.to_action(|e| e.to_open_prompt().or_else(|| e.to_cancel())))
|
||||
{
|
||||
if let Some(action) = action.and_then(|e| {
|
||||
e.to_action(|e| {
|
||||
e.to_open_prompt()
|
||||
.or_else(|| e.to_cancel())
|
||||
.or_else(|| e.to_command_start())
|
||||
})
|
||||
}) {
|
||||
match action {
|
||||
Action::OpenPrompt => {
|
||||
self.tasks.clear(); // Prompt overrides all
|
||||
self.tasks.push(Task::Prompt(Prompt::new()));
|
||||
self.tasks.push(Task::Prompt(Prompt::new("")));
|
||||
}
|
||||
Action::OpenSwitcher => {
|
||||
self.tasks.clear(); // Overrides all
|
||||
|
|
@ -94,6 +98,11 @@ impl Element<()> for Root {
|
|||
self.tasks.clear(); // Overrides all
|
||||
self.tasks.push(Task::Opener(Opener::new(path)));
|
||||
}
|
||||
Action::CommandStart(cmd) => {
|
||||
self.tasks.clear(); // Prompt overrides all
|
||||
self.tasks
|
||||
.push(Task::Prompt(Prompt::new(&format!("{cmd} "))));
|
||||
}
|
||||
Action::Cancel => self.tasks.push(Task::Confirm(Confirm {
|
||||
label: Label("Are you sure you wish to quit? (y/n)".to_string()),
|
||||
action: Action::Quit,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue