# 26 — Shell abbreviations and user-extensible builtins **Status:** done ## Problem Users cannot define shell abbreviations or custom commands. For example, there's no way to alias `gs` to `git status` or add a custom `mkcd` that creates a directory and cd's into it. ## Current architecture Commit `f88b1795` moved all shell internals (`__command`, `__interactive`, `__getenv`, `__setenv`) out of `_G` and into a hidden registry table at `LUA_RIDX_LUSH`. Builtins (`cd`, `exec`, `umask`) live at `LUA_RIDX_LUSH.builtins`. The dispatch path in `try_builtin()` (`lcmd.c:875`) already does a table lookup: ```c lua_getfield(L, -1, "builtins") /* get builtins table */ lua_getfield(L, -1, pa->argv[0]) /* look up command name */ ``` This means builtins are **already data-driven** — if a function appears in the table, it gets called. But the table is invisible to user code, so there's no way to add entries. ## Proposal: expose `lush.builtins` to user code Expose the `LUA_RIDX_LUSH` table (or a curated view of it) as a global like `lush` or `__lush`. Users extend the shell by adding functions to `lush.builtins`: ```lua -- abbreviation lush.builtins.gs = function(cmd) !git status end -- custom builtin lush.builtins.mkcd = function(cmd, dir) !mkdir -p ${dir} !cd ${dir} end -- override (with access to original) local orig_cd = lush.builtins.cd lush.builtins.cd = function(cmd, dir) orig_cd(cmd, dir) print("now in: " .. $PWD) end ``` This requires no new dispatch mechanism — `try_builtin()` already does the right thing. The only change is making the table accessible. ## Design considerations ### What to expose Option A: Expose the entire `LUA_RIDX_LUSH` table as `lush`: ```lua lush.builtins -- cd, exec, umask, user additions lush.command -- __command (backtick execution) lush.interactive -- __interactive (! execution) lush.getenv -- __getenv ($VAR read) lush.setenv -- __setenv ($VAR write) ``` This gives power users access to the shell primitives directly, which is useful for building abstractions. But it also means users could break internals. Option B: Expose only `lush.builtins` — safer, sufficient for the abbreviation use case. Consider the clarity of these endpoints. Would an alternative naming scheme be clearer? i.e. ```lua lush.commands -- cd, exec, umask, user additions lush.run -- __command (backtick execution) lush.interactive -- __interactive (! execution) -- is there an idiotmatic name for this? lush.getenv -- __getenv ($VAR read) lush.setenv -- __setenv ($VAR write) ``` ### Relationship to f88b1795 That commit deliberately hid these from `_G` to avoid polluting the namespace. Exposing a single `lush` global is a middle ground: one clean entry point instead of scattered `__double_underscore` globals, and users can only extend via a structured API rather than accidentally shadowing internals. ### Abbreviations vs builtins Shell abbreviations (fish-style text expansion) and builtins (function dispatch) are different features in traditional shells. But in lush, a builtin that calls `!git status` achieves the same effect as an abbreviation — the `!` prefix runs the command interactively with terminal inheritance. So a single mechanism covers both use cases. ### Builtin protocol Current builtins receive `(cmd_name, arg1, arg2, ...)` and return a result table `{code, stdout, stderr}`. User builtins should follow the same protocol. Document this as the contract. ## Files likely affected | File | Changes | |------|---------| | `linit.c` | Expose `LUA_RIDX_LUSH` table as `lush` global | | `lcmd.c` | No changes — `try_builtin()` already works via table lookup | | `lbuiltin.c` | No changes — existing builtins stay as-is | ## Open questions - Name: `lush`, `shell`, `__shell`? `lush` is clean and matches the project name. - Should `lush.command` / `lush.interactive` be exposed or kept hidden? - Should there be a `builtin` command to bypass user overrides (like bash's `builtin cd`)?