def error-fmt [] {
    $"(ansi red)($in)(ansi reset)"
}

def throw-error [error: string, msg: string, span: record] {
    error make {
        msg: ($error | error-fmt)
        label: {
            text: $msg
            span: $span
        }
    }
}

def module-not-found-error [span: record] {
    throw-error "std::help::module_not_found" "module not found" $span
}

def alias-not-found-error [span: record] {
    throw-error "std::help::alias_not_found" "alias not found" $span
}

def extern-not-found-error [span: record] {
    throw-error "std::help::extern_not_found" "extern not found" $span
}

def operator-not-found-error [span: record] {
    throw-error "std::help::operator_not_found" "operator not found" $span
}

def command-not-found-error [span: record] {
    throw-error "std::help::command_not_found" "command not found" $span
}

def get-all-operators [] {
    [
        [type, operator, name, description, precedence];
        [Assignment, =, Assign, 'Assigns a value to a variable.', 10]
        [Assignment, +=, AddAssign, 'Adds a value to a variable.', 10]
        [Assignment, -=, SubtractAssign, 'Subtracts a value from a variable.', 10]
        [Assignment, *=, MultiplyAssign, 'Multiplies a variable by a value.', 10]
        [Assignment, /=, DivideAssign, 'Divides a variable by a value.', 10]
        [Assignment, ++=, ConcatenateAssign, 'Concatenates a list, a string, or a binary value to a variable of the same type.', 10]
        [Comparison, ==, Equal, 'Checks if two values are equal.', 80]
        [Comparison, !=, NotEqual, 'Checks if two values are not equal.', 80]
        [Comparison, <, LessThan, 'Checks if a value is less than another.', 80]
        [Comparison, >, GreaterThan, 'Checks if a value is greater than another.', 80]
        [Comparison, <=, LessThanOrEqual, 'Checks if a value is less than or equal to another.', 80]
        [Comparison, >=, GreaterThanOrEqual, 'Checks if a value is greater than or equal to another.', 80]
        [Comparison, '=~ or like', RegexMatch, 'Checks if a value matches a regular expression.', 80]
        [Comparison, '!~ or not-like', NotRegexMatch, 'Checks if a value does not match a regular expression.', 80]
        [Comparison, in, In, 'Checks if a value is in a list, is part of a string, or is a key in a record.', 80]
        [Comparison, not-in, NotIn, 'Checks if a value is not in a list, is not part of a string, or is not a key in a record.', 80]
        [Comparison, has, Has, 'Checks if a list contains a value, a string contains another, or if a record has a key.', 80]
        [Comparison, not-has, NotHas, 'Checks if a list does not contains a value, a string does not contains another, or if a record does not have a key.', 80]
        [Comparison, starts-with, StartsWith, 'Checks if a string starts with another.', 80]
        [Comparison, ends-with, EndsWith, 'Checks if a string ends with another.', 80]
        [Math, +, Add, 'Adds two values.', 90]
        [Math, -, Subtract, 'Subtracts two values.', 90]
        [Math, *, Multiply, 'Multiplies two values.', 95]
        [Math, /, Divide, 'Divides two values.', 95]
        [Math, //, FloorDivide, 'Divides two values and floors the result.', 95]
        [Math, mod, Modulo, 'Divides two values and returns the remainder.', 95]
        [Math, **, Pow, 'Raises one value to the power of another.', 100]
        [Math, ++, Concatenate, 'Concatenates two lists, two strings, or two binary values.', 80]
        [Bitwise, bit-or, BitOr, 'Performs a bitwise OR on two values.', 60]
        [Bitwise, bit-xor, BitXor, 'Performs a bitwise XOR on two values.', 70]
        [Bitwise, bit-and, BitAnd, 'Performs a bitwise AND on two values.', 75]
        [Bitwise, bit-shl, ShiftLeft, 'Bitwise shifts a value left by another.', 85]
        [Bitwise, bit-shr, ShiftRight, 'Bitwise shifts a value right by another.', 85]
        [Boolean, or, Or, 'Checks if either value is true.', 40]
        [Boolean, xor, Xor, 'Checks if one value is true and the other is false.', 45]
        [Boolean, and, And, 'Checks if both values are true.', 50]
        [Boolean, not, Not, 'Negates a value or expression.', 55]
    ]
}

def "nu-complete list-aliases" [] {
    scope aliases | select name description | rename value description
}

def "nu-complete list-modules" [] {
    scope modules | select name description | rename value description
}

def "nu-complete list-operators" [] {
    let completions = (
        get-all-operators
        | select name description
        | rename value description
    )
    $completions
}

def "nu-complete list-commands" [] {
    scope commands | select name description | rename value description
}

def "nu-complete main-help" [] {
    [
        { value: "commands", description: "Show help on Nushell commands." }
        { value: "aliases", description: "Show help on Nushell aliases." }
        { value: "modules", description: "Show help on Nushell modules." }
        { value: "externs", description: "Show help on Nushell externs." }
        { value: "operators", description: "Show help on Nushell operators." }
        { value: "escapes", description: "Show help on Nushell string escapes." }
    ]
    | append (nu-complete list-commands)
}

def "nu-complete list-externs" [] {
    scope commands | where is_extern == true | select name description | rename value description
}

def build-help-header [
    text: string
    --no-newline (-n)
] {
    let header = $"(ansi green)($text)(ansi reset):"

    if $no_newline {
        $header
    } else {
        $header ++ "\n"
    }
}

def build-module-page [module: record] {
    let description = (if not ($module.description? | is-empty) {[
        $module.description
        ""
    ]} else { [] })

    let name = [
        $"(build-help-header -n "Module") ($module.name)"
        ""
    ]

    let commands = (if not ($module.commands? | is-empty) {[
        (build-help-header -n "Exported commands")
        $"    (
            $module.commands | each {|command|
                $'($command) (char lparen)($module.name) ($command)(char rparen)'
            }
            | str join ', '
        )"
        ""
    ]} else { [] })

    let aliases = (if not ($module.aliases? | is-empty) {[
        (build-help-header -n "Exported aliases")
        $"    ($module.aliases | str join ', ')"
        ""
    ]} else { [] })

    let env_block = (if ($module.env_block? | is-empty) {[
        $"This module (ansi cyan)does not export(ansi reset) environment."
    ]} else {[
        $"This module (ansi cyan)exports(ansi reset) environment."
        (view source $module.env_block)
    ]})

    [$description $name $commands $aliases $env_block] | flatten | str join "\n"
}

