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]]
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",

View file

@ -122,3 +122,5 @@ debug = false
name = "nu"
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_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

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 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;

View file

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

View file

@ -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))]