diff --git a/TODO.md b/TODO.md
index 92049f2ac..4ea8afd78 100644
--- a/TODO.md
+++ b/TODO.md
@@ -19,6 +19,8 @@
 - [x] Iteration (`each`) over tables
 - [x] Row conditions
 - [x] Simple completions
+- [ ] Detecting `$it` currently only looks at top scope but should find any free `$it` in the expression (including subexprs)
+- [ ] Signature needs to make parameters visible in scope before block is parsed
 - [ ] Value serialization
 - [ ] Handling rows with missing columns during a cell path
 - [ ] Error shortcircuit (stopping on first error)
diff --git a/crates/nu-cli/src/completions.rs b/crates/nu-cli/src/completions.rs
index 45b9b17bd..2140ca0da 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/errors.rs b/crates/nu-cli/src/errors.rs
index b5470ead1..a7ee99216 100644
--- a/crates/nu-cli/src/errors.rs
+++ b/crates/nu-cli/src/errors.rs
@@ -261,6 +261,14 @@ pub fn report_parsing_error(
                     .with_labels(vec![Label::primary(diag_file_id, diag_range)
                         .with_message("needs a parameter name")])
             }
+            ParseError::AssignmentMismatch(msg, label, span) => {
+                let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
+                Diagnostic::error()
+                    .with_message(msg)
+                    .with_labels(vec![
+                        Label::primary(diag_file_id, diag_range).with_message(label)
+                    ])
+            }
         };
 
     // println!("DIAG");
diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs
index 54804b76f..87ec535ed 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/benchmark.rs b/crates/nu-command/src/benchmark.rs
index 5bd5a6afc..1590e8af8 100644
--- a/crates/nu-command/src/benchmark.rs
+++ b/crates/nu-command/src/benchmark.rs
@@ -17,7 +17,11 @@ impl Command for Benchmark {
     }
 
     fn signature(&self) -> nu_protocol::Signature {
-        Signature::build("benchmark").required("block", SyntaxShape::Block, "the block to run")
+        Signature::build("benchmark").required(
+            "block",
+            SyntaxShape::Block(Some(vec![])),
+            "the block to run",
+        )
     }
 
     fn run(
diff --git a/crates/nu-command/src/def.rs b/crates/nu-command/src/def.rs
index 25004d800..0f6ef1b56 100644
--- a/crates/nu-command/src/def.rs
+++ b/crates/nu-command/src/def.rs
@@ -17,7 +17,11 @@ impl Command for Def {
         Signature::build("def")
             .required("def_name", SyntaxShape::String, "definition name")
             .required("params", SyntaxShape::Signature, "parameters")
-            .required("block", SyntaxShape::Block, "body of the definition")
+            .required(
+                "block",
+                SyntaxShape::Block(Some(vec![])),
+                "body of the definition",
+            )
     }
 
     fn run(
diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs
index f9a61a77b..9291a9742 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<RefCell<EngineState>> {
@@ -48,6 +48,11 @@ pub fn create_default_context() -> Rc<RefCell<EngineState>> {
 
         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/do_.rs b/crates/nu-command/src/do_.rs
index 3630e84ff..20bd0eb7d 100644
--- a/crates/nu-command/src/do_.rs
+++ b/crates/nu-command/src/do_.rs
@@ -15,7 +15,11 @@ impl Command for Do {
     }
 
     fn signature(&self) -> nu_protocol::Signature {
-        Signature::build("do").required("block", SyntaxShape::Block, "the block to run")
+        Signature::build("do").required(
+            "block",
+            SyntaxShape::Block(Some(vec![])),
+            "the block to run",
+        )
     }
 
     fn run(
diff --git a/crates/nu-command/src/each.rs b/crates/nu-command/src/each.rs
index cd2894e9b..e48cfb719 100644
--- a/crates/nu-command/src/each.rs
+++ b/crates/nu-command/src/each.rs
@@ -15,7 +15,13 @@ impl Command for Each {
     }
 
     fn signature(&self) -> nu_protocol::Signature {
-        Signature::build("each").required("block", SyntaxShape::Block, "the block to run")
+        Signature::build("each")
+            .required(
+                "block",
+                SyntaxShape::Block(Some(vec![SyntaxShape::Any])),
+                "the block to run",
+            )
+            .switch("numbered", "iterate with an index", Some('n'))
     }
 
     fn run(
@@ -27,20 +33,42 @@ impl Command for Each {
         let block_id = call.positional[0]
             .as_block()
             .expect("internal error: expected block");
+
+        let numbered = call.has_flag("numbered");
         let context = context.clone();
+        let span = call.head;
 
         match input {
             Value::Range { val, .. } => Ok(Value::Stream {
                 stream: val
                     .into_iter()
-                    .map(move |x| {
+                    .enumerate()
+                    .map(move |(idx, x)| {
                         let engine_state = context.engine_state.borrow();
                         let block = engine_state.get_block(block_id);
 
                         let state = context.enter_scope();
+
                         if let Some(var) = block.signature.get_positional(0) {
                             if let Some(var_id) = &var.var_id {
-                                state.add_var(*var_id, x);
+                                if numbered {
+                                    state.add_var(
+                                        *var_id,
+                                        Value::Record {
+                                            cols: vec!["index".into(), "item".into()],
+                                            vals: vec![
+                                                Value::Int {
+                                                    val: idx as i64,
+                                                    span,
+                                                },
+                                                x,
+                                            ],
+                                            span,
+                                        },
+                                    );
+                                } else {
+                                    state.add_var(*var_id, x);
+                                }
                             }
                         }
 
@@ -55,14 +83,32 @@ impl Command for Each {
             Value::List { vals: val, .. } => Ok(Value::Stream {
                 stream: val
                     .into_iter()
-                    .map(move |x| {
+                    .enumerate()
+                    .map(move |(idx, x)| {
                         let engine_state = context.engine_state.borrow();
                         let block = engine_state.get_block(block_id);
 
                         let state = context.enter_scope();
                         if let Some(var) = block.signature.get_positional(0) {
                             if let Some(var_id) = &var.var_id {
-                                state.add_var(*var_id, x);
+                                if numbered {
+                                    state.add_var(
+                                        *var_id,
+                                        Value::Record {
+                                            cols: vec!["index".into(), "item".into()],
+                                            vals: vec![
+                                                Value::Int {
+                                                    val: idx as i64,
+                                                    span,
+                                                },
+                                                x,
+                                            ],
+                                            span,
+                                        },
+                                    );
+                                } else {
+                                    state.add_var(*var_id, x);
+                                }
                             }
                         }
 
@@ -76,14 +122,32 @@ impl Command for Each {
             }),
             Value::Stream { stream, .. } => Ok(Value::Stream {
                 stream: stream
-                    .map(move |x| {
+                    .enumerate()
+                    .map(move |(idx, x)| {
                         let engine_state = context.engine_state.borrow();
                         let block = engine_state.get_block(block_id);
 
                         let state = context.enter_scope();
                         if let Some(var) = block.signature.get_positional(0) {
                             if let Some(var_id) = &var.var_id {
-                                state.add_var(*var_id, x);
+                                if numbered {
+                                    state.add_var(
+                                        *var_id,
+                                        Value::Record {
+                                            cols: vec!["index".into(), "item".into()],
+                                            vals: vec![
+                                                Value::Int {
+                                                    val: idx as i64,
+                                                    span,
+                                                },
+                                                x,
+                                            ],
+                                            span,
+                                        },
+                                    );
+                                } else {
+                                    state.add_var(*var_id, x);
+                                }
                             }
                         }
 
diff --git a/crates/nu-command/src/for_.rs b/crates/nu-command/src/for_.rs
index 35d806dd5..158cd6b6c 100644
--- a/crates/nu-command/src/for_.rs
+++ b/crates/nu-command/src/for_.rs
@@ -29,7 +29,11 @@ impl Command for For {
                 ),
                 "range of the loop",
             )
-            .required("block", SyntaxShape::Block, "the block to run")
+            .required(
+                "block",
+                SyntaxShape::Block(Some(vec![])),
+                "the block to run",
+            )
     }
 
     fn run(
diff --git a/crates/nu-command/src/git.rs b/crates/nu-command/src/git.rs
new file mode 100644
index 000000000..5fe8521f3
--- /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<nu_protocol::Value, nu_protocol::ShellError> {
+        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 000000000..143fff96b
--- /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<nu_protocol::Value, nu_protocol::ShellError> {
+        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/if_.rs b/crates/nu-command/src/if_.rs
index 2b8df4867..a9b1489a6 100644
--- a/crates/nu-command/src/if_.rs
+++ b/crates/nu-command/src/if_.rs
@@ -17,7 +17,7 @@ impl Command for If {
     fn signature(&self) -> nu_protocol::Signature {
         Signature::build("if")
             .required("cond", SyntaxShape::Expression, "condition")
-            .required("then_block", SyntaxShape::Block, "then block")
+            .required("then_block", SyntaxShape::Block(Some(vec![])), "then block")
             .optional(
                 "else",
                 SyntaxShape::Keyword(b"else".to_vec(), Box::new(SyntaxShape::Expression)),
diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs
index 2b7a3cac0..a508e0cb4 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 000000000..3a0a14892
--- /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<nu_protocol::Value, nu_protocol::ShellError> {
+        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-engine/src/eval.rs b/crates/nu-engine/src/eval.rs
index e7d40894f..143379a75 100644
--- a/crates/nu-engine/src/eval.rs
+++ b/crates/nu-engine/src/eval.rs
@@ -114,13 +114,19 @@ pub fn eval_expression(
             val: *f,
             span: expr.span,
         }),
-        Expr::Range(from, to, operator) => {
-            // TODO: Embed the min/max into Range and set max to be the true max
+        Expr::Range(from, next, to, operator) => {
             let from = if let Some(f) = from {
                 eval_expression(context, f)?
             } else {
-                Value::Int {
-                    val: 0i64,
+                Value::Nothing {
+                    span: Span::unknown(),
+                }
+            };
+
+            let next = if let Some(s) = next {
+                eval_expression(context, s)?
+            } else {
+                Value::Nothing {
                     span: Span::unknown(),
                 }
             };
@@ -128,31 +134,13 @@ pub fn eval_expression(
             let to = if let Some(t) = to {
                 eval_expression(context, t)?
             } else {
-                Value::Int {
-                    val: 100i64,
+                Value::Nothing {
                     span: Span::unknown(),
                 }
             };
 
-            let range = match (&from, &to) {
-                (&Value::Int { .. }, &Value::Int { .. }) => Range {
-                    from: from.clone(),
-                    to: to.clone(),
-                    inclusion: operator.inclusion,
-                },
-                (lhs, rhs) => {
-                    return Err(ShellError::OperatorMismatch {
-                        op_span: operator.span,
-                        lhs_ty: lhs.get_type(),
-                        lhs_span: lhs.span(),
-                        rhs_ty: rhs.get_type(),
-                        rhs_span: rhs.span(),
-                    })
-                }
-            };
-
             Ok(Value::Range {
-                val: Box::new(range),
+                val: Box::new(Range::new(expr.span, from, next, to, operator)?),
                 span: expr.span,
             })
         }
@@ -190,7 +178,6 @@ pub fn eval_expression(
                 x => Err(ShellError::UnsupportedOperator(x, op_span)),
             }
         }
-
         Expr::Subexpression(block_id) => {
             let engine_state = context.engine_state.borrow();
             let block = engine_state.get_block(*block_id);
diff --git a/crates/nu-parser/src/errors.rs b/crates/nu-parser/src/errors.rs
index f4c486e0f..2965d5f14 100644
--- a/crates/nu-parser/src/errors.rs
+++ b/crates/nu-parser/src/errors.rs
@@ -30,4 +30,5 @@ pub enum ParseError {
     RestNeedsName(Span),
     ExtraColumns(usize, Span),
     MissingColumns(usize, Span),
+    AssignmentMismatch(String, String, Span),
 }
diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs
index 17bc3178c..ba3fde5db 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![];
@@ -85,15 +90,19 @@ pub fn flatten_expression(
             }
             output
         }
-        Expr::Range(from, to, op) => {
+        Expr::Range(from, next, to, op) => {
             let mut output = vec![];
             if let Some(f) = from {
                 output.extend(flatten_expression(working_set, f));
             }
+            if let Some(s) = next {
+                output.extend(vec![(op.next_op_span, FlatShape::Operator)]);
+                output.extend(flatten_expression(working_set, s));
+            }
+            output.extend(vec![(op.span, FlatShape::Operator)]);
             if let Some(t) = to {
                 output.extend(flatten_expression(working_set, t));
             }
-            output.extend(vec![(op.span, FlatShape::Operator)]);
             output
         }
         Expr::Bool(_) => {
diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs
index 5c03e08dd..7e66e760a 100644
--- a/crates/nu-parser/src/parser.rs
+++ b/crates/nu-parser/src/parser.rs
@@ -63,16 +63,35 @@ fn check_call(command: Span, sig: &Signature, call: &Call) -> Option<ParseError>
     }
 }
 
-fn check_name(working_set: &mut StateWorkingSet, spans: &[Span]) -> Option<ParseError> {
-    if spans[1..].len() < 2 {
-        Some(ParseError::UnknownState(
-            "missing definition name".into(),
-            span(spans),
-        ))
+fn check_name<'a>(
+    working_set: &mut StateWorkingSet,
+    spans: &'a [Span],
+) -> Option<(&'a Span, ParseError)> {
+    if spans.len() == 1 {
+        None
+    } else if spans.len() < 4 {
+        if working_set.get_span_contents(spans[1]) == b"=" {
+            let name = String::from_utf8_lossy(working_set.get_span_contents(spans[0]));
+            Some((
+                &spans[1],
+                ParseError::AssignmentMismatch(
+                    format!("{} missing name", name),
+                    "missing name".into(),
+                    spans[1],
+                ),
+            ))
+        } else {
+            None
+        }
     } else if working_set.get_span_contents(spans[2]) != b"=" {
-        Some(ParseError::UnknownState(
-            "missing equal sign in definition".into(),
-            span(spans),
+        let name = String::from_utf8_lossy(working_set.get_span_contents(spans[0]));
+        Some((
+            &spans[2],
+            ParseError::AssignmentMismatch(
+                format!("{} missing sign", name),
+                "missing equal sign".into(),
+                spans[2],
+            ),
         ))
     } else {
         None
@@ -94,6 +113,7 @@ pub fn parse_external_call(
             expr: Expr::ExternalCall(name, args),
             span: span(spans),
             ty: Type::Unknown,
+            custom_completion: None,
         },
         None,
     )
@@ -341,6 +361,7 @@ fn parse_multispan_value(
                         ),
                         span: arg_span,
                         ty: Type::Unknown,
+                        custom_completion: None,
                     },
                     error,
                 );
@@ -355,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,
             )
@@ -534,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,
@@ -577,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,
@@ -610,7 +636,7 @@ pub fn parse_call(
                 return (
                     garbage(Span::new(0, 0)),
                     Some(ParseError::UnknownState(
-                        "internal error: incomplete statement".into(),
+                        "Incomplete statement".into(),
                         span(spans),
                     )),
                 );
@@ -625,10 +651,19 @@ pub fn parse_call(
                 expr: Expr::Call(call),
                 span: span(spans),
                 ty: Type::Unknown, // FIXME
+                custom_completion: None,
             },
             err,
         )
     } else {
+        // We might be parsing left-unbounded range ("..10")
+        let bytes = working_set.get_span_contents(spans[0]);
+        if let (Some(b'.'), Some(b'.')) = (bytes.get(0), bytes.get(1)) {
+            let (range_expr, range_err) = parse_range(working_set, spans[0]);
+            if range_err.is_none() {
+                return (range_expr, range_err);
+            }
+        }
         parse_external_call(working_set, spans)
     }
 }
@@ -641,6 +676,7 @@ pub fn parse_int(token: &[u8], span: Span) -> (Expression, Option<ParseError>) {
                     expr: Expr::Int(v),
                     span,
                     ty: Type::Int,
+                    custom_completion: None,
                 },
                 None,
             )
@@ -661,6 +697,7 @@ pub fn parse_int(token: &[u8], span: Span) -> (Expression, Option<ParseError>) {
                     expr: Expr::Int(v),
                     span,
                     ty: Type::Int,
+                    custom_completion: None,
                 },
                 None,
             )
@@ -681,6 +718,7 @@ pub fn parse_int(token: &[u8], span: Span) -> (Expression, Option<ParseError>) {
                     expr: Expr::Int(v),
                     span,
                     ty: Type::Int,
+                    custom_completion: None,
                 },
                 None,
             )
@@ -700,6 +738,7 @@ pub fn parse_int(token: &[u8], span: Span) -> (Expression, Option<ParseError>) {
                 expr: Expr::Int(x),
                 span,
                 ty: Type::Int,
+                custom_completion: None,
             },
             None,
         )
@@ -718,6 +757,7 @@ pub fn parse_float(token: &[u8], span: Span) -> (Expression, Option<ParseError>)
                 expr: Expr::Float(x),
                 span,
                 ty: Type::Float,
+                custom_completion: None,
             },
             None,
         )
@@ -746,8 +786,8 @@ pub fn parse_range(
     working_set: &mut StateWorkingSet,
     span: Span,
 ) -> (Expression, Option<ParseError>) {
-    // Range follows the following syntax: [<from>][<step_operator><step>]<range_operator>[<to>]
-    //   where <step_operator> is ".."
+    // Range follows the following syntax: [<from>][<next_operator><next>]<range_operator>[<to>]
+    //   where <next_operator> is ".."
     //   and  <range_operator> is ".." or "..<"
     //   and one of the <from> or <to> bounds must be present (just '..' is not allowed since it
     //     looks like parent directory)
@@ -762,42 +802,28 @@ pub fn parse_range(
     // First, figure out what exact operators are used and determine their positions
     let dotdot_pos: Vec<_> = token.match_indices("..").map(|(pos, _)| pos).collect();
 
-    let (step_op_pos, range_op_pos) =
+    let (next_op_pos, range_op_pos) =
         match dotdot_pos.len() {
             1 => (None, dotdot_pos[0]),
             2 => (Some(dotdot_pos[0]), dotdot_pos[1]),
             _ => return (
                 garbage(span),
                 Some(ParseError::Expected(
-                    "one range operator ('..' or '..<') and optionally one step operator ('..')"
+                    "one range operator ('..' or '..<') and optionally one next operator ('..')"
                         .into(),
                     span,
                 )),
             ),
         };
 
-    let _step_op_span = step_op_pos.map(|pos| {
-        Span::new(
-            span.start + pos,
-            span.start + pos + "..".len(), // Only ".." is allowed for step operator
-        )
-    });
-
-    let (range_op, range_op_str, range_op_span) = if let Some(pos) = token.find("..<") {
+    let (inclusion, range_op_str, range_op_span) = if let Some(pos) = token.find("..<") {
         if pos == range_op_pos {
             let op_str = "..<";
             let op_span = Span::new(
                 span.start + range_op_pos,
                 span.start + range_op_pos + op_str.len(),
             );
-            (
-                RangeOperator {
-                    inclusion: RangeInclusion::RightExclusive,
-                    span: op_span,
-                },
-                "..<",
-                op_span,
-            )
+            (RangeInclusion::RightExclusive, "..<", op_span)
         } else {
             return (
                 garbage(span),
@@ -813,21 +839,14 @@ pub fn parse_range(
             span.start + range_op_pos,
             span.start + range_op_pos + op_str.len(),
         );
-        (
-            RangeOperator {
-                inclusion: RangeInclusion::Inclusive,
-                span: op_span,
-            },
-            "..",
-            op_span,
-        )
+        (RangeInclusion::Inclusive, "..", op_span)
     };
 
-    // Now, based on the operator positions, figure out where the bounds & step are located and
+    // Now, based on the operator positions, figure out where the bounds & next are located and
     // parse them
-    // TODO: Actually parse the step number
+    // TODO: Actually parse the next number
     let from = if token.starts_with("..") {
-        // token starts with either step operator, or range operator -- we don't care which one
+        // token starts with either next operator, or range operator -- we don't care which one
         None
     } else {
         let from_span = Span::new(span.start, span.start + dotdot_pos[0]);
@@ -867,11 +886,35 @@ pub fn parse_range(
         );
     }
 
+    let (next, next_op_span) = if let Some(pos) = next_op_pos {
+        let next_op_span = Span::new(span.start + pos, span.start + pos + "..".len());
+        let next_span = Span::new(next_op_span.end, range_op_span.start);
+
+        match parse_value(working_set, next_span, &SyntaxShape::Number) {
+            (expression, None) => (Some(Box::new(expression)), next_op_span),
+            _ => {
+                return (
+                    garbage(span),
+                    Some(ParseError::Expected("number".into(), span)),
+                )
+            }
+        }
+    } else {
+        (None, Span::unknown())
+    };
+
+    let range_op = RangeOperator {
+        inclusion,
+        span: range_op_span,
+        next_op_span,
+    };
+
     (
         Expression {
-            expr: Expr::Range(from, to, range_op),
+            expr: Expr::Range(from, next, to, range_op),
             span,
             ty: Type::Range,
+            custom_completion: None,
         },
         None,
     )
@@ -942,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;
@@ -984,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,
                 });
             }
         }
@@ -1015,6 +1060,7 @@ pub fn parse_string_interpolation(
                 })),
                 span,
                 ty: Type::String,
+                custom_completion: None,
             },
             error,
         )
@@ -1038,6 +1084,7 @@ pub fn parse_variable_expr(
                 expr: Expr::Bool(true),
                 span,
                 ty: Type::Bool,
+                custom_completion: None,
             },
             None,
         );
@@ -1047,6 +1094,7 @@ pub fn parse_variable_expr(
                 expr: Expr::Bool(false),
                 span,
                 ty: Type::Bool,
+                custom_completion: None,
             },
             None,
         );
@@ -1061,21 +1109,12 @@ pub fn parse_variable_expr(
                     expr: Expr::Var(id),
                     span,
                     ty: working_set.get_variable(id).clone(),
+                    custom_completion: None,
                 },
                 None,
             )
         } else {
-            let name = working_set.get_span_contents(span).to_vec();
-            // this seems okay to set it to unknown here, but we should double-check
-            let id = working_set.add_variable(name, Type::Unknown);
-            (
-                Expression {
-                    expr: Expr::Var(id),
-                    span,
-                    ty: Type::Unknown,
-                },
-                None,
-            )
+            (garbage(span), Some(ParseError::VariableNotFound(span)))
         }
     } else {
         (garbage(span), err)
@@ -1140,6 +1179,7 @@ pub fn parse_full_column_path(
                     expr: Expr::Subexpression(block_id),
                     span,
                     ty: Type::Unknown, // FIXME
+                    custom_completion: None,
                 },
                 true,
             )
@@ -1156,6 +1196,7 @@ pub fn parse_full_column_path(
                     expr: Expr::Var(var_id),
                     span: Span::unknown(),
                     ty: Type::Unknown,
+                    custom_completion: None,
                 },
                 false,
             )
@@ -1222,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,
         )
@@ -1249,6 +1291,7 @@ pub fn parse_string(
                 expr: Expr::String(token),
                 span,
                 ty: Type::String,
+                custom_completion: None,
             },
             None,
         )
@@ -1275,7 +1318,7 @@ pub fn parse_shape_name(
         b"int" => SyntaxShape::Int,
         b"path" => SyntaxShape::FilePath,
         b"glob" => SyntaxShape::GlobPattern,
-        b"block" => SyntaxShape::Block,
+        b"block" => SyntaxShape::Block(None), //FIXME
         b"cond" => SyntaxShape::RowCondition,
         b"operator" => SyntaxShape::Operator,
         b"math" => SyntaxShape::MathExpression,
@@ -1318,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,
             )
@@ -1328,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])),
             )
@@ -1340,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,
         )
@@ -1376,6 +1422,7 @@ pub fn parse_row_condition(
             ty: Type::Bool,
             span,
             expr: Expr::RowCondition(var_id, Box::new(expression)),
+            custom_completion: None,
         },
         err,
     )
@@ -1416,6 +1463,7 @@ pub fn parse_signature(
             expr: Expr::Signature(sig),
             span,
             ty: Type::Unknown,
+            custom_completion: None,
         },
         error,
     )
@@ -1815,6 +1863,7 @@ pub fn parse_list_expression(
             } else {
                 Type::Unknown
             })),
+            custom_completion: None,
         },
         error,
     )
@@ -1863,6 +1912,7 @@ pub fn parse_table_expression(
                 expr: Expr::List(vec![]),
                 span,
                 ty: Type::List(Box::new(Type::Unknown)),
+                custom_completion: None,
             },
             None,
         ),
@@ -1928,6 +1978,7 @@ pub fn parse_table_expression(
                     expr: Expr::Table(table_headers, rows),
                     span,
                     ty: Type::Table,
+                    custom_completion: None,
                 },
                 error,
             )
@@ -1937,6 +1988,7 @@ pub fn parse_table_expression(
 
 pub fn parse_block_expression(
     working_set: &mut StateWorkingSet,
+    shape: &SyntaxShape,
     span: Span,
 ) -> (Expression, Option<ParseError>) {
     let bytes = working_set.get_span_contents(span);
@@ -1977,7 +2029,7 @@ pub fn parse_block_expression(
     working_set.enter_scope();
 
     // Check to see if we have parameters
-    let (signature, amt_to_skip): (Option<Box<Signature>>, usize) = match output.first() {
+    let (mut signature, amt_to_skip): (Option<Box<Signature>>, usize) = match output.first() {
         Some(Token {
             contents: TokenContents::Pipe,
             span,
@@ -2023,12 +2075,31 @@ pub fn parse_block_expression(
     let (output, err) = lite_parse(&output[amt_to_skip..]);
     error = error.or(err);
 
+    if let SyntaxShape::Block(Some(v)) = shape {
+        if signature.is_none() && v.len() == 1 {
+            // We'll assume there's an `$it` present
+            let var_id = working_set.add_variable(b"$it".to_vec(), Type::Unknown);
+
+            let mut new_sigature = Signature::new("");
+            new_sigature.required_positional.push(PositionalArg {
+                var_id: Some(var_id),
+                name: "$it".into(),
+                desc: String::new(),
+                shape: SyntaxShape::Any,
+            });
+
+            signature = Some(Box::new(new_sigature));
+        }
+    }
+
     let (mut output, err) = parse_block(working_set, &output, false);
     error = error.or(err);
 
     if let Some(signature) = signature {
         output.signature = signature;
     } else if let Some(last) = working_set.delta.scope.last() {
+        // FIXME: this only supports the top $it. Instead, we should look for a free $it in the expression.
+
         if let Some(var_id) = last.get_var(b"$it") {
             let mut signature = Signature::new("");
             signature.required_positional.push(PositionalArg {
@@ -2050,6 +2121,7 @@ pub fn parse_block_expression(
             expr: Expr::Block(block_id),
             span,
             ty: Type::Block,
+            custom_completion: None,
         },
         error,
     )
@@ -2079,8 +2151,8 @@ pub fn parse_value(
             return parse_full_column_path(working_set, None, span);
         }
     } else if bytes.starts_with(b"{") {
-        if matches!(shape, SyntaxShape::Block) || matches!(shape, SyntaxShape::Any) {
-            return parse_block_expression(working_set, span);
+        if matches!(shape, SyntaxShape::Block(_)) || matches!(shape, SyntaxShape::Any) {
+            return parse_block_expression(working_set, shape, span);
         } else {
             return (
                 Expression::garbage(span),
@@ -2103,15 +2175,20 @@ 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),
         SyntaxShape::String | SyntaxShape::GlobPattern | SyntaxShape::FilePath => {
             parse_string(working_set, span)
         }
-        SyntaxShape::Block => {
+        SyntaxShape::Block(_) => {
             if bytes.starts_with(b"{") {
-                parse_block_expression(working_set, span)
+                parse_block_expression(working_set, shape, span)
             } else {
                 (
                     Expression::garbage(span),
@@ -2159,7 +2236,7 @@ pub fn parse_value(
                     SyntaxShape::Range,
                     SyntaxShape::Filesize,
                     SyntaxShape::Duration,
-                    SyntaxShape::Block,
+                    SyntaxShape::Block(None),
                     SyntaxShape::String,
                 ];
                 for shape in shapes.iter() {
@@ -2215,6 +2292,7 @@ pub fn parse_operator(
             expr: Expr::Operator(operator),
             span,
             ty: Type::Unknown,
+            custom_completion: None,
         },
         None,
     )
@@ -2294,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,
                 });
             }
         }
@@ -2328,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,
         });
     }
 
@@ -2411,7 +2491,8 @@ pub fn parse_def(
         let (sig, err) = parse_signature(working_set, spans[2]);
         error = error.or(err);
 
-        let (block, err) = parse_block_expression(working_set, spans[3]);
+        let (block, err) =
+            parse_block_expression(working_set, &SyntaxShape::Block(Some(vec![])), spans[3]);
         error = error.or(err);
         working_set.exit_scope();
 
@@ -2456,6 +2537,7 @@ pub fn parse_def(
                         expr: Expr::Call(call),
                         span: span(spans),
                         ty: Type::Unknown,
+                        custom_completion: None,
                     }])),
                     error,
                 )
@@ -2469,7 +2551,7 @@ pub fn parse_def(
         (
             garbage_statement(spans),
             Some(ParseError::UnknownState(
-                "definition unparseable. Expected structure: def <name> [] {}".into(),
+                "Expected structure: def <name> [] {}".into(),
                 span(spans),
             )),
         )
@@ -2483,9 +2565,9 @@ pub fn parse_alias(
     let name = working_set.get_span_contents(spans[0]);
 
     if name == b"alias" {
-        if let Some(err) = check_name(working_set, spans) {
+        if let Some((span, err)) = check_name(working_set, spans) {
             return (
-                Statement::Pipeline(Pipeline::from_vec(vec![garbage(span(spans))])),
+                Statement::Pipeline(Pipeline::from_vec(vec![garbage(*span)])),
                 Some(err),
             );
         }
@@ -2519,6 +2601,7 @@ pub fn parse_alias(
                     expr: Expr::Call(call),
                     span: call_span,
                     ty: Type::Unknown,
+                    custom_completion: None,
                 }])),
                 None,
             );
@@ -2541,9 +2624,9 @@ pub fn parse_let(
     let name = working_set.get_span_contents(spans[0]);
 
     if name == b"let" {
-        if let Some(err) = check_name(working_set, spans) {
+        if let Some((span, err)) = check_name(working_set, spans) {
             return (
-                Statement::Pipeline(Pipeline::from_vec(vec![garbage(span(spans))])),
+                Statement::Pipeline(Pipeline::from_vec(vec![garbage(*span)])),
                 Some(err),
             );
         }
@@ -2567,6 +2650,7 @@ pub fn parse_let(
                     expr: Expr::Call(call),
                     span: call_span,
                     ty: Type::Unknown,
+                    custom_completion: None,
                 }])),
                 err,
             );
@@ -2585,16 +2669,16 @@ pub fn parse_statement(
     working_set: &mut StateWorkingSet,
     spans: &[Span],
 ) -> (Statement, Option<ParseError>) {
-    // FIXME: improve errors by checking keyword first
-    if let (decl, None) = parse_def(working_set, spans) {
-        (decl, None)
-    } else if let (stmt, None) = parse_let(working_set, spans) {
-        (stmt, None)
-    } else if let (stmt, None) = parse_alias(working_set, spans) {
-        (stmt, None)
-    } else {
-        let (expr, err) = parse_expression(working_set, spans);
-        (Statement::Pipeline(Pipeline::from_vec(vec![expr])), err)
+    let name = working_set.get_span_contents(spans[0]);
+
+    match name {
+        b"def" => parse_def(working_set, spans),
+        b"let" => parse_let(working_set, spans),
+        b"alias" => parse_alias(working_set, spans),
+        _ => {
+            let (expr, err) = parse_expression(working_set, spans);
+            (Statement::Pipeline(Pipeline::from_vec(vec![expr])), err)
+        }
     }
 }
 
diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs
index 67a082c67..58167b47f 100644
--- a/crates/nu-parser/src/type_check.rs
+++ b/crates/nu-parser/src/type_check.rs
@@ -20,6 +20,7 @@ pub fn math_result_type(
     op: &mut Expression,
     rhs: &mut Expression,
 ) -> (Type, Option<ParseError>) {
+    //println!("checking: {:?} {:?} {:?}", lhs, op, rhs);
     match &op.expr {
         Expr::Operator(operator) => match operator {
             Operator::Plus => match (&lhs.ty, &rhs.ty) {
diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs
index ef52f1fbc..26393f37e 100644
--- a/crates/nu-parser/tests/test_parser.rs
+++ b/crates/nu-parser/tests/test_parser.rs
@@ -2,10 +2,43 @@ use nu_parser::ParseError;
 use nu_parser::*;
 use nu_protocol::{
     ast::{Expr, Expression, Pipeline, Statement},
-    engine::{EngineState, StateWorkingSet},
+    engine::{Command, EngineState, StateWorkingSet},
     Signature, SyntaxShape,
 };
 
+#[cfg(test)]
+pub struct Let;
+
+#[cfg(test)]
+impl Command for Let {
+    fn name(&self) -> &str {
+        "let"
+    }
+
+    fn usage(&self) -> &str {
+        "Create a variable and give it a value."
+    }
+
+    fn signature(&self) -> nu_protocol::Signature {
+        Signature::build("let")
+            .required("var_name", SyntaxShape::VarWithOptType, "variable name")
+            .required(
+                "initial_value",
+                SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
+                "equals sign followed by value",
+            )
+    }
+
+    fn run(
+        &self,
+        _context: &nu_protocol::engine::EvaluationContext,
+        _call: &nu_protocol::ast::Call,
+        _input: nu_protocol::Value,
+    ) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
+        todo!()
+    }
+}
+
 #[test]
 pub fn parse_int() {
     let engine_state = EngineState::new();
@@ -164,6 +197,7 @@ mod range {
                     Expression {
                         expr: Expr::Range(
                             Some(_),
+                            None,
                             Some(_),
                             RangeOperator {
                                 inclusion: RangeInclusion::Inclusive,
@@ -195,6 +229,7 @@ mod range {
                     Expression {
                         expr: Expr::Range(
                             Some(_),
+                            None,
                             Some(_),
                             RangeOperator {
                                 inclusion: RangeInclusion::RightExclusive,
@@ -209,6 +244,38 @@ mod range {
         }
     }
 
+    #[test]
+    fn parse_reverse_range() {
+        let engine_state = EngineState::new();
+        let mut working_set = StateWorkingSet::new(&engine_state);
+
+        let (block, err) = parse(&mut working_set, None, b"10..0", true);
+
+        assert!(err.is_none());
+        assert!(block.len() == 1);
+        match &block[0] {
+            Statement::Pipeline(Pipeline { expressions }) => {
+                assert!(expressions.len() == 1);
+                assert!(matches!(
+                    expressions[0],
+                    Expression {
+                        expr: Expr::Range(
+                            Some(_),
+                            None,
+                            Some(_),
+                            RangeOperator {
+                                inclusion: RangeInclusion::Inclusive,
+                                ..
+                            }
+                        ),
+                        ..
+                    }
+                ))
+            }
+            _ => panic!("No match"),
+        }
+    }
+
     #[test]
     fn parse_subexpression_range() {
         let engine_state = EngineState::new();
@@ -226,6 +293,7 @@ mod range {
                     Expression {
                         expr: Expr::Range(
                             Some(_),
+                            None,
                             Some(_),
                             RangeOperator {
                                 inclusion: RangeInclusion::RightExclusive,
@@ -245,6 +313,8 @@ mod range {
         let engine_state = EngineState::new();
         let mut working_set = StateWorkingSet::new(&engine_state);
 
+        working_set.add_decl(Box::new(Let));
+
         let (block, err) = parse(&mut working_set, None, b"let a = 2; $a..10", true);
 
         assert!(err.is_none());
@@ -257,6 +327,7 @@ mod range {
                     Expression {
                         expr: Expr::Range(
                             Some(_),
+                            None,
                             Some(_),
                             RangeOperator {
                                 inclusion: RangeInclusion::Inclusive,
@@ -276,6 +347,8 @@ mod range {
         let engine_state = EngineState::new();
         let mut working_set = StateWorkingSet::new(&engine_state);
 
+        working_set.add_decl(Box::new(Let));
+
         let (block, err) = parse(&mut working_set, None, b"let a = 2; $a..<($a + 10)", true);
 
         assert!(err.is_none());
@@ -288,6 +361,7 @@ mod range {
                     Expression {
                         expr: Expr::Range(
                             Some(_),
+                            None,
                             Some(_),
                             RangeOperator {
                                 inclusion: RangeInclusion::RightExclusive,
@@ -320,6 +394,39 @@ mod range {
                         expr: Expr::Range(
                             Some(_),
                             None,
+                            None,
+                            RangeOperator {
+                                inclusion: RangeInclusion::Inclusive,
+                                ..
+                            }
+                        ),
+                        ..
+                    }
+                ))
+            }
+            _ => panic!("No match"),
+        }
+    }
+
+    #[test]
+    fn parse_left_unbounded_range() {
+        let engine_state = EngineState::new();
+        let mut working_set = StateWorkingSet::new(&engine_state);
+
+        let (block, err) = parse(&mut working_set, None, b"..10", true);
+
+        assert!(err.is_none());
+        assert!(block.len() == 1);
+        match &block[0] {
+            Statement::Pipeline(Pipeline { expressions }) => {
+                assert!(expressions.len() == 1);
+                assert!(matches!(
+                    expressions[0],
+                    Expression {
+                        expr: Expr::Range(
+                            None,
+                            None,
+                            Some(_),
                             RangeOperator {
                                 inclusion: RangeInclusion::Inclusive,
                                 ..
@@ -349,6 +456,39 @@ mod range {
                     expressions[0],
                     Expression {
                         expr: Expr::Range(
+                            Some(_),
+                            None,
+                            Some(_),
+                            RangeOperator {
+                                inclusion: RangeInclusion::Inclusive,
+                                ..
+                            }
+                        ),
+                        ..
+                    }
+                ))
+            }
+            _ => panic!("No match"),
+        }
+    }
+
+    #[test]
+    fn parse_float_range() {
+        let engine_state = EngineState::new();
+        let mut working_set = StateWorkingSet::new(&engine_state);
+
+        let (block, err) = parse(&mut working_set, None, b"2.0..4.0..10.0", true);
+
+        assert!(err.is_none());
+        assert!(block.len() == 1);
+        match &block[0] {
+            Statement::Pipeline(Pipeline { expressions }) => {
+                assert!(expressions.len() == 1);
+                assert!(matches!(
+                    expressions[0],
+                    Expression {
+                        expr: Expr::Range(
+                            Some(_),
                             Some(_),
                             Some(_),
                             RangeOperator {
diff --git a/crates/nu-protocol/src/ast/call.rs b/crates/nu-protocol/src/ast/call.rs
index efe5d7b48..7ee6d6c16 100644
--- a/crates/nu-protocol/src/ast/call.rs
+++ b/crates/nu-protocol/src/ast/call.rs
@@ -25,4 +25,14 @@ impl Call {
             named: vec![],
         }
     }
+
+    pub fn has_flag(&self, flag_name: &str) -> bool {
+        for name in &self.named {
+            if flag_name == name.0 {
+                return true;
+            }
+        }
+
+        false
+    }
 }
diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs
index f26673f2a..62d7a25c8 100644
--- a/crates/nu-protocol/src/ast/expr.rs
+++ b/crates/nu-protocol/src/ast/expr.rs
@@ -7,8 +7,9 @@ pub enum Expr {
     Int(i64),
     Float(f64),
     Range(
-        Option<Box<Expression>>,
-        Option<Box<Expression>>,
+        Option<Box<Expression>>, // from
+        Option<Box<Expression>>, // next value after "from"
+        Option<Box<Expression>>, // to
         RangeOperator,
     ),
     Var(VarId),
diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs
index 228bdf50e..da79d9cc9 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<String>,
 }
 
 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/ast/operator.rs b/crates/nu-protocol/src/ast/operator.rs
index c7c82eba4..edd4f56fc 100644
--- a/crates/nu-protocol/src/ast/operator.rs
+++ b/crates/nu-protocol/src/ast/operator.rs
@@ -59,6 +59,7 @@ pub enum RangeInclusion {
 pub struct RangeOperator {
     pub inclusion: RangeInclusion,
     pub span: Span,
+    pub next_op_span: Span,
 }
 
 impl Display for RangeOperator {
diff --git a/crates/nu-protocol/src/syntax_shape.rs b/crates/nu-protocol/src/syntax_shape.rs
index 3e3337cd0..b0c1b9659 100644
--- a/crates/nu-protocol/src/syntax_shape.rs
+++ b/crates/nu-protocol/src/syntax_shape.rs
@@ -34,7 +34,7 @@ pub enum SyntaxShape {
     GlobPattern,
 
     /// A block is allowed, eg `{start this thing}`
-    Block,
+    Block(Option<Vec<SyntaxShape>>),
 
     /// A table is allowed, eg `[[first, second]; [1, 2]]`
     Table,
@@ -69,14 +69,18 @@ pub enum SyntaxShape {
 
     /// A general expression, eg `1 + 2` or `foo --bar`
     Expression,
+
+    /// A custom shape with custom completion logic
+    Custom(Box<SyntaxShape>, String),
 }
 
 impl SyntaxShape {
     pub fn to_type(&self) -> Type {
         match self {
             SyntaxShape::Any => Type::Unknown,
-            SyntaxShape::Block => Type::Block,
+            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,
diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs
index 094ca2d59..5d81e03f9 100644
--- a/crates/nu-protocol/src/value/mod.rs
+++ b/crates/nu-protocol/src/value/mod.rs
@@ -8,7 +8,7 @@ pub use stream::*;
 
 use std::fmt::Debug;
 
-use crate::ast::{PathMember, RangeInclusion};
+use crate::ast::PathMember;
 use crate::{span, BlockId, Span, Type};
 
 use crate::ShellError;
@@ -131,20 +131,10 @@ impl Value {
             Value::Int { val, .. } => val.to_string(),
             Value::Float { val, .. } => val.to_string(),
             Value::Range { val, .. } => {
-                let vals: Vec<i64> = match (&val.from, &val.to) {
-                    (Value::Int { val: from, .. }, Value::Int { val: to, .. }) => {
-                        match val.inclusion {
-                            RangeInclusion::Inclusive => (*from..=*to).collect(),
-                            RangeInclusion::RightExclusive => (*from..*to).collect(),
-                        }
-                    }
-                    _ => Vec::new(),
-                };
-
                 format!(
                     "range: [{}]",
-                    vals.iter()
-                        .map(|x| x.to_string())
+                    val.into_iter()
+                        .map(|x| x.into_string())
                         .collect::<Vec<String>>()
                         .join(", ")
                 )
diff --git a/crates/nu-protocol/src/value/range.rs b/crates/nu-protocol/src/value/range.rs
index 54d836de1..80eb56882 100644
--- a/crates/nu-protocol/src/value/range.rs
+++ b/crates/nu-protocol/src/value/range.rs
@@ -1,12 +1,108 @@
-use crate::{ast::RangeInclusion, *};
+use std::cmp::Ordering;
+
+use crate::{
+    ast::{RangeInclusion, RangeOperator},
+    *,
+};
 
 #[derive(Debug, Clone, PartialEq)]
 pub struct Range {
     pub from: Value,
+    pub incr: Value,
     pub to: Value,
     pub inclusion: RangeInclusion,
 }
 
+impl Range {
+    pub fn new(
+        expr_span: Span,
+        from: Value,
+        next: Value,
+        to: Value,
+        operator: &RangeOperator,
+    ) -> Result<Range, ShellError> {
+        // Select from & to values if they're not specified
+        // TODO: Replace the placeholder values with proper min/max based on data type
+        let from = if let Value::Nothing { .. } = from {
+            Value::Int {
+                val: 0i64,
+                span: Span::unknown(),
+            }
+        } else {
+            from
+        };
+
+        let to = if let Value::Nothing { .. } = to {
+            if let Ok(Value::Bool { val: true, .. }) = next.lt(expr_span, &from) {
+                Value::Int {
+                    val: -100i64,
+                    span: Span::unknown(),
+                }
+            } else {
+                Value::Int {
+                    val: 100i64,
+                    span: Span::unknown(),
+                }
+            }
+        } else {
+            to
+        };
+
+        // Check if the range counts up or down
+        let moves_up = matches!(from.lte(expr_span, &to), Ok(Value::Bool { val: true, .. }));
+
+        // Convert the next value into the inctement
+        let incr = if let Value::Nothing { .. } = next {
+            if moves_up {
+                Value::Int {
+                    val: 1i64,
+                    span: Span::unknown(),
+                }
+            } else {
+                Value::Int {
+                    val: -1i64,
+                    span: Span::unknown(),
+                }
+            }
+        } else {
+            next.sub(operator.next_op_span, &from)?
+        };
+
+        let zero = Value::Int {
+            val: 0i64,
+            span: Span::unknown(),
+        };
+
+        // Increment must be non-zero, otherwise we iterate forever
+        if matches!(incr.eq(expr_span, &zero), Ok(Value::Bool { val: true, .. })) {
+            return Err(ShellError::CannotCreateRange(expr_span));
+        }
+
+        // If to > from, then incr > 0, otherwise we iterate forever
+        if let (Value::Bool { val: true, .. }, Value::Bool { val: false, .. }) = (
+            to.gt(operator.span, &from)?,
+            incr.gt(operator.next_op_span, &zero)?,
+        ) {
+            return Err(ShellError::CannotCreateRange(expr_span));
+        }
+
+        // If to < from, then incr < 0, otherwise we iterate forever
+        if let (Value::Bool { val: true, .. }, Value::Bool { val: false, .. }) = (
+            to.lt(operator.span, &from)?,
+            incr.lt(operator.next_op_span, &zero)?,
+        ) {
+            return Err(ShellError::CannotCreateRange(expr_span));
+        }
+
+        Ok(Range {
+            from,
+            incr,
+            to,
+            inclusion: operator.inclusion,
+        })
+    }
+}
+
 impl IntoIterator for Range {
     type Item = Value;
 
@@ -25,8 +121,7 @@ pub struct RangeIterator {
     span: Span,
     is_end_inclusive: bool,
     moves_up: bool,
-    one: Value,
-    negative_one: Value,
+    incr: Value,
     done: bool,
 }
 
@@ -52,41 +147,66 @@ impl RangeIterator {
             span,
             is_end_inclusive: matches!(range.inclusion, RangeInclusion::Inclusive),
             done: false,
-            one: Value::Int { val: 1, span },
-            negative_one: Value::Int { val: -1, span },
+            incr: range.incr,
         }
     }
 }
 
+// Compare two floating point numbers. The decision interval for equality is dynamically scaled
+// as the value being compared increases in magnitude.
+fn compare_floats(val: f64, other: f64) -> Option<Ordering> {
+    let prec = f64::EPSILON.max(val.abs() * f64::EPSILON);
+
+    if (other - val).abs() < prec {
+        return Some(Ordering::Equal);
+    }
+
+    val.partial_cmp(&other)
+}
+
 impl Iterator for RangeIterator {
     type Item = Value;
     fn next(&mut self) -> Option<Self::Item> {
-        use std::cmp::Ordering;
         if self.done {
             return None;
         }
 
         let ordering = if matches!(self.end, Value::Nothing { .. }) {
-            Ordering::Less
+            Some(Ordering::Less)
         } else {
             match (&self.curr, &self.end) {
-                (Value::Int { val: x, .. }, Value::Int { val: y, .. }) => x.cmp(y),
-                // (Value::Float { val: x, .. }, Value::Float { val: y, .. }) => x.cmp(y),
-                // (Value::Float { val: x, .. }, Value::Int { val: y, .. }) => x.cmp(y),
-                // (Value::Int { val: x, .. }, Value::Float { val: y, .. }) => x.cmp(y),
-                _ => {
-                    self.done = true;
-                    return Some(Value::Error {
-                        error: ShellError::CannotCreateRange(self.span),
-                    });
+                (Value::Int { val: curr, .. }, Value::Int { val: end, .. }) => Some(curr.cmp(end)),
+                (Value::Float { val: curr, .. }, Value::Float { val: end, .. }) => {
+                    compare_floats(*curr, *end)
                 }
+                (Value::Float { val: curr, .. }, Value::Int { val: end, .. }) => {
+                    compare_floats(*curr, *end as f64)
+                }
+                (Value::Int { val: curr, .. }, Value::Float { val: end, .. }) => {
+                    compare_floats(*curr as f64, *end)
+                }
+                _ => None,
             }
         };
 
-        if self.moves_up
-            && (ordering == Ordering::Less || self.is_end_inclusive && ordering == Ordering::Equal)
+        let ordering = if let Some(ord) = ordering {
+            ord
+        } else {
+            self.done = true;
+            return Some(Value::Error {
+                error: ShellError::CannotCreateRange(self.span),
+            });
+        };
+
+        let desired_ordering = if self.moves_up {
+            Ordering::Less
+        } else {
+            Ordering::Greater
+        };
+
+        if (ordering == desired_ordering) || (self.is_end_inclusive && ordering == Ordering::Equal)
         {
-            let next_value = self.curr.add(self.span, &self.one);
+            let next_value = self.curr.add(self.span, &self.incr);
 
             let mut next = match next_value {
                 Ok(result) => result,
@@ -98,22 +218,6 @@ impl Iterator for RangeIterator {
             };
             std::mem::swap(&mut self.curr, &mut next);
 
-            Some(next)
-        } else if !self.moves_up
-            && (ordering == Ordering::Greater
-                || self.is_end_inclusive && ordering == Ordering::Equal)
-        {
-            let next_value = self.curr.add(self.span, &self.negative_one);
-
-            let mut next = match next_value {
-                Ok(result) => result,
-                Err(error) => {
-                    self.done = true;
-                    return Some(Value::Error { error });
-                }
-            };
-            std::mem::swap(&mut self.curr, &mut next);
-
             Some(next)
         } else {
             None
diff --git a/src/tests.rs b/src/tests.rs
index df619f2b9..7a04df632 100644
--- a/src/tests.rs
+++ b/src/tests.rs
@@ -214,6 +214,16 @@ fn block_param2() -> TestResult {
     run_test("[3] | each { |y| $y + 10 }", "[13]")
 }
 
+#[test]
+fn block_param3_list_iteration() -> TestResult {
+    run_test("[1,2,3] | each { $it + 10 }", "[11, 12, 13]")
+}
+
+#[test]
+fn block_param4_list_iteration() -> TestResult {
+    run_test("[1,2,3] | each { |y| $y + 10 }", "[11, 12, 13]")
+}
+
 #[test]
 fn range_iteration1() -> TestResult {
     run_test("1..4 | each { |y| $y + 10 }", "[11, 12, 13, 14]")
@@ -255,6 +265,22 @@ fn build_string3() -> TestResult {
     )
 }
 
+#[test]
+fn build_string4() -> TestResult {
+    run_test(
+        "['sam','rick','pete'] | each { build-string $it ' is studying'}",
+        "[sam is studying, rick is studying, pete is studying]",
+    )
+}
+
+#[test]
+fn build_string5() -> TestResult {
+    run_test(
+        "['sam','rick','pete'] | each { |x| build-string $x ' is studying'}",
+        "[sam is studying, rick is studying, pete is studying]",
+    )
+}
+
 #[test]
 fn cell_path_subexpr1() -> TestResult {
     run_test("([[lang, gems]; [nu, 100]]).lang", "[nu]")
@@ -308,3 +334,11 @@ fn row_condition2() -> TestResult {
         "1",
     )
 }
+
+#[test]
+fn better_block_types() -> TestResult {
+    run_test(
+        r#"([1, 2, 3] | each -n { $"($it.index) is ($it.item)" }).1"#,
+        "1 is 2",
+    )
+}