# Show help on nushell modules.
#
# When requesting help for a single module, its commands and aliases will be highlighted if they
# are also available in the current scope. Commands/aliases that were imported under a different name
# (such as with a prefix after `use some-module`) will be highlighted in parentheses.
#
# Examples:
#     > let us define some example modules to play with
#     > ```nushell
#     > # my foo module
#     > module foo {
#     >     def bar [] { "foo::bar" }
#     >     export def baz [] { "foo::baz" }
#     >
#     >     export-env {
#     >         $env.FOO = "foo::FOO"
#     >     }
#     > }
#     >
#     > # my bar module
#     > module bar {
#     >     def bar [] { "bar::bar" }
#     >     export def baz [] { "bar::baz" }
#     >
#     >     export-env {
#     >         $env.BAR = "bar::BAR"
#     >     }
#     > }
#     >
#     > # my baz module
#     > module baz {
#     >     def foo [] { "baz::foo" }
#     >     export def bar [] { "baz::bar" }
#     >
#     >     export-env {
#     >         $env.BAZ = "baz::BAZ"
#     >     }
#     > }
#     > ```
#
#     show all aliases
#     > help modules
#     ╭───┬──────┬──────────┬────────────────┬──────────────┬───────────────╮
#     │ # │ name │ commands │    aliases     │  env_block   │  description  │
#     ├───┼──────┼──────────┼────────────────┼──────────────┼───────────────┤
#     │ 0 │ bar  │ [baz]    │ [list 0 items] │ <Block 1331> │ my bar module │
#     │ 1 │ baz  │ [bar]    │ [list 0 items] │ <Block 1335> │ my baz module │
#     │ 2 │ foo  │ [baz]    │ [list 0 items] │ <Block 1327> │ my foo module │
#     ╰───┴──────┴──────────┴────────────────┴──────────────┴───────────────╯
#
#     search for string in module names
#     > help modules --find ba
#     ╭───┬──────┬─────────────┬────────────────┬──────────────┬───────────────╮
#     │ # │ name │  commands   │    aliases     │  env_block   │  description  │
#     ├───┼──────┼─────────────┼────────────────┼──────────────┼───────────────┤
#     │ 0 │ bar  │ ╭───┬─────╮ │ [list 0 items] │ <Block 1331> │ my bar module │
#     │   │      │ │ 0 │ baz │ │                │              │               │
#     │   │      │ ╰───┴─────╯ │                │              │               │
#     │ 1 │ baz  │ ╭───┬─────╮ │ [list 0 items] │ <Block 1335> │ my baz module │
#     │   │      │ │ 0 │ bar │ │                │              │               │
#     │   │      │ ╰───┴─────╯ │                │              │               │
#     ╰───┴──────┴─────────────┴────────────────┴──────────────┴───────────────╯
#
#     search help for single module
#     > help modules foo
#     my foo module
#
#     Module: foo
#
#     Exported commands:
#         baz [foo baz]
#
#     This module exports environment.
#     {
#             $env.FOO = "foo::FOO"
#         }
#
#     search for a module that does not exist
#     > help modules "does not exist"
#     Error:
#       × std::help::module_not_found
#        ╭─[entry #21:1:1]
#      1 │ help modules "does not exist"
#        ·              ────────┬───────
#        ·                      ╰── module not found
#        ╰────
export def modules [
    ...module: string@"nu-complete list-modules"  # the name of module to get help on
    --find (-f): string  # string to find in module names
] {
    let modules = (scope modules)

    if not ($find | is-empty) {
        $modules | find $find --columns [name description]
    } else if not ($module | is-empty) {
        let found_module = ($modules | where name == ($module | str join " "))

        if ($found_module | is-empty) {
            module-not-found-error (metadata $module | get span)
        }

        build-module-page ($found_module | get 0)
    } else {
        $modules
    }
}

