Skip to content

Commit fbecc2a

Browse files
fix(zsh): use global scope for typeset to support lazy loading
The usage of typeset within zsh's scope rules means that if the eval "$(forge zsh plugin)" is run within a function, the typeset's will be function local scoped. However, forge expects these to be globally scoped. This can be fixed by specifying typeset -g to be global instead of scoped to whatever scope it currently is. With this fixed, it would allow lazy loading plugin managers to work, or in my case, by manually wrapping the forge initialization into a function: forge_ai() { if [[ -z "$_FORGE_PLUGIN_LOADED" ]]; then eval "$(forge zsh plugin)" fi } This allows me to load forge when I want to, instead of it being always initialized on startup. Most of the typesets are regular arrays, however, zsh-syntax-highlighting's ZSH_HIGHLIGHT_PATTERNS is an associative array, so we need to use -gA instead for that variable.
1 parent 16f54bb commit fbecc2a

3 files changed

Lines changed: 37 additions & 23 deletions

File tree

crates/forge_main/src/zsh/plugin.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ pub fn generate_zsh_plugin() -> Result<String> {
4444
output.push_str(&completions_str);
4545

4646
// Set environment variable to indicate plugin is loaded (with timestamp)
47-
output.push_str("\n_FORGE_PLUGIN_LOADED=$(date +%s)\n");
47+
// Use typeset -g so the variable is global even when eval'd inside a function
48+
// (e.g. lazy-loading plugin managers like zinit, zplug, zsh-defer)
49+
output.push_str("\ntypeset -g _FORGE_PLUGIN_LOADED=$(date +%s)\n");
4850

4951
Ok(output)
5052
}
@@ -55,7 +57,9 @@ pub fn generate_zsh_theme() -> Result<String> {
5557
super::normalize_script(include_str!("../../../../shell-plugin/forge.theme.zsh"));
5658

5759
// Set environment variable to indicate theme is loaded (with timestamp)
58-
content.push_str("\n_FORGE_THEME_LOADED=$(date +%s)\n");
60+
// Use typeset -g so the variable is global even when eval'd inside a function
61+
// (e.g. lazy-loading plugin managers like zinit, zplug, zsh-defer)
62+
content.push_str("\ntypeset -g _FORGE_THEME_LOADED=$(date +%s)\n");
5963

6064
Ok(content)
6165
}

shell-plugin/lib/config.zsh

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,51 @@
11
#!/usr/bin/env zsh
22

33
# Configuration variables for forge plugin
4-
# Using typeset to keep variables local to plugin scope and prevent public exposure
4+
# Using typeset -gh (global + hidden) so variables survive lazy-loading
5+
# from within a function scope (e.g. zinit, zplug, zsh-defer) while
6+
# staying hidden from `typeset` listings.
57

6-
typeset -h _FORGE_BIN="${FORGE_BIN:-forge}"
7-
typeset -h _FORGE_CONVERSATION_PATTERN=":"
8-
typeset -h _FORGE_MAX_COMMIT_DIFF="${FORGE_MAX_COMMIT_DIFF:-100000}"
9-
typeset -h _FORGE_DELIMITER='\s\s+'
10-
typeset -h _FORGE_PREVIEW_WINDOW="--preview-window=bottom:75%:wrap:border-sharp"
8+
typeset -gh _FORGE_BIN="${FORGE_BIN:-forge}"
9+
typeset -gh _FORGE_CONVERSATION_PATTERN=":"
10+
typeset -gh _FORGE_MAX_COMMIT_DIFF="${FORGE_MAX_COMMIT_DIFF:-100000}"
11+
typeset -gh _FORGE_DELIMITER='\s\s+'
12+
typeset -gh _FORGE_PREVIEW_WINDOW="--preview-window=bottom:75%:wrap:border-sharp"
1113

1214
# Detect bat command - use bat if available, otherwise fall back to cat
1315
if command -v bat &>/dev/null; then
14-
typeset -h _FORGE_CAT_CMD="bat --color=always --style=numbers,changes --line-range=:500"
16+
typeset -gh _FORGE_CAT_CMD="bat --color=always --style=numbers,changes --line-range=:500"
1517
else
16-
typeset -h _FORGE_CAT_CMD="cat"
18+
typeset -gh _FORGE_CAT_CMD="cat"
1719
fi
1820

