mirror of
https://github.com/nushell/nushell
synced 2024-12-26 13:03:07 +00:00
Add a 'commandline' command for manipulating the current buffer (#6492)
* Add a 'commandline' command for manipulating the current buffer from `executehostcommand` keybindings. Inspired by fish: https://fishshell.com/docs/current/cmds/commandline.html * Update to development reedline Includes nushell/reedline#472 Co-authored-by: sholderbach <sholderbach@users.noreply.github.com>
This commit is contained in:
parent
3e0655cdba
commit
9ee4086dfa
7 changed files with 131 additions and 7 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -4079,8 +4079,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "reedline"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5559b5ab4817b0da0c6fc6814edfae537209e01d955a2f3e7595606e3d039691"
|
||||
source = "git+https://github.com/nushell/reedline?branch=main#dc091e828590de6fd335af3f1d001ede851ac20a"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crossterm 0.24.0",
|
||||
|
|
|
@ -122,3 +122,5 @@ debug = false
|
|||
name = "nu"
|
||||
path = "src/main.rs"
|
||||
|
||||
[patch.crates-io]
|
||||
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
|
||||
|
|
|
@ -14,11 +14,11 @@ use nu_engine::{convert_env_values, eval_block};
|
|||
use nu_parser::{lex, parse};
|
||||
use nu_protocol::{
|
||||
ast::PathMember,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
engine::{EngineState, ReplOperation, Stack, StateWorkingSet},
|
||||
format_duration, BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span,
|
||||
Spanned, Type, Value, VarId,
|
||||
};
|
||||
use reedline::{DefaultHinter, Emacs, SqliteBackedHistory, Vi};
|
||||
use reedline::{DefaultHinter, EditCommand, Emacs, SqliteBackedHistory, Vi};
|
||||
use std::io::{self, Write};
|
||||
use std::{sync::atomic::Ordering, time::Instant};
|
||||
use strip_ansi_escapes::strip;
|
||||
|
@ -347,6 +347,12 @@ pub fn evaluate_repl(
|
|||
.into_diagnostic()?; // todo: don't stop repl if error here?
|
||||
}
|
||||
|
||||
engine_state
|
||||
.repl_buffer_state
|
||||
.lock()
|
||||
.expect("repl buffer state mutex")
|
||||
.replace(line_editor.current_buffer_contents().to_string());
|
||||
|
||||
// Right before we start running the code the user gave us,
|
||||
// fire the "pre_execution" hook
|
||||
if let Some(hook) = config.hooks.pre_execution.clone() {
|
||||
|
@ -489,6 +495,24 @@ pub fn evaluate_repl(
|
|||
}
|
||||
run_ansi_sequence(RESET_APPLICATION_MODE)?;
|
||||
}
|
||||
|
||||
let mut ops = engine_state
|
||||
.repl_operation_queue
|
||||
.lock()
|
||||
.expect("repl op queue mutex");
|
||||
while let Some(op) = ops.pop_front() {
|
||||
match op {
|
||||
ReplOperation::Append(s) => line_editor.run_edit_commands(&[
|
||||
EditCommand::MoveToEnd,
|
||||
EditCommand::InsertString(s),
|
||||
]),
|
||||
ReplOperation::Insert(s) => {
|
||||
line_editor.run_edit_commands(&[EditCommand::InsertString(s)])
|
||||
}
|
||||
ReplOperation::Replace(s) => line_editor
|
||||
.run_edit_commands(&[EditCommand::Clear, EditCommand::InsertString(s)]),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Signal::CtrlC) => {
|
||||
// `Reedline` clears the line content. New prompt is shown
|
||||
|
|
84
crates/nu-command/src/core_commands/commandline.rs
Normal file
84
crates/nu-command/src/core_commands/commandline.rs
Normal file
|
@ -0,0 +1,84 @@
|
|||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::ReplOperation;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::Category;
|
||||
use nu_protocol::IntoPipelineData;
|
||||
use nu_protocol::{PipelineData, ShellError, Signature, SyntaxShape, Value};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Commandline;
|
||||
|
||||
impl Command for Commandline {
|
||||
fn name(&self) -> &str {
|
||||
"commandline"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("commandline")
|
||||
.switch(
|
||||
"append",
|
||||
"appends the string to the end of the buffer",
|
||||
Some('a'),
|
||||
)
|
||||
.switch(
|
||||
"insert",
|
||||
"inserts the string into the buffer at the cursor position",
|
||||
Some('i'),
|
||||
)
|
||||
.switch(
|
||||
"replace",
|
||||
"replaces the current contents of the buffer (default)",
|
||||
Some('r'),
|
||||
)
|
||||
.optional(
|
||||
"cmd",
|
||||
SyntaxShape::String,
|
||||
"the string to perform the operation with",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"View or modify the current command line input buffer"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["repl", "interactive"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
if let Some(cmd) = call.opt::<Value>(engine_state, stack, 0)? {
|
||||
let mut ops = engine_state
|
||||
.repl_operation_queue
|
||||
.lock()
|
||||
.expect("repl op queue mutex");
|
||||
ops.push_back(if call.has_flag("append") {
|
||||
ReplOperation::Append(cmd.as_string()?)
|
||||
} else if call.has_flag("insert") {
|
||||
ReplOperation::Insert(cmd.as_string()?)
|
||||
} else {
|
||||
ReplOperation::Replace(cmd.as_string()?)
|
||||
});
|
||||
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
|
||||
} else if let Some(ref cmd) = *engine_state
|
||||
.repl_buffer_state
|
||||
.lock()
|
||||
.expect("repl buffer state mutex")
|
||||
{
|
||||
Ok(Value::String {
|
||||
val: cmd.clone(),
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
} else {
|
||||
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
mod alias;
|
||||
mod ast;
|
||||
mod commandline;
|
||||
mod debug;
|
||||
mod def;
|
||||
mod def_env;
|
||||
|
@ -30,6 +31,7 @@ mod version;
|
|||
|
||||
pub use alias::Alias;
|
||||
pub use ast::Ast;
|
||||
pub use commandline::Commandline;
|
||||
pub use debug::Debug;
|
||||
pub use def::Def;
|
||||
pub use def_env::DefEnv;
|
||||
|
|
|
@ -30,6 +30,7 @@ pub fn create_default_context() -> EngineState {
|
|||
bind_command! {
|
||||
Alias,
|
||||
Ast,
|
||||
Commandline,
|
||||
Debug,
|
||||
Def,
|
||||
DefEnv,
|
||||
|
|
|
@ -6,16 +6,24 @@ use crate::{
|
|||
};
|
||||
use core::panic;
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
sync::{atomic::AtomicBool, Arc, Mutex},
|
||||
};
|
||||
|
||||
static PWD_ENV: &str = "PWD";
|
||||
|
||||
// TODO: move to different file? where?
|
||||
/// An operation to be performed with the current buffer of the interactive shell.
|
||||
#[derive(Clone)]
|
||||
pub enum ReplOperation {
|
||||
Append(String),
|
||||
Insert(String),
|
||||
Replace(String),
|
||||
}
|
||||
|
||||
/// The core global engine state. This includes all global definitions as well as any global state that
|
||||
/// will persist for the whole session.
|
||||
///
|
||||
|
@ -72,6 +80,8 @@ pub struct EngineState {
|
|||
pub env_vars: EnvVars,
|
||||
pub previous_env_vars: HashMap<String, Value>,
|
||||
pub config: Config,
|
||||
pub repl_buffer_state: Arc<Mutex<Option<String>>>,
|
||||
pub repl_operation_queue: Arc<Mutex<VecDeque<ReplOperation>>>,
|
||||
#[cfg(feature = "plugin")]
|
||||
pub plugin_signatures: Option<PathBuf>,
|
||||
#[cfg(not(windows))]
|
||||
|
@ -110,6 +120,8 @@ impl EngineState {
|
|||
env_vars: EnvVars::from([(DEFAULT_OVERLAY_NAME.to_string(), HashMap::new())]),
|
||||
previous_env_vars: HashMap::new(),
|
||||
config: Config::default(),
|
||||
repl_buffer_state: Arc::new(Mutex::new(None)),
|
||||
repl_operation_queue: Arc::new(Mutex::new(VecDeque::new())),
|
||||
#[cfg(feature = "plugin")]
|
||||
plugin_signatures: None,
|
||||
#[cfg(not(windows))]
|
||||
|
|
Loading…
Reference in a new issue