def build-alias-page [alias: record] {
    let description = (if not ($alias.description? | is-empty) {[
        $alias.description
        ""
    ]} else { [] })

    let rest = [
        (build-help-header -n "Alias")
        $"  ($alias.name)"
        ""
        (build-help-header -n "Expansion")
        $"  ($alias.expansion)"
    ]

    [$description $rest] | flatten | str join "\n"
}

# Show help on nushell aliases.
#
# Examples:
#     > let us define a bunch of aliases
#     > ```nushell
#     > # my foo alias
#     > alias foo = echo "this is foo"
#     >
#     > # my bar alias
#     > alias bar = echo "this is bar"
#     >
#     > # my baz alias
#     > alias baz = echo "this is baz"
#     >
#     > # a multiline alias
#     > alias multi = echo "this
#     > is
#     > a
#     > multiline
#     > string"
#     > ```
#
#     show all aliases
#     > help aliases
#     ╭───┬───────┬────────────────────┬───────────────────╮
#     │ # │ name  │     expansion      │    description    │
#     ├───┼───────┼────────────────────┼───────────────────┤
#     │ 0 │ bar   │ echo "this is bar" │ my bar alias      │
#     │ 1 │ baz   │ echo "this is baz" │ my baz alias      │
#     │ 2 │ foo   │ echo "this is foo" │ my foo alias      │
#     │ 3 │ multi │ echo "this         │ a multiline alias │
#     │   │       │ is                 │                   │
#     │   │       │ a                  │                   │
#     │   │       │ multiline          │                   │
#     │   │       │ string"            │                   │
#     ╰───┴───────┴────────────────────┴───────────────────╯
#
#     search for string in alias names
#     > help aliases --find ba
#     ╭───┬──────┬────────────────────┬──────────────╮
#     │ # │ name │     expansion      │ description  │
#     ├───┼──────┼────────────────────┼──────────────┤
#     │ 0 │ bar  │ echo "this is bar" │ my bar alias │
#     │ 1 │ baz  │ echo "this is baz" │ my baz alias │
#     ╰───┴──────┴────────────────────┴──────────────╯
#
#     search help for single alias
#     > help aliases multi
#     a multiline alias
#
#     Alias: multi
#
#     Expansion:
#       echo "this
#     is
#     a
#     multiline
#     string"
#
#     search for an alias that does not exist
#     > help aliases "does not exist"
#     Error:
#       × std::help::alias_not_found
#        ╭─[entry #21:1:1]
#      1 │ help aliases "does not exist"
#        ·              ────────┬───────
#        ·                      ╰── alias not found
#        ╰────
export def aliases [
    ...alias: string@"nu-complete list-aliases"  # the name of alias to get help on
    --find (-f): string  # string to find in alias names
] {
    let aliases = (scope aliases | sort-by name)

    if not ($find | is-empty) {
        $aliases | find $find --columns [name description]
    } else if not ($alias | is-empty) {
        let found_alias = ($aliases | where name == ($alias | str join " "))

        if ($found_alias | is-empty) {
            alias-not-found-error (metadata $alias | get span)
        }

        build-alias-page ($found_alias | get 0)
    } else {
        $aliases
    }
}