1921
# Commands cache - loaded lazily on first use
20-
typeset -h _FORGE_COMMANDS=""
22+
typeset -gh _FORGE_COMMANDS=""
2123

2224
# Hidden variables to be used only via the ForgeCLI
23-
typeset -h _FORGE_CONVERSATION_ID
24-
typeset -h _FORGE_ACTIVE_AGENT
25+
typeset -gh _FORGE_CONVERSATION_ID
26+
typeset -gh _FORGE_ACTIVE_AGENT
2527

2628
# Previous conversation ID for :conversation - (like cd -)
27-
typeset -h _FORGE_PREVIOUS_CONVERSATION_ID
29+
typeset -gh _FORGE_PREVIOUS_CONVERSATION_ID
2830

2931
# Session-scoped model and provider overrides (set via :model / :m).
3032
# When non-empty, these are passed as --model / --provider to every forge
3133
# invocation for the lifetime of the current shell session.
32-
typeset -h _FORGE_SESSION_MODEL
33-
typeset -h _FORGE_SESSION_PROVIDER
34+
typeset -gh _FORGE_SESSION_MODEL
35+
typeset -gh _FORGE_SESSION_PROVIDER
3436

3537
# Session-scoped reasoning effort override (set via :reasoning-effort / :re).
3638
# When non-empty, exported as FORGE_REASONING__EFFORT for every forge invocation.
37-
typeset -h _FORGE_SESSION_REASONING_EFFORT
39+
typeset -gh _FORGE_SESSION_REASONING_EFFORT
3840

3941
# Terminal context capture settings
4042
# Master switch for terminal context capture (preexec/precmd hooks)
41-
typeset -h _FORGE_TERM_ENABLED="${FORGE_TERM_ENABLED:-true}"
43+
typeset -gh _FORGE_TERM_ENABLED="${FORGE_TERM_ENABLED:-true}"
4244
# Maximum number of commands to keep in the ring buffer (metadata: cmd + exit code)
43-
typeset -h _FORGE_TERM_MAX_COMMANDS="${FORGE_TERM_MAX_COMMANDS:-5}"
45+
typeset -gh _FORGE_TERM_MAX_COMMANDS="${FORGE_TERM_MAX_COMMANDS:-5}"
4446
# OSC 133 semantic prompt marker emission: "auto", "on", or "off"
45-
typeset -h _FORGE_TERM_OSC133="${FORGE_TERM_OSC133:-auto}"
47+
typeset -gh _FORGE_TERM_OSC133="${FORGE_TERM_OSC133:-auto}"
4648
# Ring buffer arrays for context capture
47-
typeset -ha _FORGE_TERM_COMMANDS=()
48-
typeset -ha _FORGE_TERM_EXIT_CODES=()
49-
typeset -ha _FORGE_TERM_TIMESTAMPS=()
49+
typeset -gha _FORGE_TERM_COMMANDS=()
50+
typeset -gha _FORGE_TERM_EXIT_CODES=()
51+
typeset -gha _FORGE_TERM_TIMESTAMPS=()

shell-plugin/lib/highlight.zsh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
# Syntax highlighting configuration for forge commands
44
# Style the conversation pattern with appropriate highlighting
55
# Keywords in yellow, rest in default white
6+
#
7+
# Use global declarations so we update the shared zsh-syntax-highlighting
8+
# collections even when sourced from within a function (lazy-loading plugin
9+
# managers). Patterns must remain an associative array because the pattern
10+
# highlighter stores regex => style entries in ZSH_HIGHLIGHT_PATTERNS.
11+
12+
typeset -gA ZSH_HIGHLIGHT_PATTERNS
13+
typeset -ga ZSH_HIGHLIGHT_HIGHLIGHTERS
614

715
# Style tagged files
816
ZSH_HIGHLIGHT_PATTERNS+=('@\[[^]]#\]' 'fg=cyan,bold')

0 commit comments

Comments
 (0)