From b4f918b889afcbca836ad11b9df7e9353324690b Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 14 Sep 2021 16:59:46 +1200 Subject: [PATCH] Very early proof-of-concept git branch completion --- crates/nu-cli/src/completions.rs | 40 ++++++++++++- crates/nu-cli/src/syntax_highlight.rs | 1 + crates/nu-command/src/default_context.rs | 9 ++- crates/nu-command/src/git.rs | 51 ++++++++++++++++ crates/nu-command/src/git_checkout.rs | 66 +++++++++++++++++++++ crates/nu-command/src/lib.rs | 6 ++ crates/nu-command/src/list_git_branches.rs | 69 ++++++++++++++++++++++ crates/nu-parser/src/flatten.rs | 5 ++ crates/nu-parser/src/parser.rs | 44 ++++++++++++++ crates/nu-protocol/src/ast/expression.rs | 2 + crates/nu-protocol/src/syntax_shape.rs | 4 ++ 11 files changed, 293 insertions(+), 4 deletions(-) create mode 100644 crates/nu-command/src/git.rs create mode 100644 crates/nu-command/src/git_checkout.rs create mode 100644 crates/nu-command/src/list_git_branches.rs diff --git a/crates/nu-cli/src/completions.rs b/crates/nu-cli/src/completions.rs index 45b9b17bd0..2140ca0da9 100644 --- a/crates/nu-cli/src/completions.rs +++ b/crates/nu-cli/src/completions.rs @@ -1,7 +1,11 @@ use std::{cell::RefCell, rc::Rc}; +use nu_engine::eval_block; use nu_parser::{flatten_block, parse}; -use nu_protocol::engine::{EngineState, StateWorkingSet}; +use nu_protocol::{ + engine::{EngineState, EvaluationContext, Stack, StateWorkingSet}, + Value, +}; use reedline::Completer; pub struct NuCompleter { @@ -26,7 +30,39 @@ impl Completer for NuCompleter { for flat in flattened { if pos >= flat.0.start && pos <= flat.0.end { - match flat.1 { + match &flat.1 { + nu_parser::FlatShape::Custom(custom_completion) => { + let prefix = working_set.get_span_contents(flat.0).to_vec(); + + let (block, ..) = + parse(&mut working_set, None, custom_completion.as_bytes(), false); + let context = EvaluationContext { + engine_state: self.engine_state.clone(), + stack: Stack::default(), + }; + let result = eval_block(&context, &block, Value::nothing()); + + let v: Vec<_> = match result { + Ok(Value::List { vals, .. }) => vals + .into_iter() + .map(move |x| { + let s = x.as_string().expect("FIXME"); + + ( + reedline::Span { + start: flat.0.start - offset, + end: flat.0.end - offset, + }, + s, + ) + }) + .filter(|x| x.1.as_bytes().starts_with(&prefix)) + .collect(), + _ => vec![], + }; + + return v; + } nu_parser::FlatShape::External | nu_parser::FlatShape::InternalCall => { let prefix = working_set.get_span_contents(flat.0); let results = working_set.find_commands_by_prefix(prefix); diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index 54804b76f5..87ec535ed0 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -39,6 +39,7 @@ impl Highlighter for NuHighlighter { [(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)] .to_string(); match shape.1 { + FlatShape::Custom(..) => output.push((Style::new().bold(), next_token)), FlatShape::External => output.push((Style::new().bold(), next_token)), FlatShape::ExternalArg => output.push((Style::new().bold(), next_token)), FlatShape::Garbage => output.push(( diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index f9a61a77b9..9291a9742b 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -6,8 +6,8 @@ use nu_protocol::{ }; use crate::{ - where_::Where, Alias, Benchmark, BuildString, Def, Do, Each, For, If, Length, Let, LetEnv, Ls, - Table, + where_::Where, Alias, Benchmark, BuildString, Def, Do, Each, For, Git, GitCheckout, If, Length, + Let, LetEnv, ListGitBranches, Ls, Table, }; pub fn create_default_context() -> Rc> { @@ -48,6 +48,11 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(Table)); + // This is a WIP proof of concept + working_set.add_decl(Box::new(ListGitBranches)); + working_set.add_decl(Box::new(Git)); + working_set.add_decl(Box::new(GitCheckout)); + let sig = Signature::build("exit"); working_set.add_decl(sig.predeclare()); let sig = Signature::build("vars"); diff --git a/crates/nu-command/src/git.rs b/crates/nu-command/src/git.rs new file mode 100644 index 0000000000..5fe8521f39 --- /dev/null +++ b/crates/nu-command/src/git.rs @@ -0,0 +1,51 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, Value}; + +pub struct Git; + +impl Command for Git { + fn name(&self) -> &str { + "git" + } + + fn usage(&self) -> &str { + "Run a block" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("git") + } + + fn run( + &self, + _context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + use std::process::Command as ProcessCommand; + use std::process::Stdio; + + let proc = ProcessCommand::new("git").stdout(Stdio::piped()).spawn(); + + match proc { + Ok(child) => { + match child.wait_with_output() { + Ok(val) => { + let result = val.stdout; + + Ok(Value::string(&String::from_utf8_lossy(&result), call.head)) + } + Err(_err) => { + // FIXME + Ok(Value::nothing()) + } + } + } + Err(_err) => { + // FIXME + Ok(Value::nothing()) + } + } + } +} diff --git a/crates/nu-command/src/git_checkout.rs b/crates/nu-command/src/git_checkout.rs new file mode 100644 index 0000000000..143fff96b3 --- /dev/null +++ b/crates/nu-command/src/git_checkout.rs @@ -0,0 +1,66 @@ +use nu_engine::eval_expression; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, SyntaxShape, Value}; + +pub struct GitCheckout; + +impl Command for GitCheckout { + fn name(&self) -> &str { + "git checkout" + } + + fn usage(&self) -> &str { + "Checkout a git revision" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("git checkout").required( + "branch", + SyntaxShape::Custom(Box::new(SyntaxShape::String), "list-git-branches".into()), + "the branch to checkout", + ) + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + use std::process::Command as ProcessCommand; + use std::process::Stdio; + + let block = &call.positional[0]; + + let out = eval_expression(context, block)?; + + let out = out.as_string()?; + + let proc = ProcessCommand::new("git") + .arg("checkout") + .arg(out) + .stdout(Stdio::piped()) + .spawn(); + + match proc { + Ok(child) => { + match child.wait_with_output() { + Ok(val) => { + let result = val.stdout; + + Ok(Value::string(&String::from_utf8_lossy(&result), call.head)) + } + Err(_err) => { + // FIXME + Ok(Value::nothing()) + } + } + } + Err(_err) => { + // FIXME + Ok(Value::nothing()) + } + } + } +} diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs index 2b7a3cac0a..a508e0cb47 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -6,10 +6,13 @@ mod default_context; mod do_; mod each; mod for_; +mod git; +mod git_checkout; mod if_; mod length; mod let_; mod let_env; +mod list_git_branches; mod ls; mod table; mod where_; @@ -22,9 +25,12 @@ pub use default_context::create_default_context; pub use do_::Do; pub use each::Each; pub use for_::For; +pub use git::Git; +pub use git_checkout::GitCheckout; pub use if_::If; pub use length::Length; pub use let_::Let; pub use let_env::LetEnv; +pub use list_git_branches::ListGitBranches; pub use ls::Ls; pub use table::Table; diff --git a/crates/nu-command/src/list_git_branches.rs b/crates/nu-command/src/list_git_branches.rs new file mode 100644 index 0000000000..3a0a148925 --- /dev/null +++ b/crates/nu-command/src/list_git_branches.rs @@ -0,0 +1,69 @@ +// Note: this is a temporary command that later will be converted into a pipeline + +use std::process::Command as ProcessCommand; +use std::process::Stdio; + +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, Value}; + +pub struct ListGitBranches; + +//NOTE: this is not a real implementation :D. It's just a simple one to test with until we port the real one. +impl Command for ListGitBranches { + fn name(&self) -> &str { + "list-git-branches" + } + + fn usage(&self) -> &str { + "List the git branches of the current directory." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("list-git-branches") + } + + fn run( + &self, + _context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + let list_branches = ProcessCommand::new("git") + .arg("branch") + .stdout(Stdio::piped()) + .spawn(); + + if let Ok(child) = list_branches { + if let Ok(output) = child.wait_with_output() { + let val = output.stdout; + + let s = String::from_utf8_lossy(&val).to_string(); + + let lines: Vec<_> = s + .lines() + .filter_map(|x| { + if x.starts_with("* ") { + None + } else { + Some(x.trim()) + } + }) + .map(|x| Value::String { + val: x.into(), + span: call.head, + }) + .collect(); + + Ok(Value::List { + vals: lines, + span: call.head, + }) + } else { + Ok(Value::Nothing { span: call.head }) + } + } else { + Ok(Value::Nothing { span: call.head }) + } + } +} diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index f32de5eca9..ba3fde5db3 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -16,6 +16,7 @@ pub enum FlatShape { Signature, String, Variable, + Custom(String), } pub fn flatten_block(working_set: &StateWorkingSet, block: &Block) -> Vec<(Span, FlatShape)> { @@ -40,6 +41,10 @@ pub fn flatten_expression( working_set: &StateWorkingSet, expr: &Expression, ) -> Vec<(Span, FlatShape)> { + if let Some(custom_completion) = &expr.custom_completion { + return vec![(expr.span, FlatShape::Custom(custom_completion.clone()))]; + } + match &expr.expr { Expr::BinaryOp(lhs, op, rhs) => { let mut output = vec![]; diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 1d24bea002..7e66e760ab 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -113,6 +113,7 @@ pub fn parse_external_call( expr: Expr::ExternalCall(name, args), span: span(spans), ty: Type::Unknown, + custom_completion: None, }, None, ) @@ -360,6 +361,7 @@ fn parse_multispan_value( ), span: arg_span, ty: Type::Unknown, + custom_completion: None, }, error, ); @@ -374,6 +376,7 @@ fn parse_multispan_value( expr: Expr::Keyword(keyword.clone(), keyword_span, Box::new(expr)), span: arg_span, ty, + custom_completion: None, }, error, ) @@ -553,12 +556,14 @@ pub fn parse_call( expr: Expr::Call(mut call), span, ty, + custom_completion: None, } => { call.head = orig_span; Expression { expr: Expr::Call(call), span, ty, + custom_completion: None, } } x => x, @@ -596,12 +601,14 @@ pub fn parse_call( expr: Expr::Call(mut call), span, ty, + custom_completion: None, } => { call.head = orig_span; Expression { expr: Expr::Call(call), span, ty, + custom_completion: None, } } x => x, @@ -644,6 +651,7 @@ pub fn parse_call( expr: Expr::Call(call), span: span(spans), ty: Type::Unknown, // FIXME + custom_completion: None, }, err, ) @@ -668,6 +676,7 @@ pub fn parse_int(token: &[u8], span: Span) -> (Expression, Option) { expr: Expr::Int(v), span, ty: Type::Int, + custom_completion: None, }, None, ) @@ -688,6 +697,7 @@ pub fn parse_int(token: &[u8], span: Span) -> (Expression, Option) { expr: Expr::Int(v), span, ty: Type::Int, + custom_completion: None, }, None, ) @@ -708,6 +718,7 @@ pub fn parse_int(token: &[u8], span: Span) -> (Expression, Option) { expr: Expr::Int(v), span, ty: Type::Int, + custom_completion: None, }, None, ) @@ -727,6 +738,7 @@ pub fn parse_int(token: &[u8], span: Span) -> (Expression, Option) { expr: Expr::Int(x), span, ty: Type::Int, + custom_completion: None, }, None, ) @@ -745,6 +757,7 @@ pub fn parse_float(token: &[u8], span: Span) -> (Expression, Option) expr: Expr::Float(x), span, ty: Type::Float, + custom_completion: None, }, None, ) @@ -901,6 +914,7 @@ pub fn parse_range( expr: Expr::Range(from, next, to, range_op), span, ty: Type::Range, + custom_completion: None, }, None, ) @@ -971,6 +985,7 @@ pub fn parse_string_interpolation( expr: Expr::String(String::from_utf8_lossy(str_contents).to_string()), span, ty: Type::String, + custom_completion: None, }); } token_start = b; @@ -1013,6 +1028,7 @@ pub fn parse_string_interpolation( expr: Expr::String(String::from_utf8_lossy(str_contents).to_string()), span, ty: Type::String, + custom_completion: None, }); } } @@ -1044,6 +1060,7 @@ pub fn parse_string_interpolation( })), span, ty: Type::String, + custom_completion: None, }, error, ) @@ -1067,6 +1084,7 @@ pub fn parse_variable_expr( expr: Expr::Bool(true), span, ty: Type::Bool, + custom_completion: None, }, None, ); @@ -1076,6 +1094,7 @@ pub fn parse_variable_expr( expr: Expr::Bool(false), span, ty: Type::Bool, + custom_completion: None, }, None, ); @@ -1090,6 +1109,7 @@ pub fn parse_variable_expr( expr: Expr::Var(id), span, ty: working_set.get_variable(id).clone(), + custom_completion: None, }, None, ) @@ -1159,6 +1179,7 @@ pub fn parse_full_column_path( expr: Expr::Subexpression(block_id), span, ty: Type::Unknown, // FIXME + custom_completion: None, }, true, ) @@ -1175,6 +1196,7 @@ pub fn parse_full_column_path( expr: Expr::Var(var_id), span: Span::unknown(), ty: Type::Unknown, + custom_completion: None, }, false, ) @@ -1241,6 +1263,7 @@ pub fn parse_full_column_path( expr: Expr::FullCellPath(Box::new(FullCellPath { head, tail })), ty: Type::Unknown, span: full_column_span, + custom_completion: None, }, error, ) @@ -1268,6 +1291,7 @@ pub fn parse_string( expr: Expr::String(token), span, ty: Type::String, + custom_completion: None, }, None, ) @@ -1337,6 +1361,7 @@ pub fn parse_var_with_opt_type( expr: Expr::Var(id), span: span(&spans[*spans_idx - 1..*spans_idx + 1]), ty, + custom_completion: None, }, None, ) @@ -1347,6 +1372,7 @@ pub fn parse_var_with_opt_type( expr: Expr::Var(id), span: spans[*spans_idx], ty: Type::Unknown, + custom_completion: None, }, Some(ParseError::MissingType(spans[*spans_idx])), ) @@ -1359,6 +1385,7 @@ pub fn parse_var_with_opt_type( expr: Expr::Var(id), span: span(&spans[*spans_idx..*spans_idx + 1]), ty: Type::Unknown, + custom_completion: None, }, None, ) @@ -1395,6 +1422,7 @@ pub fn parse_row_condition( ty: Type::Bool, span, expr: Expr::RowCondition(var_id, Box::new(expression)), + custom_completion: None, }, err, ) @@ -1435,6 +1463,7 @@ pub fn parse_signature( expr: Expr::Signature(sig), span, ty: Type::Unknown, + custom_completion: None, }, error, ) @@ -1834,6 +1863,7 @@ pub fn parse_list_expression( } else { Type::Unknown })), + custom_completion: None, }, error, ) @@ -1882,6 +1912,7 @@ pub fn parse_table_expression( expr: Expr::List(vec![]), span, ty: Type::List(Box::new(Type::Unknown)), + custom_completion: None, }, None, ), @@ -1947,6 +1978,7 @@ pub fn parse_table_expression( expr: Expr::Table(table_headers, rows), span, ty: Type::Table, + custom_completion: None, }, error, ) @@ -2089,6 +2121,7 @@ pub fn parse_block_expression( expr: Expr::Block(block_id), span, ty: Type::Block, + custom_completion: None, }, error, ) @@ -2142,6 +2175,11 @@ pub fn parse_value( } match shape { + SyntaxShape::Custom(shape, custom_completion) => { + let (mut expression, err) = parse_value(working_set, span, shape); + expression.custom_completion = Some(custom_completion.clone()); + (expression, err) + } SyntaxShape::Number => parse_number(bytes, span), SyntaxShape::Int => parse_int(bytes, span), SyntaxShape::Range => parse_range(working_set, span), @@ -2254,6 +2292,7 @@ pub fn parse_operator( expr: Expr::Operator(operator), span, ty: Type::Unknown, + custom_completion: None, }, None, ) @@ -2333,6 +2372,7 @@ pub fn parse_math_expression( expr: Expr::BinaryOp(Box::new(lhs), Box::new(op), Box::new(rhs)), span: op_span, ty: result_ty, + custom_completion: None, }); } } @@ -2367,6 +2407,7 @@ pub fn parse_math_expression( expr: Expr::BinaryOp(Box::new(lhs), Box::new(op), Box::new(rhs)), span: binary_op_span, ty: result_ty, + custom_completion: None, }); } @@ -2496,6 +2537,7 @@ pub fn parse_def( expr: Expr::Call(call), span: span(spans), ty: Type::Unknown, + custom_completion: None, }])), error, ) @@ -2559,6 +2601,7 @@ pub fn parse_alias( expr: Expr::Call(call), span: call_span, ty: Type::Unknown, + custom_completion: None, }])), None, ); @@ -2607,6 +2650,7 @@ pub fn parse_let( expr: Expr::Call(call), span: call_span, ty: Type::Unknown, + custom_completion: None, }])), err, ); diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index 228bdf50ec..da79d9cc9d 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -6,6 +6,7 @@ pub struct Expression { pub expr: Expr, pub span: Span, pub ty: Type, + pub custom_completion: Option, } impl Expression { @@ -14,6 +15,7 @@ impl Expression { expr: Expr::Garbage, span, ty: Type::Unknown, + custom_completion: None, } } diff --git a/crates/nu-protocol/src/syntax_shape.rs b/crates/nu-protocol/src/syntax_shape.rs index b5b81fe9ab..b0c1b96599 100644 --- a/crates/nu-protocol/src/syntax_shape.rs +++ b/crates/nu-protocol/src/syntax_shape.rs @@ -69,6 +69,9 @@ pub enum SyntaxShape { /// A general expression, eg `1 + 2` or `foo --bar` Expression, + + /// A custom shape with custom completion logic + Custom(Box, String), } impl SyntaxShape { @@ -77,6 +80,7 @@ impl SyntaxShape { SyntaxShape::Any => Type::Unknown, SyntaxShape::Block(_) => Type::Block, SyntaxShape::CellPath => Type::Unknown, + SyntaxShape::Custom(custom, _) => custom.to_type(), SyntaxShape::Duration => Type::Duration, SyntaxShape::Expression => Type::Unknown, SyntaxShape::FilePath => Type::FilePath,