def build-extern-page [extern: record] {
    let description = (if not ($extern.description? | is-empty) {[
        $extern.description
        ""
    ]} else { [] })

    let rest = [
        (build-help-header -n "Extern")
        $" ($extern.name)"
    ]

    [$description $rest] | flatten | str join "\n"
}

# Show help on nushell externs.
export def externs [
    ...extern: string@"nu-complete list-externs"  # the name of extern to get help on
    --find (-f): string  # string to find in extern names
] {
    let externs = (
        scope commands
        | where is_extern == true
        | select name module_name description
        | sort-by name
        | str trim
    )

    if not ($find | is-empty) {
        $externs | find $find --columns [name description]
    } else if not ($extern | is-empty) {
        let found_extern = ($externs | where name == ($extern | str join " "))

        if ($found_extern | is-empty) {
            extern-not-found-error (metadata $extern | get span)
        }

        build-extern-page ($found_extern | get 0)
    } else {
        $externs
    }
}

def build-operator-page [operator: record] {
    [
        (build-help-header -n "Description")
        $"    ($operator.description)"
        ""
        (build-help-header -n "Operator")
        $"  ($operator.name) (char lparen)(ansi cyan_bold)($operator.operator)(ansi reset)(char rparen)"
        (build-help-header -n "Type")
        $"  ($operator.type)"
        (build-help-header -n "Precedence")
        $"  ($operator.precedence)"
    ] | str join "\n"
}

# Show help on nushell operators.
#
# Examples:
#     search for string in operators names
#     > help operators --find Bit
#     ╭───┬─────────┬──────────┬────────┬───────────────────────────────────────┬────────────╮
#     │ # │  type   │ operator │  name  │              description              │ precedence │
#     ├───┼─────────┼──────────┼────────┼───────────────────────────────────────┼────────────┤
#     │ 0 │ Bitwise │ bit-and  │ BitAnd │ Performs a bitwise AND on two values. │         75 │
#     │ 1 │ Bitwise │ bit-or   │ BitOr  │ Performs a bitwise OR on two values.  │         60 │
#     │ 2 │ Bitwise │ bit-xor  │ BitXor │ Performs a bitwise XOR on two values. │         70 │
#     ╰───┴─────────┴──────────┴────────┴───────────────────────────────────────┴────────────╯
#
#     search help for single operator
#     > help operators NotRegexMatch
#     Description:
#         Checks if a value does not match a regular expression.
#
#     Operator: NotRegexMatch (!~)
#     Type: Comparison
#     Precedence: 80
#
#     search for an operator that does not exist
#     > help operator "does not exist"
#     Error:
#       × std::help::operator_not_found
#        ╭─[entry #21:1:1]
#      1 │ help operator "does not exist"
#        ·               ────────┬───────
#        ·                       ╰── operator not found
#        ╰────
export def operators [
    ...operator: string@"nu-complete list-operators"  # the name of operator to get help on
    --find (-f): string  # string to find in operator names
] {
    let operators = (get-all-operators)

    if not ($find | is-empty) {
        $operators | find $find --columns [type name]
    } else if not ($operator | is-empty) {
        let found_operator = ($operators | where name == ($operator | str join " "))

        if ($found_operator | is-empty) {
            operator-not-found-error (metadata $operator | get span)
        }

        build-operator-page ($found_operator | get 0)
    } else {
        $operators
    }
}

