implemented ex mode :w/:e commands
implemented tab completion and history search for the ex mode prompt as well fixed paths not expanding correctly in ex mode command arguments
This commit is contained in:
@@ -12,21 +12,15 @@ use super::vicmd::{
|
||||
ViCmd, Word,
|
||||
};
|
||||
use crate::{
|
||||
expand::expand_cmd_sub,
|
||||
libsh::{error::ShResult, guards::var_ctx_guard},
|
||||
parse::{
|
||||
execute::exec_input,
|
||||
lex::{LexFlags, LexStream, QuoteState, Tk, TkRule},
|
||||
},
|
||||
prelude::*,
|
||||
readline::{
|
||||
expand::expand_cmd_sub, libsh::{error::ShResult, guards::var_ctx_guard}, parse::{
|
||||
Redir, RedirType, execute::exec_input, lex::{LexFlags, LexStream, QuoteState, Tk, TkFlags, TkRule}
|
||||
}, prelude::*, procio::{IoFrame, IoMode, IoStack}, readline::{
|
||||
history::History,
|
||||
markers,
|
||||
register::{RegisterContent, write_register},
|
||||
term::RawModeGuard,
|
||||
vicmd::ReadSrc,
|
||||
},
|
||||
state::{VarFlags, VarKind, read_shopts, write_meta, write_vars},
|
||||
vicmd::{ReadSrc, WriteDest},
|
||||
}, state::{VarFlags, VarKind, read_shopts, write_meta, write_vars}
|
||||
};
|
||||
|
||||
const PUNCTUATION: [&str; 3] = ["?", "!", "."];
|
||||
@@ -3358,8 +3352,54 @@ impl LineBuf {
|
||||
self.cursor.add(grapheme_count);
|
||||
}
|
||||
},
|
||||
Verb::Write(dest) => {}
|
||||
Verb::Edit(path) => {}
|
||||
Verb::Write(dest) => {
|
||||
match dest {
|
||||
WriteDest::FileAppend(ref path_buf) |
|
||||
WriteDest::File(ref path_buf) => {
|
||||
let Ok(mut file) = (if matches!(dest, WriteDest::File(_)) {
|
||||
OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.write(true)
|
||||
.open(path_buf)
|
||||
} else {
|
||||
OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(path_buf)
|
||||
}) else {
|
||||
write_meta(|m| {
|
||||
m.post_system_message(format!("Failed to open file {}", path_buf.display()))
|
||||
});
|
||||
return Ok(());
|
||||
};
|
||||
if let Err(e) = file.write_all(self.as_str().as_bytes()) {
|
||||
write_meta(|m| {
|
||||
m.post_system_message(format!("Failed to write to file {}: {e}", path_buf.display()))
|
||||
});
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
WriteDest::Cmd(cmd) => {
|
||||
let buf = self.as_str().to_string();
|
||||
let io_mode = IoMode::Buffer {
|
||||
tgt_fd: STDIN_FILENO,
|
||||
buf,
|
||||
flags: TkFlags::IS_HEREDOC | TkFlags::LIT_HEREDOC,
|
||||
};
|
||||
let redir = Redir::new(io_mode, RedirType::Input);
|
||||
let mut frame = IoFrame::new();
|
||||
frame.push(redir);
|
||||
let mut stack = IoStack::new();
|
||||
stack.push_frame(frame);
|
||||
exec_input(cmd, Some(stack), false, Some("ex write".into()))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Verb::Edit(path) => {
|
||||
let input = format!("$EDITOR {}",path.display());
|
||||
exec_input(input, None, true, Some("ex edit".into()))?;
|
||||
}
|
||||
Verb::Normal(_) | Verb::Substitute(..) | Verb::RepeatSubstitute | Verb::RepeatGlobal => {}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -346,6 +346,18 @@ impl ShedVi {
|
||||
self
|
||||
}
|
||||
|
||||
/// A mutable reference to the currently focused editor
|
||||
/// This includes the main LineBuf, and sub-editors for modes like Ex mode.
|
||||
pub fn focused_editor(&mut self) -> &mut LineBuf {
|
||||
self.mode.editor().unwrap_or(&mut self.editor)
|
||||
}
|
||||
|
||||
/// A mutable reference to the currently focused history, if any.
|
||||
/// This includes the main history struct, and history for sub-editors like Ex mode.
|
||||
pub fn focused_history(&mut self) -> &mut History {
|
||||
self.mode.history().unwrap_or(&mut self.history)
|
||||
}
|
||||
|
||||
/// Feed raw bytes from stdin into the reader's buffer
|
||||
pub fn feed_bytes(&mut self, bytes: &[u8]) {
|
||||
self.reader.feed_bytes(bytes);
|
||||
@@ -367,8 +379,8 @@ impl ShedVi {
|
||||
self.completer.reset_stay_active();
|
||||
self.needs_redraw = true;
|
||||
Ok(())
|
||||
} else if self.history.fuzzy_finder.is_active() {
|
||||
self.history.fuzzy_finder.reset_stay_active();
|
||||
} else if self.focused_history().fuzzy_finder.is_active() {
|
||||
self.focused_history().fuzzy_finder.reset_stay_active();
|
||||
self.needs_redraw = true;
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -457,20 +469,28 @@ impl ShedVi {
|
||||
// Process all available keys
|
||||
while let Some(key) = self.reader.read_key()? {
|
||||
// If completer or history search are active, delegate input to it
|
||||
if self.history.fuzzy_finder.is_active() {
|
||||
if self.focused_history().fuzzy_finder.is_active() {
|
||||
self.print_line(false)?;
|
||||
match self.history.fuzzy_finder.handle_key(key)? {
|
||||
match self.focused_history().fuzzy_finder.handle_key(key)? {
|
||||
SelectorResponse::Accept(cmd) => {
|
||||
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect));
|
||||
|
||||
self.editor.set_buffer(cmd.to_string());
|
||||
self.editor.move_cursor_to_end();
|
||||
{
|
||||
let editor = self.focused_editor();
|
||||
editor.set_buffer(cmd.to_string());
|
||||
editor.move_cursor_to_end();
|
||||
}
|
||||
|
||||
self
|
||||
.history
|
||||
.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
|
||||
self.editor.set_hint(None);
|
||||
self.history.fuzzy_finder.clear(&mut self.writer)?;
|
||||
self.history.fuzzy_finder.reset();
|
||||
{
|
||||
let mut writer = std::mem::take(&mut self.writer);
|
||||
self.focused_history().fuzzy_finder.clear(&mut writer)?;
|
||||
self.writer = writer;
|
||||
}
|
||||
self.focused_history().fuzzy_finder.reset();
|
||||
|
||||
with_vars([("_HIST_ENTRY".into(), cmd.clone())], || {
|
||||
post_cmds.exec_with(&cmd);
|
||||
@@ -493,7 +513,11 @@ impl ShedVi {
|
||||
post_cmds.exec();
|
||||
|
||||
self.editor.set_hint(None);
|
||||
self.history.fuzzy_finder.clear(&mut self.writer)?;
|
||||
{
|
||||
let mut writer = std::mem::take(&mut self.writer);
|
||||
self.focused_history().fuzzy_finder.clear(&mut writer)?;
|
||||
self.writer = writer;
|
||||
}
|
||||
write_vars(|v| {
|
||||
v.set_var(
|
||||
"SHED_VI_MODE",
|
||||
@@ -520,8 +544,8 @@ impl ShedVi {
|
||||
let span_start = self.completer.token_span().0;
|
||||
let new_cursor = span_start + candidate.len();
|
||||
let line = self.completer.get_completed_line(&candidate);
|
||||
self.editor.set_buffer(line);
|
||||
self.editor.cursor.set(new_cursor);
|
||||
self.focused_editor().set_buffer(line);
|
||||
self.focused_editor().cursor.set(new_cursor);
|
||||
// Don't reset yet — clear() needs old_layout to erase the selector.
|
||||
|
||||
if !self.history.at_pending() {
|
||||
@@ -650,7 +674,8 @@ impl ShedVi {
|
||||
}
|
||||
|
||||
if let KeyEvent(KeyCode::Tab, mod_keys) = key {
|
||||
if self.editor.attempt_history_expansion(&self.history) {
|
||||
if self.mode.report_mode() != ModeReport::Ex
|
||||
&& self.editor.attempt_history_expansion(&self.history) {
|
||||
// If history expansion occurred, don't attempt completion yet
|
||||
// allow the user to see the expanded command and accept or edit it before completing
|
||||
return Ok(None);
|
||||
@@ -660,8 +685,8 @@ impl ShedVi {
|
||||
ModKeys::SHIFT => -1,
|
||||
_ => 1,
|
||||
};
|
||||
let line = self.editor.as_str().to_string();
|
||||
let cursor_pos = self.editor.cursor_byte_pos();
|
||||
let line = self.focused_editor().as_str().to_string();
|
||||
let cursor_pos = self.focused_editor().cursor_byte_pos();
|
||||
|
||||
match self.completer.complete(line, cursor_pos, direction) {
|
||||
Err(e) => {
|
||||
@@ -685,8 +710,8 @@ impl ShedVi {
|
||||
.map(|c| c.len())
|
||||
.unwrap_or_default();
|
||||
|
||||
self.editor.set_buffer(line.clone());
|
||||
self.editor.cursor.set(new_cursor);
|
||||
self.focused_editor().set_buffer(line.clone());
|
||||
self.focused_editor().cursor.set(new_cursor);
|
||||
|
||||
if !self.history.at_pending() {
|
||||
self.history.reset_to_pending();
|
||||
@@ -748,18 +773,18 @@ impl ShedVi {
|
||||
self.needs_redraw = true;
|
||||
return Ok(None);
|
||||
} else if let KeyEvent(KeyCode::Char('R'), ModKeys::CTRL) = key
|
||||
&& self.mode.report_mode() == ModeReport::Insert
|
||||
&& matches!(self.mode.report_mode(), ModeReport::Insert | ModeReport::Ex)
|
||||
{
|
||||
let initial = self.editor.as_str();
|
||||
match self.history.start_search(initial) {
|
||||
let initial = self.focused_editor().as_str().to_string();
|
||||
match self.focused_history().start_search(&initial) {
|
||||
Some(entry) => {
|
||||
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect));
|
||||
with_vars([("_HIST_ENTRY".into(), entry.clone())], || {
|
||||
post_cmds.exec_with(&entry);
|
||||
});
|
||||
|
||||
self.editor.set_buffer(entry);
|
||||
self.editor.move_cursor_to_end();
|
||||
self.focused_editor().set_buffer(entry);
|
||||
self.focused_editor().move_cursor_to_end();
|
||||
self
|
||||
.history
|
||||
.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
|
||||
@@ -767,9 +792,9 @@ impl ShedVi {
|
||||
}
|
||||
None => {
|
||||
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistoryOpen));
|
||||
let entries = self.history.fuzzy_finder.candidates();
|
||||
let entries = self.focused_history().fuzzy_finder.candidates().to_vec();
|
||||
let matches = self
|
||||
.history
|
||||
.focused_history()
|
||||
.fuzzy_finder
|
||||
.filtered()
|
||||
.iter()
|
||||
@@ -792,7 +817,7 @@ impl ShedVi {
|
||||
},
|
||||
);
|
||||
|
||||
if self.history.fuzzy_finder.is_active() {
|
||||
if self.focused_history().fuzzy_finder.is_active() {
|
||||
write_vars(|v| {
|
||||
v.set_var(
|
||||
"SHED_VI_MODE",
|
||||
@@ -849,10 +874,10 @@ impl ShedVi {
|
||||
}
|
||||
|
||||
if cmd.verb().is_some_and(|v| v.1 == Verb::EndOfFile) {
|
||||
if self.editor.buffer.is_empty() {
|
||||
if self.focused_editor().buffer.is_empty() {
|
||||
return Ok(Some(ReadlineEvent::Eof));
|
||||
} else {
|
||||
self.editor = LineBuf::new();
|
||||
*self.focused_editor() = LineBuf::new();
|
||||
self.mode = Box::new(ViInsert::new());
|
||||
self.needs_redraw = true;
|
||||
return Ok(None);
|
||||
@@ -1007,7 +1032,11 @@ impl ShedVi {
|
||||
let one_line = new_layout.end.row == 0;
|
||||
|
||||
self.completer.clear(&mut self.writer)?;
|
||||
self.history.fuzzy_finder.clear(&mut self.writer)?;
|
||||
{
|
||||
let mut writer = std::mem::take(&mut self.writer);
|
||||
self.focused_history().fuzzy_finder.clear(&mut writer)?;
|
||||
self.writer = writer;
|
||||
}
|
||||
|
||||
if let Some(layout) = self.old_layout.as_ref() {
|
||||
self.writer.clear_rows(layout)?;
|
||||
@@ -1100,10 +1129,15 @@ impl ShedVi {
|
||||
self.completer.draw(&mut self.writer)?;
|
||||
|
||||
self
|
||||
.history
|
||||
.focused_history()
|
||||
.fuzzy_finder
|
||||
.set_prompt_line_context(preceding_width, new_layout.cursor.col);
|
||||
self.history.fuzzy_finder.draw(&mut self.writer)?;
|
||||
|
||||
{
|
||||
let mut writer = std::mem::take(&mut self.writer);
|
||||
self.focused_history().fuzzy_finder.draw(&mut writer)?;
|
||||
self.writer = writer;
|
||||
}
|
||||
|
||||
self.old_layout = Some(new_layout);
|
||||
self.needs_redraw = false;
|
||||
|
||||
@@ -893,6 +893,7 @@ impl Default for Layout {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct TermWriter {
|
||||
last_bell: Option<Instant>,
|
||||
out: RawFd,
|
||||
|
||||
@@ -7,6 +7,8 @@ use itertools::Itertools;
|
||||
use crate::bitflags;
|
||||
use crate::expand::{Expander, expand_raw};
|
||||
use crate::libsh::error::{ShErr, ShErrKind, ShResult};
|
||||
use crate::parse::lex::TkFlags;
|
||||
use crate::readline::complete::SimpleCompleter;
|
||||
use crate::readline::history::History;
|
||||
use crate::readline::keys::KeyEvent;
|
||||
use crate::readline::linebuf::LineBuf;
|
||||
@@ -152,6 +154,14 @@ impl ViMode for ViEx {
|
||||
None
|
||||
}
|
||||
|
||||
fn editor(&mut self) -> Option<&mut LineBuf> {
|
||||
Some(&mut self.pending_cmd.buf)
|
||||
}
|
||||
|
||||
fn history(&mut self) -> Option<&mut History> {
|
||||
Some(&mut self.pending_cmd.history)
|
||||
}
|
||||
|
||||
fn cursor_style(&self) -> String {
|
||||
"\x1b[3 q".to_string()
|
||||
}
|
||||
@@ -328,8 +338,13 @@ fn parse_read(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>, Option<St
|
||||
}
|
||||
|
||||
fn get_path(path: &str) -> Result<PathBuf, Option<String>> {
|
||||
let expanded = expand_raw(&mut path.chars().peekable())
|
||||
.map_err(|e| Some(format!("Error expanding path: {}", e)))?;
|
||||
log::debug!("Expanding path: {}", path);
|
||||
let expanded = Expander::from_raw(path, TkFlags::empty())
|
||||
.map_err(|e| Some(format!("Error expanding path: {}", e)))?
|
||||
.expand()
|
||||
.map_err(|e| Some(format!("Error expanding path: {}", e)))?
|
||||
.join(" ");
|
||||
log::debug!("Expanded path: {}", expanded);
|
||||
Ok(PathBuf::from(&expanded))
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@ use std::fmt::Display;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::libsh::error::ShResult;
|
||||
use crate::readline::history::History;
|
||||
use crate::readline::keys::{KeyCode as K, KeyEvent as E, ModKeys as M};
|
||||
use crate::readline::linebuf::LineBuf;
|
||||
use crate::readline::vicmd::{Motion, MotionCmd, To, Verb, VerbCmd, ViCmd};
|
||||
|
||||
pub mod ex;
|
||||
@@ -79,9 +81,9 @@ pub trait ViMode {
|
||||
fn as_replay(&self) -> Option<CmdReplay>;
|
||||
fn cursor_style(&self) -> String;
|
||||
fn pending_seq(&self) -> Option<String>;
|
||||
fn pending_cursor(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
fn pending_cursor(&self) -> Option<usize> { None }
|
||||
fn editor(&mut self) -> Option<&mut LineBuf> { None }
|
||||
fn history(&mut self) -> Option<&mut History> { None }
|
||||
fn move_cursor_on_undo(&self) -> bool;
|
||||
fn clamp_cursor(&self) -> bool;
|
||||
fn hist_scroll_start_pos(&self) -> Option<To>;
|
||||
|
||||
Reference in New Issue
Block a user