Skip to content

Commit f0901f9

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 58d0df9 commit f0901f9

3 files changed

Lines changed: 32 additions & 18 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: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,42 @@
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 fd command - Ubuntu/Debian use 'fdfind', others use 'fd'
13-
typeset -h _FORGE_FD_CMD="$(command -v fdfind 2>/dev/null || command -v fd 2>/dev/null || echo 'fd')"
15+
typeset -gh _FORGE_FD_CMD="$(command -v fdfind 2>/dev/null || command -v fd 2>/dev/null || echo 'fd')"
1416

1517
# Detect bat command - use bat if available, otherwise fall back to cat
1618
if command -v bat &>/dev/null; then
17-
typeset -h _FORGE_CAT_CMD="bat --color=always --style=numbers,changes --line-range=:500"
19+
typeset -gh _FORGE_CAT_CMD="bat --color=always --style=numbers,changes --line-range=:500"
1820
else
19-
typeset -h _FORGE_CAT_CMD="cat"
21+
typeset -gh _FORGE_CAT_CMD="cat"
2022
fi
2123

2224
# Commands cache - loaded lazily on first use
23-
typeset -h _FORGE_COMMANDS=""
25+
typeset -gh _FORGE_COMMANDS=""
2426

2527
# Hidden variables to be used only via the ForgeCLI
26-
typeset -h _FORGE_CONVERSATION_ID
27-
typeset -h _FORGE_ACTIVE_AGENT
28+
typeset -gh _FORGE_CONVERSATION_ID
29+
typeset -gh _FORGE_ACTIVE_AGENT
2830

2931
# Previous conversation ID for :conversation - (like cd -)
30-
typeset -h _FORGE_PREVIOUS_CONVERSATION_ID
32+
typeset -gh _FORGE_PREVIOUS_CONVERSATION_ID
3133

3234
# Session-scoped model and provider overrides (set via :model / :m).
3335
# When non-empty, these are passed as --model / --provider to every forge
3436
# invocation for the lifetime of the current shell session.
35-
typeset -h _FORGE_SESSION_MODEL
36-
typeset -h _FORGE_SESSION_PROVIDER
37+
typeset -gh _FORGE_SESSION_MODEL
38+
typeset -gh _FORGE_SESSION_PROVIDER
3739

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

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)