def get-extension-by-prefix [prefix: string] {
  scope commands
  | where name starts-with $prefix
  | insert extension { get name | parse $"($prefix){ext}" | get ext.0 | $"*.($in)" }
  | select extension name
  | rename --column { name: command }
}

def get-command-extensions [command: string] {
  # low-tech version of `nu-highlight`, which produces suboptimal results with unknown commands
  def hl [shape: string] {
    let color = $env.config.color_config | get $"shape_($shape)"
    $"(ansi $color)($in)(ansi reset)"
  }

  let extensions = {
    "open": {||
      [
        (
          $"('open' | hl internalcall) will attempt to automatically parse the file according to its extension,"
          + $" by calling ('from ext' | hl internalcall) on the file contents. For example,"
          + $" ('open' | hl internalcall) ('file.json' | hl globpattern) will call"
          + $" ('from json' | hl internalcall). If the file is not a supported type, its content will be returned"
          + $" as a binary stream instead."
        )
        ""
        "The following extensions are recognized:"
        (get-extension-by-prefix "from " | table --index false)
      ]
    }

    "save": {||
      [
        (
          $"('save' | hl internalcall) will attempt to automatically serialize its input into the format"
          + $" determined by the file extension, by calling ('to ext' | hl internalcall) before writing the data" 
          + $" to the file. For example, ('save' | hl internalcall) ('file.json' | hl globpattern)"
          + $" will call ('to json' | hl internalcall)."
        )
        ""
        "The following extensions are recognized:"
        (get-extension-by-prefix "to " | table --index false)
      ]
    }
  }

  if $command in $extensions {
    $extensions
    | get $command
    | do $in
    | each { lines | each { $"  ($in)" } | str join "\n" }
  } else {
    []
  }
}

