mirror of
https://github.com/nushell/nushell
synced 2025-01-13 21:55: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]]
|
[[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",
|
||||||
|
|
|
@ -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" }
|
||||||
|
|
|
@ -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
|
||||||
|
|
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 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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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))]
|
||||||
|
|
Loading…
Reference in a new issue