Using bu as a scripting dependency
The recommended way to use BashTab in your project is via the module system.
# 1. Add BashTab as a git submodule
git submodule add https://github.com/evagreendev/BashTab.git deps/bash-tab
# 2. Scaffold your module
source deps/bash-tab/activate
bu new-module --name myproject
# 3. Edit myproject/myproject_bu_preinit.sh, then activate
source ./myproject/activate
See the Workflow Guide for a complete walkthrough.
Legacy (manual registration)
The following variables and functions are used by libraries invoking BashTab to change its behavior at initialization time.
Initialization variables
Bash variables are all strings/arrays of strings/maps of strings, alongside some variable attributes (e.g. number -i, readonly -r etc.) but we can interpret them differently. These variables are “declared” (really, it’s for bash-langugage server to give hints when hovering over them) in bu_user_defined_decl.sh.
The customizable variables all have the BU_USER_DEFINED_ prefix.
Let us define the following conventional variable “types”:
| Type | Description | Example |
|---|---|---|
Function | The variable name refers to a shell function. | bu_init() { ... } |
AbsPath[A] | Absolute path. A is an optional annotation describing the expected file type (for example, ExecutableScript or SourceableScript). | |
ExecutableScript | Annotation for AbsPath indicating the file is an executable script. | /usr/local/bin/my-script has type AbsPath[ExecutableScript] |
SourceableScript | Annotation for AbsPath indicating the file is intended to be sourced. | ./bu_entrypoint.sh has type RelPath[SourceableScript] |
RelPath[A] | Relative path. A is an optional annotation like with AbsPath. | scripts/myscript.sh |
Path[A] | Either AbsPath[A] or RelPath[A]. | ./lib/core/bu_core_base.sh |
Int | Integer value. | 42 |
Ref[T] | A nameref to T, where T is some parameterized type. | declare -n ref=executable_script_path_name has type Ref[Path[ExecutableScript]] |
Array[T] | Bash indexed array whose elements are of type T. | BU_FOO=(one two three) has type Array[String] BU_BAR=(1 2 3) has type Array[Int] |
Map[K, V] | Bash associative array mapping keys of type K to values of type V. | declare -A m=([1]=one) has type Map[Int, String] |
T1 \| T2 | Union Type of T1 and T2 |
Variable list
| Variable | Type | Description |
|---|---|---|
BU_USER_DEFINED_STATIC_CONFIGS | Array[ Function \| Path[SourceableScript] ] | Static user-defined configuration callback scripts/functions. These are sourced once during initialization. |
BU_USER_DEFINED_DYNAMIC_CONFIGS | Array[ Function \| Path[SourceableScript] ] | Dynamic user-defined configuration callback scripts/functions. These are sourced every time the shell sources user-defined configs. |
BU_USER_DEFINED_STATIC_PRE_INIT_ENTRYPOINT_CALLBACKS | Array[ Function \| Path[SourceableScript] ] | Static user-defined pre-initialization callback scripts/functions. These are sourced once before shell initialization. |
BU_USER_DEFINED_DYNAMIC_POST_ENTRYPOINT_CALLBACKS | Array[ Function \| Path[SourceableScript] ] | Dynamic user-defined post-initialization callback scripts/functions. These are sourced after shell initialization. |
BU_USER_DEFINED_STATIC_POST_ENTRYPOINT_CALLBACKS | Array[ Function \| Path[SourceableScript] ] | Static user-defined post-initialization callback scripts/functions. These are sourced once after shell initialization. |
BU_USER_DEFINED_DYNAMIC_POST_ENTRYPOINT_CALLBACKS | Array[ Function \| Path[SourceableScript] ] | Dynamic user-defined post-initialization callback scripts/functions. These are sourced after shell initialization. |
BU_USER_DEFINED_COMPLETION_COMMAND_TO_KEY_CONVERSIONS | Array[Function] | User-defined command-to-key conversion functions. These functions can customize how commands are converted to completion keys. |
BU_USER_DEFINED_AUTOCOMPLETE_HELPERS | Array[Function] | User-defined autocomplete helper functions. These functions provide custom lazy autocompletion behavior. |
BU_USER_DEFINED_CLI_COMMAND_NAME | Function | A custom command line name for bu. |
Module registry
Modules register by appending to BU_MODULE_LIST, an exported scalar of the form "name:version:preinit_path;...". bu_entrypoint.sh parses this (deduping by name), sources each preinit, and populates BU_MODULE_REGISTRY for bu get-module.
# In a module script (appends):
BU_MODULE_LIST+="modname:0.1.0:/path/to/modname_bu_preinit.sh;"
# Or set directly in a top-level activate script:
export BU_MODULE_LIST="myproject:0.1.0:/path/to/preinit.sh;"
Host projects register library dependencies via bu_module_require (sourced BEFORE bu_entrypoint.sh; dependency-free — builtins + git only, safe under set -e/set -u):
source "$BU_DIR/lib/bu_module_require.sh"
bu_module_require somelib --dir "/path/to/somelib" # opt-in by presence
bu_module_require otherlib --dir "/path" --required # abort if missing
bu_module_require remotelib --dir "/path" --git-url "https://..." --branch main
It is idempotent (anchored match — lib vs mylib never collides), resolves <dir>/*_bu_module.sh or an explicit --module-file, and verifies the script actually registered the requested name.
This populates:
| Variable | Type | Description |
|---|---|---|
BU_MODULE_REGISTRY | Map[String, String] | name → "version:preinit_path". Available in current shell. |
BU_MODULE_LIST | String (exported) | "name:version:path;...". Deduped after parsing. Survives subshells for bu get-module. |
bu get-module reads BU_MODULE_LIST to display loaded modules with name, version, and path.
Initialization callable functions
Another point of customization are the pre-init functions. They are found in bu_core_preinit.sh. They all have the bu_preinit_ prefix.
bu_preinit_register_user_defined_key_binding:- Description: Register a key binding for interactive shells. Values are stored in
BU_KEY_BINDINGSand later applied viabind -x. When called from a module preinit callback (whereBU_CURRENT_MODULEis set), the owning module is stamped intoBU_KEY_BINDING_MODULESsobuhelp can attribute the binding; core defaults are stampedbu. - Params:
$1= key sequence (e.g.\ee),$2= command/function to invoke,$3(optional) = human-readable description shown inbuhelp. - Example:
bu_preinit_register_user_defined_key_binding '\em' my_custom_edit "Edit in my custom editor"
- Description: Register a key binding for interactive shells. Values are stored in
bu_preinit_register_user_defined_completion_func:- Description: Register a completion function for a specific command. Mappings are stored in
BU_AUTOCOMPLETE_COMPLETION_FUNCSand used to callcomplete -F. - Params:
$1= command name,$2= completion function name. - Example:
bu_preinit_register_user_defined_completion_func mycmd __mycmd_completion
- Description: Register a completion function for a specific command. Mappings are stored in
bu_preinit_register_user_defined_subcommand_dir:- Description: Register a directory containing subcommand scripts. Optionally provide a conversion function to transform file names to
verb-nouncommand names. - Params:
$1= directory path,...= optional conversion function and args. - Example:
bu_preinit_register_user_defined_subcommand_dir ~/my-commands bu_convert_file_to_command_namespace prefix
- Description: Register a directory containing subcommand scripts. Optionally provide a conversion function to transform file names to
bu_preinit_register_user_defined_subcommand_file:- Description: Register a single script file as a
busubcommand. - Params:
$1= file path,$2(optional) = command name (derived from filename if omitted),$3(optional) = type (function,execute,source). - Example:
bu_preinit_register_user_defined_subcommand_file ~/scripts/get-status.sh get-status execute
- Description: Register a single script file as a
bu_preinit_register_user_defined_subcommand_function:- Description: Register an in-shell function as a
busubcommand. - Params:
$1= function name,$2(optional) = command name,$3(optional) = type. - Example:
bu_preinit_register_user_defined_subcommand_function my_helper_func my-helper function
- Description: Register an in-shell function as a
bu_preinit_register_new_alias:- Description: Create a
bualias that expands a positional-style invocation into a named-argument command. Alias specs use{}for a single positional,{...}for remaining input, and{?}for optional remaining input. - Params:
$1= alias name,$2..= alias spec (seebu_preinit_register_new_aliasfor syntax rules). - Example:
bu_preinit_register_new_alias gc get-command --namespace {} {?} --verb {} {?} --noun {} {...}
- Description: Create a
Development & Contributing
Code structure
The core library is in core
- bu_core_base.sh provides the core library functions for the framework
- bu_core_autocomplete.sh provides the autocompletion functionality for the
bucommand and the scripts it invokes.
There are library functions that are not used by the core scripting framework, but are nonetheless useful in writing your own scripts.
- bu_core_tmux.sh provides imperative utilities (
bu_spawn) for orchestrating jobs across tmux panes.
Running Tests
BashTab uses the BATS (Bash Automated Testing System) framework for unit testing.
git submodule update --init
source ./bu_test_entrypoint.sh
bats ./test/parse_bash_test.bats # hand-written parser tests (54 tests)
bats ./test/ts_test.bats # tree-sitter daemon tests (49 tests)
bats ./test/fzf_dims_test.bats # fzf dimension calculation tests (14 tests)
bats ./test/out_test.bats # structured output tests (38 tests)
bats ./test/traceback_test.bats # exit-handler traceback rendering tests
Exit handler & traceback styles
When a command fails under errexit (bu_exit_handler_setup enables set -e -E), the exit handler in bu_core_base.sh prints a traceback of the call stack. The rendering style is controlled by the registered setting BU_STACKTRACE_STYLE:
| Value | Description |
|---|---|
short | One compact line per frame: 0: false at script.sh:42 |
full | Python-style frames with the surrounding source lines, fault line highlighted (default) |
# Compact
bu set-config BU_STACKTRACE_STYLE short
# Verbose — show surrounding source lines (default)
bu set-config BU_STACKTRACE_STYLE full
The full style renders each frame as a File "...", line N, in func header followed by a source window. The number of lines shown before/after each frame is BU_STACKTRACE_CONTEXT_LINES (default 4), a tunable global in bu_core_base.sh that can be exported before sourcing to widen the window:
export BU_STACKTRACE_CONTEXT_LINES=5
source ./activate
File "/path/to/script.sh", line 42, in my_func
40 │ local x=1
41 │ x=$((x + 1))
> 42 │ false
43 │ cleanup
44 │ }
Frames whose source file is missing or unreadable fall back to a single grey <source unavailable> line rather than failing the exit handler.
The full style also applies a light, dependency-free syntax highlight to the source lines: bash reserved words (violet), brackets/parens/braces (yellow), comments (grey, falling back to blue on 8-colour terminals), and single/double quoted strings (green). It is a single-pass highlighter, so it does not parse nested $((...))/${...} constructs or heredocs — it trades a little precision for zero dependencies and speed on the error path. When the terminal has no colour support the codes degrade to empty and the source is shown plain.
Tree-sitter daemon
Located at lib/bin/bu_ts_daemon.js. The bash wrapper lib/core/bu_core_ts.sh manages a coproc node process that accepts CURSOR:LINE input and returns JSON CST with:
| Field | Description |
|---|---|
cmdName | Command name at cursor |
cmdWords | Space-separated command words (unit separator delimited) |
pipeBefore / pipeAfter | Text before/after the last pipe separator |
cursor.replaceStart/End/Text | Range-based replacement (LSP TextEdit style) |
cursor.completeKind | command, dollar_word, dollar_brace |
Toggle with BU_AUTOCOMPLETE_USE_TREE_SITTER=true.
fzf autocomplete display
The __bu_fzf_compute_dimensions function handles dropdown positioning (tested across 40–200 column terminals). Metadata formatting is shared between the legacy and tree-sitter autocomplete paths, with:
- Inline hints: type tags + sizes in fzf
--with-nthfields - Preview panel: 40-char side window via
--previewwhen metadata overflows - Alias merging: case-pattern alternatives that are equal modulo leading
-/+and case (--select,select,SELECT) collapse into a single row — the first form, unless the user typed another form’s prefix (keeps the row alive through compgen’s prefix filter). Using any form excludes the whole group. Alternatives that differ after normalization (-v|--verb,--json|--yaml) stay separate rows; the legacy short/long used-option exclusion still applies.
Structured output (JSONL pipeline)
PowerShell-inspired structured output: JSONL (one JSON object per line) is the object stream, jq is the backend. Commands emit records, transforms shape them, and a sink formatter decides presentation at the end of the pipeline (Out-Default: table on a terminal, JSONL when piped).
Implemented in bu_core_out.sh with cmdlet commands (bu where, bu select, bu sort, bu query-object, bu format-table, …), including:
- Recordifiers / transforms / sinks with documented streaming-vs-buffering behavior
bu query-object— SQL-style clauses (where group-by agg having select distinct order-by first)- Pipeline-aware field completion (static registry + opt-in probing)
- Alias merging in option completion (
--select|selectis one row) - Multi-word verbs (
convert-to,convert-from)
See the full guide: Structured Output
Code Documentation Standards
This project follows a consistent documentation format for functions and variables, designed to work with bash-language-server.
Function Documentation Format
Functions should be documented with the following format:
# ```
# *Description*
# Short description of function
#
# *Params*:
# - `$1`: Description of first parameter
# - `$2`: Description of second parameter
#
# *Returns*:
# - `$BU_RET`: Description of return value
#
# *Example*:
# ```bash
# function_name "arg1" "arg2"
# ```
#
# *Notes*:
# - Important note 1
# - Important note 2
# ```
The outer triple backticks allow bash-language-server to properly parse the markdown documentation inside the comments. How it works is that bash-language-server defaults to wrapping comment blocks in a ` block, thus the top and bottom triple backticks close up the txt block so we can add arbitrary markdown in between.
Variable Naming Conventions
- Global Variables: Prefix with
BU_to namespace them within BashTab (e.g.,BU_VERSION,BU_CONFIG_PATH) - Functions: Prefix with
bu_to namespace them (e.g.,bu_init,bu_parse_args) - Return Values:
- Use
BU_RETto return strings and non-associative arrays. The value can be either scalar or array depending on the function’s purpose - Use
BU_RET_MAPto return associative arrays.
- Use
- User-Defined Variables: Prefix with
BU_USER_DEFINED_for variables that are expected to be defined externally by users
Building a Single-File Distribution
To consolidate the entire BashTab library into a single file for easier distribution or embedding:
source ./activate --__bu-inline ./inline.sh
This generates an inline.sh file containing the complete BashTab library. --__bu-inline makes certain assumptions about where source statements are invoked, so this might break at some points.