def build-command-page [command: record] {
    let description = (if not ($command.description? | is-empty) {[
        $command.description
    ]} else { [] })
    let extra_description = (if not ($command.extra_description? | is-empty) {[
        ""
        $command.extra_description
    ]} else { [] })

    let search_terms = (if not ($command.search_terms? | is-empty) {[
        ""
        $"(build-help-header -n 'Search terms') ($command.search_terms)"
    ]} else { [] })

    let module = (if not ($command.module_name? | is-empty) {[
        ""
        $"(build-help-header -n 'Module') ($command.module_name)"
    ]} else { [] })

    let category = (if not ($command.category? | is-empty) {[
        ""
        $"(build-help-header -n 'Category') ($command.category)"
    ]} else { [] })

    let this = ([
        ""
        "This command:"
    ] | append (
      {
        "Creates scope" : $command.creates_scope,
        "Is built-in" : ($command.type == "built-in"),
        "Is const" : $command.is_const,
        "Is a subcommand" : $command.is_sub,
        "Is a part of a plugin": ($command.type == "plugin"),
        "Is a custom command": ($command.type == "custom"),
        "Is a keyword": ($command.type == "keyword"),
      }
      | transpose name value
      | update value { if $in { "2705" } else { "274c" } | char --unicode $in }
      | transpose -dr
      | table
    ))

    let signatures = ($command.signatures | transpose | get column1)

    let cli_usage = (if not ($signatures | is-empty) {
        let parameters = ($signatures | get 0 | where parameter_type != input and parameter_type != output)

        let positionals = ($parameters | where parameter_type == positional and parameter_type != rest)
        let flags = ($parameters | where parameter_type != positional and parameter_type != rest)

        [
            ""
            (build-help-header -n "Usage")
            ([
                $"  > ($command.name) "
                (if not ($flags | is-empty) { "{flags} " } else "")
                ($positionals | each {|param|
                    $"<($param.parameter_name)> "
                })
            ] | flatten | str join "")
            ""
        ]
    } else { [] })

    let subcommands = (scope commands | where name =~ $"^($command.name) " | select name description)
    let subcommands = (if not ($subcommands | is-empty) {[
        (build-help-header "Subcommands")
        ($subcommands | each {|subcommand |
            $"  (ansi teal)($subcommand.name)(ansi reset) - ($subcommand.description)"
        } | str join "\n")
    ]} else { [] })

    let rest = (if not ($signatures | is-empty) {
        let parameters = ($signatures | get 0 | where parameter_type != input and parameter_type != output)

        let positionals = ($parameters | where parameter_type == positional and parameter_type != rest)
        let flags = ($parameters | where parameter_type != positional and parameter_type != rest)
        let is_rest = (not ($parameters | where parameter_type == rest | is-empty))

        ([
            ""
            (build-help-header "Flags")
            ($flags | each {|flag|
                [
                    "  ",
                    (if ($flag.short_flag | is-empty) { "" } else {
                        $"-(ansi teal)($flag.short_flag)(ansi reset), "
                    }),
                    (if ($flag.parameter_name | is-empty) { "" } else {
                        $"--(ansi teal)($flag.parameter_name)(ansi reset)"
                    }),
                    (if ($flag.syntax_shape | is-empty) { "" } else {
                        $": <(ansi light_blue)($flag.syntax_shape)(ansi reset)>"
                    }),
                    (if ($flag.description | is-empty) { "" } else {
                        $" - ($flag.description)"
                    }),
                    (if ($flag.parameter_default | is-empty) { "" } else {
                        $" \(default: ($flag.parameter_default)\)"
                    }),
                ] | str join ""
            } | str join "\n")
            $"  (ansi teal)-h(ansi reset), --(ansi teal)help(ansi reset) - Display the help message for this command"

            ""
            (build-help-header "Signatures")
            ($signatures | each {|signature|
                let input = ($signature | where parameter_type == input | get 0)
                let output = ($signature | where parameter_type == output | get 0)

                ([
                    $"  <($input.syntax_shape)> | ($command.name)"
                    ($positionals | each {|positional|
                        $" <($positional.syntax_shape)>"
                    })
                    $" -> <($output.syntax_shape)>"
                ] | str join "")
            } | str join "\n")

            (if (not ($positionals | is-empty)) or $is_rest {[
                ""
                (build-help-header "Parameters")
                ($positionals | each {|positional|
                    ([
                        "  ",
                        $"(ansi teal)($positional.parameter_name)(ansi reset)",
                        (if ($positional.syntax_shape | is-empty) { "" } else {
                            $": <(ansi light_blue)($positional.syntax_shape)(ansi reset)>"
                        }),
                        (if ($positional.description | is-empty) { "" } else {
                            $" ($positional.description)"
                        }),
                        (if ($positional.parameter_default | is-empty) { "" } else {
                            $" \(optional, default: ($positional.parameter_default)\)"
                        })
                    ] | str join "")
                } | str join "\n")

                (if $is_rest {
                    let rest = ($parameters | where parameter_type == rest | get 0)
                    $"  ...(ansi teal)rest(ansi reset): <(ansi light_blue)($rest.syntax_shape)(ansi reset)> ($rest.description)"
                })
            ]} else { [] })
        ] | flatten)
    } else { [] })

    # This section documents how the command can be extended
    # E.g. `open` can be extended by adding more `from ...` commands
    let extensions = (
      get-command-extensions $command.name
      | if ($in | is-not-empty) {
        prepend [
          ""
          (build-help-header -n "Extensions")
        ]
      } else {}
    )

    let examples = (if not ($command.examples | is-empty) {[
        ""
        (build-help-header -n "Examples")
        ($command.examples | each {|example| [
            $"  ($example.description)"
            $"  > ($example.example | nu-highlight)"
            (if not ($example.result | is-empty) {
                $example.result
                | table -e
                | to text
                | if ($example.result | describe) == "binary" { str join } else { lines }
                | each {|line|
                    $"  ($line)"
                }
                | str join "\n"
            })
            ""
        ] | str join "\n"})
    ] | flatten} else { [] })

    [
        $description
        $extra_description
        $search_terms
        $module
        $category
        $this
        $cli_usage
        $subcommands
        $rest
        $extensions
        $examples
    ] | flatten | str join "\n"
}

