Skip to main content
Skip to main content

Language Server & VSCode Extension

argsh logo

argsh includes a Language Server Protocol (LSP) implementation and VSCode extension that provides IDE support for argsh-specific syntax. Works alongside shellcheck — argsh handles framework-specific validation, shellcheck handles general bash.

Tip

The same diagnostics engine is also available as a standalone CLI binary — argsh-lint — for CI pipelines and editor-agnostic integrations. See How to Lint your scripts for the command-line interface. Both the LSP server and the CLI share a single Rust crate so the set of checks stays in sync.

Features

Diagnostics

The language server validates your argsh scripts in real-time. Each diagnostic has a code (AG001–AG013) that can be suppressed.

CodeSeverityDescription
AG001ErrorArgs entry missing description
AG002ErrorUsage entry missing description
AG003ErrorInvalid field spec (bad modifier)
AG004WarningMissing local variable declaration for args field
AG005ErrorArgs declared but :args never called
AG006ErrorUsage declared but :usage never called
AG007WarningUsage target function not found (searched imports)
AG008WarningDuplicate flag name
AG009WarningDuplicate short alias
AG010WarningCommand resolves to bare function (not namespaced)
AG012HintLocal variable shadows parent scope args field
AG013WarningImport could not be resolved to a file

Suppressing diagnostics

Like shellcheck, add comments to suppress specific diagnostics:

# argsh disable=AG004
local -a args=('port|p:~int' "Port") # no local port needed

# argsh disable=AG007,AG010 # suppress multiple codes

echo "hello" # argsh disable=AG004 # inline suppression

# argsh disable-file=AG007 # suppress for entire file

Completions

Context-aware completions trigger automatically:

ContextTriggerSuggestions
Inside args=(...) after :::+, :~int, :~float, :!, :#
After :~~int, float, file, boolean, string, custom to:: types
Inside usage=(...) after @@readonly, destructive, json, idempotent, openworld
After :--Function names in the file and imports
After importspacestring, array, fmt, error, is, to, ...
After is::, to::, etc.::Library functions (e.g. is::array, to::int)

Custom to:: validator functions defined in your file are automatically suggested as types.

Help Preview

Hover over any function to see its generated help text:

  • Flag table with types, aliases, and descriptions
  • Array types shown as type[]
  • Subcommand list for :usage functions
  • Summary counts (flags, required, subcommands)

Hover over the args or usage keyword to see all defined entries in a table.

Hover over a usage entry to see the target function's full help with its flags and subcommands.

Hover over :~typename to see type documentation (built-in or custom validator info).

Hover over '-' group separators to see the section heading.

Code Lens

Clickable indicators above functions showing:

  • Branch icon for :usage dispatchers / leaf icon for :args functions
  • argsh: N params, M flags (R required) · K subcommands · ← parent
  • Click to open the script preview

Script Preview

Use Ctrl+Shift+A or the command palette to open a dashboard:

  • Command tree with all nested subcommands
  • Flag details per command (with type[] for arrays)
  • MCP tools with proper tool names and annotation badges
  • Export links to MCP JSON, YAML, and JSON

Go to Definition

Ctrl+Click on:

  • A usage entry command name — jumps to the target function
  • A :-func::name mapping — jumps to the explicit target
  • :~typename — jumps to the to::typename() function
  • import module — opens the imported file
  • Works across files (follows import and source argsh up to configurable depth)

Auto Formatter

Aligns args and usage array entries so descriptions start at the same column:

# Before
'port|p:~int' "Port number"
'verbose|v:+' "Verbose output"
'config|c' "Config file path"

# After (formatted)
'port|p:~int' "Port number"
'verbose|v:+' "Verbose output"
'config|c' "Config file path"

Triggered via:

  • Shift+Alt+F (format document)
  • argsh: Format argsh Arrays in command palette
  • Automatically on save (configurable)

Command Tree Panel

A tree view in the bottom panel showing the command hierarchy:

  • Functions with branch/leaf icons
  • Args entries as field children, usage entries as command children
  • Active function highlighted in green based on cursor position
  • Click to navigate to function

Document Symbols

The sidebar outline shows the function hierarchy with :: namespace nesting.

Export Commands

CommandDescription
argsh: Export MCP JSONMCP tool schema in JSON
argsh: Export YAMLDocgen YAML output
argsh: Export JSONDocgen JSON output

Each opens in a new editor tab.

Installation

From Source

# Build the LSP binary
cargo build --release --manifest-path crates/argsh-lsp/Cargo.toml

# Set up the binary for the extension
mkdir -p vscode-argsh/bin
cp crates/argsh-lsp/target/release/argsh-lsp vscode-argsh/bin/

# Install the VSCode extension
cd vscode-argsh && npm install && npm run compile

Then in VSCode: Ctrl+Shift+P → "Developer: Install Extension from Location..." → select vscode-argsh/.

Configuration

SettingDefaultDescription
argsh.lsp.enabledtrueEnable the language server
argsh.lsp.path""Path to argsh-lsp binary (auto-detected if empty)
argsh.commandTree.enabledtrueShow the command tree panel
argsh.codeLens.enabledtrueShow flag/subcommand counts above functions
argsh.formatOnSavetrueAuto-format argsh arrays on save
argsh.resolveDepth2Max depth for cross-file import resolution (0–5)

Other Editors

Neovim (nvim-lspconfig)

local lspconfig = require('lspconfig')
local configs = require('lspconfig.configs')

configs.argsh = {
default_config = {
cmd = { 'argsh-lsp' },
filetypes = { 'sh', 'bash' },
root_dir = lspconfig.util.find_git_ancestor,
settings = {},
},
}

lspconfig.argsh.setup({})

Helix

Add to ~/.config/helix/languages.toml:

[[language]]
name = "bash"
language-servers = ["bash-language-server", "argsh-lsp"]

[language-server.argsh-lsp]
command = "argsh-lsp"

.envrc Parsing

The LSP server reads .envrc files from the project root to discover configuration variables like PATH_BASE and PATH_SCRIPTS. This enables accurate import resolution even without running direnv allow or setting environment variables manually.

Supported patterns:

  • : "${VAR:=value}" (argsh default pattern)
  • export VAR=value and export VAR="value"
  • VAR=value (plain assignment)
  • ${VAR} and $VAR references are expanded using previously parsed values

Lines with command substitution ($(...)) are skipped — when PATH_BASE uses $(git rev-parse ...), the LSP falls back to auto-detecting the project root. Environment variables always take precedence over .envrc values.

Tip

Set ARGSH_DEBUG=1 to see debug trace output for import resolution and builtin loading. See Environment Variables for the full reference.

Architecture

crates/argsh-syntax/  — shared parsing library (pure Rust, no FFI)
crates/argsh-lsp/LSP server (tower-lsp)
vscode-argsh/ — VSCode extension (TypeScript)

The LSP server communicates over stdio and works with any LSP-compatible editor (VSCode, Windsurf, Neovim, Helix, Emacs).

Test Coverage

  • argsh-syntax: 97 unit tests (field parsing, usage parsing, document analysis, scope chains)
  • argsh-lsp: 94 tests (39 unit + 55 integration over stdio)
Was this section helpful?