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:
unrelentingtech 2022-09-09 23:31:32 +03:00 committed by GitHub
parent 3e0655cdba
commit 9ee4086dfa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 131 additions and 7 deletions

3
Cargo.lock generated
View file

@ -4079,8 +4079,7 @@ dependencies = [
[[package]] [[package]]
name = "reedline" name = "reedline"
version = "0.11.0" version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/nushell/reedline?branch=main#dc091e828590de6fd335af3f1d001ede851ac20a"
checksum = "5559b5ab4817b0da0c6fc6814edfae537209e01d955a2f3e7595606e3d039691"
dependencies = [ dependencies = [
"chrono", "chrono",
"crossterm 0.24.0", "crossterm 0.24.0",

View file

@ -122,3 +122,5 @@ debug = false
name = "nu" name = "nu"
path = "src/main.rs" path = "src/main.rs"
[patch.crates-io]
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }

View file

@ -14,11 +14,11 @@ use nu_engine::{convert_env_values, eval_block};
use nu_parser::{lex, parse}; use nu_parser::{lex, parse};
use nu_protocol::{ use nu_protocol::{
ast::PathMember, ast::PathMember,
engine::{EngineState, Stack, StateWorkingSet}, engine::{EngineState, ReplOperation, Stack, StateWorkingSet},
format_duration, BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span, format_duration, BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span,
Spanned, Type, Value, VarId, Spanned, Type, Value, VarId,
}; };
use reedline::{DefaultHinter, Emacs, SqliteBackedHistory, Vi}; use reedline::{DefaultHinter, EditCommand, Emacs, SqliteBackedHistory, Vi};
use std::io::{self, Write}; use std::io::{self, Write};
use std::{sync::atomic::Ordering, time::Instant}; use std::{sync::atomic::Ordering, time::Instant};
use strip_ansi_escapes::strip; use strip_ansi_escapes::strip;
@ -347,6 +347,12 @@ pub fn evaluate_repl(
.into_diagnostic()?; // todo: don't stop repl if error here? .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, // Right before we start running the code the user gave us,
// fire the "pre_execution" hook // fire the "pre_execution" hook
if let Some(hook) = config.hooks.pre_execution.clone() { if let Some(hook) = config.hooks.pre_execution.clone() {
@ -489,6 +495,24 @@ pub fn evaluate_repl(
} }
run_ansi_sequence(RESET_APPLICATION_MODE)?; 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) => { Ok(Signal::CtrlC) => {
// `Reedline` clears the line content. New prompt is shown // `Reedline` clears the line content. New prompt is shown

View 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())
}
}
}

View file

@ -1,5 +1,6 @@
mod alias; mod alias;
mod ast; mod ast;
mod commandline;
mod debug; mod debug;
mod def; mod def;
mod def_env; mod def_env;
@ -30,6 +31,7 @@ mod version;
pub use alias::Alias; pub use alias::Alias;
pub use ast::Ast; pub use ast::Ast;
pub use commandline::Commandline;
pub use debug::Debug; pub use debug::Debug;
pub use def::Def; pub use def::Def;
pub use def_env::DefEnv; pub use def_env::DefEnv;

View file

@ -30,6 +30,7 @@ pub fn create_default_context() -> EngineState {
bind_command! { bind_command! {
Alias, Alias,
Ast, Ast,
Commandline,
Debug, Debug,
Def, Def,
DefEnv, DefEnv,

View file

@ -6,16 +6,24 @@ use crate::{
}; };
use core::panic; use core::panic;
use std::borrow::Borrow; use std::borrow::Borrow;
use std::collections::HashSet;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::{ use std::{
collections::HashMap, collections::{HashMap, HashSet, VecDeque},
sync::{atomic::AtomicBool, Arc}, sync::{atomic::AtomicBool, Arc, Mutex},
}; };
static PWD_ENV: &str = "PWD"; 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 /// The core global engine state. This includes all global definitions as well as any global state that
/// will persist for the whole session. /// will persist for the whole session.
/// ///
@ -72,6 +80,8 @@ pub struct EngineState {
pub env_vars: EnvVars, pub env_vars: EnvVars,
pub previous_env_vars: HashMap<String, Value>, pub previous_env_vars: HashMap<String, Value>,
pub config: Config, pub config: Config,
pub repl_buffer_state: Arc<Mutex<Option<String>>>,
pub repl_operation_queue: Arc<Mutex<VecDeque<ReplOperation>>>,
#[cfg(feature = "plugin")] #[cfg(feature = "plugin")]
pub plugin_signatures: Option<PathBuf>, pub plugin_signatures: Option<PathBuf>,
#[cfg(not(windows))] #[cfg(not(windows))]
@ -110,6 +120,8 @@ impl EngineState {
env_vars: EnvVars::from([(DEFAULT_OVERLAY_NAME.to_string(), HashMap::new())]), env_vars: EnvVars::from([(DEFAULT_OVERLAY_NAME.to_string(), HashMap::new())]),
previous_env_vars: HashMap::new(), previous_env_vars: HashMap::new(),
config: Config::default(), config: Config::default(),
repl_buffer_state: Arc::new(Mutex::new(None)),
repl_operation_queue: Arc::new(Mutex::new(VecDeque::new())),
#[cfg(feature = "plugin")] #[cfg(feature = "plugin")]
plugin_signatures: None, plugin_signatures: None,
#[cfg(not(windows))] #[cfg(not(windows))]