def scope-commands [
    ...command: string@"nu-complete list-commands"  # the name of command to get help on
    --find (-f): string  # string to find in command names and description
] {
    let commands = (scope commands | sort-by name)

    if not ($find | is-empty) {
        # TODO: impl find for external commands
        $commands | find $find --columns [name description search_terms] | select name category description signatures search_terms
    } else if not ($command | is-empty) {
        let target_command = ($command | str join " ")
        let found_command = ($commands | where name == $target_command)

        if ($found_command | is-empty) {
            command-not-found-error (metadata $command | get span)
        } else {
            build-command-page ($found_command | get 0)
        }
    } else {
        $commands | select name category description signatures search_terms
    }
}

def external-commands [
    ...command: string@"nu-complete list-commands",
] {
    let target_command = $command | str join " " | str replace "^" ""
    print $"(ansi default_italic)Help pages from external command ($target_command | pretty-cmd):(ansi reset)"
    if $env.NU_HELPER? == "--help" {
        run-external ($target_command | split row " ") "--help" | if $nu.os-info.name == "windows" { collect } else {}
    } else {
        ^($env.NU_HELPER? | default "man") $target_command
    }
}

# Show help on commands.
export def commands [
    ...command: string@"nu-complete list-commands"  # the name of command to get help on
    --find (-f): string  # string to find in command names and description
] {
    try {
        scope-commands ...$command --find=$find
    } catch {
        external-commands ...$command
    }
}

def pretty-cmd [] {
    let cmd = $in
    $"(ansi default_dimmed)(ansi default_italic)($cmd)(ansi reset)"
}

# Display help information about different parts of Nushell.
#
# `help word` searches for "word" in commands, aliases and modules, in that order.
# If not found as internal to nushell, you can set `$env.NU_HELPER` to a program
# (default: man) and "word" will be passed as the first argument.
# Alternatively, you can set `$env.NU_HELPER` to `--help` and it will run "word" as
# an external and pass `--help` as the last argument (this could cause unintended
# behaviour if it doesn't support the flag, use it carefully).
#
# Examples:
#   show help for single command, alias, or module
#   > help match
#
#   show help for single sub-command, alias, or module
#   > help str join
#
#   search for string in command names, description and search terms
#   > help --find char
export def main [
    ...item: string@"nu-complete main-help"  # the name of the help item to get help on
    --find (-f): string  # string to find in help items names and description
] {
    if ($item | is-empty) and ($find | is-empty) {
        print $"Welcome to Nushell.

Here are some tips to help you get started.
  * ('help -h' | pretty-cmd) or ('help help' | pretty-cmd) - show available ('help' | pretty-cmd) subcommands and examples
  * ('help commands' | pretty-cmd) - list all available commands
  * ('help <name>' | pretty-cmd) - display help about a particular command, alias, or module
  * ('help --find <text to search>' | pretty-cmd) - search through all help commands table

Nushell works on the idea of a '(ansi default_italic)pipeline(ansi reset)'. Pipelines are commands connected with the '|' character.
Each stage in the pipeline works together to load, parse, and display information to you.

(ansi green)Examples(ansi reset):
    List the files in the current directory, sorted by size
    > ('ls | sort-by size' | nu-highlight)

    Get the current system host name
    > ('sys host | get hostname' | nu-highlight)

    Get the processes on your system actively using CPU
    > ('ps | where cpu > 0' | nu-highlight)

You can also learn more at (ansi default_italic)(ansi light_cyan_underline)https://www.nushell.sh/book/(ansi reset)"
        return
    }

    let target_item = ($item | str join " ")

    let commands = (try { scope-commands $target_item --find $find })
    if not ($commands | is-empty) { return $commands }

    let aliases = (try { aliases $target_item --find $find })
    if not ($aliases | is-empty) { return $aliases }

    let modules = (try { modules $target_item --find $find })
    if not ($modules | is-empty) { return $modules }

    if ($find | is-not-empty) {
        print -e $"No help results found mentioning: ($find)"
        return []
    }
    # use external tool (e.g: `man`) to search help for $target_item
    # the stdout and stderr of external tool will follow `main` call.
    external-commands $target_item
}
