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,
|
ViCmd, Word,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
expand::expand_cmd_sub,
|
expand::expand_cmd_sub, libsh::{error::ShResult, guards::var_ctx_guard}, parse::{
|
||||||
libsh::{error::ShResult, guards::var_ctx_guard},
|
Redir, RedirType, execute::exec_input, lex::{LexFlags, LexStream, QuoteState, Tk, TkFlags, TkRule}
|
||||||
parse::{
|
}, prelude::*, procio::{IoFrame, IoMode, IoStack}, readline::{
|
||||||
execute::exec_input,
|
|
||||||
lex::{LexFlags, LexStream, QuoteState, Tk, TkRule},
|
|
||||||
},
|
|
||||||
prelude::*,
|
|
||||||
readline::{
|
|
||||||
history::History,
|
history::History,
|
||||||
markers,
|
markers,
|
||||||
register::{RegisterContent, write_register},
|
register::{RegisterContent, write_register},
|
||||||
term::RawModeGuard,
|
term::RawModeGuard,
|
||||||
vicmd::ReadSrc,
|
vicmd::{ReadSrc, WriteDest},
|
||||||
},
|
}, state::{VarFlags, VarKind, read_shopts, write_meta, write_vars}
|
||||||
state::{VarFlags, VarKind, read_shopts, write_meta, write_vars},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const PUNCTUATION: [&str; 3] = ["?", "!", "."];
|
const PUNCTUATION: [&str; 3] = ["?", "!", "."];
|
||||||
@@ -3358,8 +3352,54 @@ impl LineBuf {
|
|||||||
self.cursor.add(grapheme_count);
|
self.cursor.add(grapheme_count);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Verb::Write(dest) => {}
|
Verb::Write(dest) => {
|
||||||
Verb::Edit(path) => {}
|
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 => {}
|
Verb::Normal(_) | Verb::Substitute(..) | Verb::RepeatSubstitute | Verb::RepeatGlobal => {}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -346,6 +346,18 @@ impl ShedVi {
|
|||||||
self
|
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
|
/// Feed raw bytes from stdin into the reader's buffer
|
||||||
pub fn feed_bytes(&mut self, bytes: &[u8]) {
|
pub fn feed_bytes(&mut self, bytes: &[u8]) {
|
||||||
self.reader.feed_bytes(bytes);
|
self.reader.feed_bytes(bytes);
|
||||||
@@ -367,8 +379,8 @@ impl ShedVi {
|
|||||||
self.completer.reset_stay_active();
|
self.completer.reset_stay_active();
|
||||||
self.needs_redraw = true;
|
self.needs_redraw = true;
|
||||||
Ok(())
|
Ok(())
|
||||||
} else if self.history.fuzzy_finder.is_active() {
|
} else if self.focused_history().fuzzy_finder.is_active() {
|
||||||
self.history.fuzzy_finder.reset_stay_active();
|
self.focused_history().fuzzy_finder.reset_stay_active();
|
||||||
self.needs_redraw = true;
|
self.needs_redraw = true;
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@@ -457,20 +469,28 @@ impl ShedVi {
|
|||||||
// Process all available keys
|
// Process all available keys
|
||||||
while let Some(key) = self.reader.read_key()? {
|
while let Some(key) = self.reader.read_key()? {
|
||||||
// If completer or history search are active, delegate input to it
|
// 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)?;
|
self.print_line(false)?;
|
||||||
match self.history.fuzzy_finder.handle_key(key)? {
|
match self.focused_history().fuzzy_finder.handle_key(key)? {
|
||||||
SelectorResponse::Accept(cmd) => {
|
SelectorResponse::Accept(cmd) => {
|
||||||
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect));
|
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
|
self
|
||||||
.history
|
.history
|
||||||
.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
|
.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
|
||||||
self.editor.set_hint(None);
|
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())], || {
|
with_vars([("_HIST_ENTRY".into(), cmd.clone())], || {
|
||||||
post_cmds.exec_with(&cmd);
|
post_cmds.exec_with(&cmd);
|
||||||
@@ -493,7 +513,11 @@ impl ShedVi {
|
|||||||
post_cmds.exec();
|
post_cmds.exec();
|
||||||
|
|
||||||
self.editor.set_hint(None);
|
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| {
|
write_vars(|v| {
|
||||||
v.set_var(
|
v.set_var(
|
||||||
"SHED_VI_MODE",
|
"SHED_VI_MODE",
|
||||||
@@ -520,8 +544,8 @@ impl ShedVi {
|
|||||||
let span_start = self.completer.token_span().0;
|
let span_start = self.completer.token_span().0;
|
||||||
let new_cursor = span_start + candidate.len();
|
let new_cursor = span_start + candidate.len();
|
||||||
let line = self.completer.get_completed_line(&candidate);
|
let line = self.completer.get_completed_line(&candidate);
|
||||||
self.editor.set_buffer(line);
|
self.focused_editor().set_buffer(line);
|
||||||
self.editor.cursor.set(new_cursor);
|
self.focused_editor().cursor.set(new_cursor);
|
||||||
// Don't reset yet — clear() needs old_layout to erase the selector.
|
// Don't reset yet — clear() needs old_layout to erase the selector.
|
||||||
|
|
||||||
if !self.history.at_pending() {
|
if !self.history.at_pending() {
|
||||||
@@ -650,7 +674,8 @@ impl ShedVi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let KeyEvent(KeyCode::Tab, mod_keys) = key {
|
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
|
// If history expansion occurred, don't attempt completion yet
|
||||||
// allow the user to see the expanded command and accept or edit it before completing
|
// allow the user to see the expanded command and accept or edit it before completing
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
@@ -660,8 +685,8 @@ impl ShedVi {
|
|||||||
ModKeys::SHIFT => -1,
|
ModKeys::SHIFT => -1,
|
||||||
_ => 1,
|
_ => 1,
|
||||||
};
|
};
|
||||||
let line = self.editor.as_str().to_string();
|
let line = self.focused_editor().as_str().to_string();
|
||||||
let cursor_pos = self.editor.cursor_byte_pos();
|
let cursor_pos = self.focused_editor().cursor_byte_pos();
|
||||||
|
|
||||||
match self.completer.complete(line, cursor_pos, direction) {
|
match self.completer.complete(line, cursor_pos, direction) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -685,8 +710,8 @@ impl ShedVi {
|
|||||||
.map(|c| c.len())
|
.map(|c| c.len())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
self.editor.set_buffer(line.clone());
|
self.focused_editor().set_buffer(line.clone());
|
||||||
self.editor.cursor.set(new_cursor);
|
self.focused_editor().cursor.set(new_cursor);
|
||||||
|
|
||||||
if !self.history.at_pending() {
|
if !self.history.at_pending() {
|
||||||
self.history.reset_to_pending();
|
self.history.reset_to_pending();
|
||||||
@@ -748,18 +773,18 @@ impl ShedVi {
|
|||||||
self.needs_redraw = true;
|
self.needs_redraw = true;
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
} else if let KeyEvent(KeyCode::Char('R'), ModKeys::CTRL) = key
|
} 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();
|
let initial = self.focused_editor().as_str().to_string();
|
||||||
match self.history.start_search(initial) {
|
match self.focused_history().start_search(&initial) {
|
||||||
Some(entry) => {
|
Some(entry) => {
|
||||||
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect));
|
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect));
|
||||||
with_vars([("_HIST_ENTRY".into(), entry.clone())], || {
|
with_vars([("_HIST_ENTRY".into(), entry.clone())], || {
|
||||||
post_cmds.exec_with(&entry);
|
post_cmds.exec_with(&entry);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.editor.set_buffer(entry);
|
self.focused_editor().set_buffer(entry);
|
||||||
self.editor.move_cursor_to_end();
|
self.focused_editor().move_cursor_to_end();
|
||||||
self
|
self
|
||||||
.history
|
.history
|
||||||
.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
|
.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
|
||||||
@@ -767,9 +792,9 @@ impl ShedVi {
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistoryOpen));
|
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
|
let matches = self
|
||||||
.history
|
.focused_history()
|
||||||
.fuzzy_finder
|
.fuzzy_finder
|
||||||
.filtered()
|
.filtered()
|
||||||
.iter()
|
.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| {
|
write_vars(|v| {
|
||||||
v.set_var(
|
v.set_var(
|
||||||
"SHED_VI_MODE",
|
"SHED_VI_MODE",
|
||||||
@@ -849,10 +874,10 @@ impl ShedVi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cmd.verb().is_some_and(|v| v.1 == Verb::EndOfFile) {
|
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));
|
return Ok(Some(ReadlineEvent::Eof));
|
||||||
} else {
|
} else {
|
||||||
self.editor = LineBuf::new();
|
*self.focused_editor() = LineBuf::new();
|
||||||
self.mode = Box::new(ViInsert::new());
|
self.mode = Box::new(ViInsert::new());
|
||||||
self.needs_redraw = true;
|
self.needs_redraw = true;
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
@@ -1007,7 +1032,11 @@ impl ShedVi {
|
|||||||
let one_line = new_layout.end.row == 0;
|
let one_line = new_layout.end.row == 0;
|
||||||
|
|
||||||
self.completer.clear(&mut self.writer)?;
|
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() {
|
if let Some(layout) = self.old_layout.as_ref() {
|
||||||
self.writer.clear_rows(layout)?;
|
self.writer.clear_rows(layout)?;
|
||||||
@@ -1100,10 +1129,15 @@ impl ShedVi {
|
|||||||
self.completer.draw(&mut self.writer)?;
|
self.completer.draw(&mut self.writer)?;
|
||||||
|
|
||||||
self
|
self
|
||||||
.history
|
.focused_history()
|
||||||
.fuzzy_finder
|
.fuzzy_finder
|
||||||
.set_prompt_line_context(preceding_width, new_layout.cursor.col);
|
.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.old_layout = Some(new_layout);
|
||||||
self.needs_redraw = false;
|
self.needs_redraw = false;
|
||||||
|
|||||||
@@ -893,6 +893,7 @@ impl Default for Layout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct TermWriter {
|
pub struct TermWriter {
|
||||||
last_bell: Option<Instant>,
|
last_bell: Option<Instant>,
|
||||||
out: RawFd,
|
out: RawFd,
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ use itertools::Itertools;
|
|||||||
use crate::bitflags;
|
use crate::bitflags;
|
||||||
use crate::expand::{Expander, expand_raw};
|
use crate::expand::{Expander, expand_raw};
|
||||||
use crate::libsh::error::{ShErr, ShErrKind, ShResult};
|
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::history::History;
|
||||||
use crate::readline::keys::KeyEvent;
|
use crate::readline::keys::KeyEvent;
|
||||||
use crate::readline::linebuf::LineBuf;
|
use crate::readline::linebuf::LineBuf;
|
||||||
@@ -152,6 +154,14 @@ impl ViMode for ViEx {
|
|||||||
None
|
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 {
|
fn cursor_style(&self) -> String {
|
||||||
"\x1b[3 q".to_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>> {
|
fn get_path(path: &str) -> Result<PathBuf, Option<String>> {
|
||||||
let expanded = expand_raw(&mut path.chars().peekable())
|
log::debug!("Expanding path: {}", path);
|
||||||
.map_err(|e| Some(format!("Error expanding path: {}", e)))?;
|
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))
|
Ok(PathBuf::from(&expanded))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ use std::fmt::Display;
|
|||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::libsh::error::ShResult;
|
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::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};
|
use crate::readline::vicmd::{Motion, MotionCmd, To, Verb, VerbCmd, ViCmd};
|
||||||
|
|
||||||
pub mod ex;
|
pub mod ex;
|
||||||
@@ -79,9 +81,9 @@ pub trait ViMode {
|
|||||||
fn as_replay(&self) -> Option<CmdReplay>;
|
fn as_replay(&self) -> Option<CmdReplay>;
|
||||||
fn cursor_style(&self) -> String;
|
fn cursor_style(&self) -> String;
|
||||||
fn pending_seq(&self) -> Option<String>;
|
fn pending_seq(&self) -> Option<String>;
|
||||||
fn pending_cursor(&self) -> Option<usize> {
|
fn pending_cursor(&self) -> Option<usize> { None }
|
||||||
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 move_cursor_on_undo(&self) -> bool;
|
||||||
fn clamp_cursor(&self) -> bool;
|
fn clamp_cursor(&self) -> bool;
|
||||||
fn hist_scroll_start_pos(&self) -> Option<To>;
|
fn hist_scroll_start_pos(&self) -> Option<To>;
|
||||||
|
|||||||
Reference in New Issue
Block a user