pi-powerline-footer 0.2.21 → 0.2.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/git-status.ts +22 -52
- package/index.ts +15 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.2.23] - 2026-02-06
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Slash command autocomplete not appearing** — Custom editor created during `session_start` never received the autocomplete provider because pi v0.52.7 moved `setupAutocomplete()` to run after extensions load. The `handleInput` override now detects the missing provider on first keystroke, re-triggers `setEditorComponent` (which succeeds because the provider exists by then), and forwards the keystroke to the new editor. Users without editor-replacing extensions were unaffected.
|
|
9
|
+
|
|
10
|
+
## [0.2.22] - 2026-01-31
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **Detached HEAD flickering** — Git branch segment no longer oscillates between showing "detached" and hiding every 500ms when HEAD is detached
|
|
14
|
+
- Root cause: two competing branch detection methods (provider reads `.git/HEAD` → `"detached"`, extension runs `git branch --show-current` → empty/null) fought via a `??` fallback that leaked the provider value on every cache expiry
|
|
15
|
+
- Branch cache now returns stale value while refreshing instead of falling through to provider
|
|
16
|
+
- Detached HEAD now shows the short commit SHA (e.g., `abc1234 (detached)`) instead of bare "detached"
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- **Extracted `runGit` helper** — Consolidated duplicated process-spawning logic from `fetchGitBranch` and `fetchGitStatus` into a shared helper
|
|
20
|
+
- `fetchGitBranch` now distinguishes "not a git repo" (null, early exit) from "detached HEAD" (empty string, SHA lookup) — avoids spawning a wasteful second process for non-git directories
|
|
21
|
+
|
|
5
22
|
## [0.2.21] - 2026-01-31
|
|
6
23
|
|
|
7
24
|
### Changed
|
package/git-status.ts
CHANGED
|
@@ -60,12 +60,9 @@ function parseGitStatusOutput(output: string): { staged: number; unstaged: numbe
|
|
|
60
60
|
return { staged, unstaged, untracked };
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
* Fetch current git branch asynchronously
|
|
65
|
-
*/
|
|
66
|
-
async function fetchGitBranch(): Promise<string | null> {
|
|
63
|
+
function runGit(args: string[], timeoutMs = 200): Promise<string | null> {
|
|
67
64
|
return new Promise((resolve) => {
|
|
68
|
-
const proc = spawn("git",
|
|
65
|
+
const proc = spawn("git", args, {
|
|
69
66
|
stdio: ["ignore", "pipe", "pipe"],
|
|
70
67
|
});
|
|
71
68
|
|
|
@@ -84,67 +81,40 @@ async function fetchGitBranch(): Promise<string | null> {
|
|
|
84
81
|
});
|
|
85
82
|
|
|
86
83
|
proc.on("close", (code) => {
|
|
87
|
-
|
|
88
|
-
finish(null);
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
const branch = stdout.trim();
|
|
92
|
-
finish(branch || null); // Empty string means detached HEAD
|
|
84
|
+
finish(code === 0 ? stdout.trim() : null);
|
|
93
85
|
});
|
|
94
86
|
|
|
95
87
|
proc.on("error", () => {
|
|
96
88
|
finish(null);
|
|
97
89
|
});
|
|
98
90
|
|
|
99
|
-
// Timeout after 200ms
|
|
100
91
|
const timeoutId = setTimeout(() => {
|
|
101
92
|
proc.kill();
|
|
102
93
|
finish(null);
|
|
103
|
-
},
|
|
94
|
+
}, timeoutMs);
|
|
104
95
|
});
|
|
105
96
|
}
|
|
106
97
|
|
|
107
98
|
/**
|
|
108
|
-
* Fetch git
|
|
99
|
+
* Fetch current git branch asynchronously.
|
|
100
|
+
* For detached HEAD, returns the short commit SHA (matches provider's "detached" behavior).
|
|
109
101
|
*/
|
|
110
|
-
async function
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
let stdout = "";
|
|
117
|
-
let resolved = false;
|
|
118
|
-
|
|
119
|
-
const finish = (result: { staged: number; unstaged: number; untracked: number } | null) => {
|
|
120
|
-
if (resolved) return;
|
|
121
|
-
resolved = true;
|
|
122
|
-
clearTimeout(timeoutId);
|
|
123
|
-
resolve(result);
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
proc.stdout.on("data", (data) => {
|
|
127
|
-
stdout += data.toString();
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
proc.on("close", (code) => {
|
|
131
|
-
if (code !== 0) {
|
|
132
|
-
finish(null);
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
finish(parseGitStatusOutput(stdout));
|
|
136
|
-
});
|
|
102
|
+
async function fetchGitBranch(): Promise<string | null> {
|
|
103
|
+
const branch = await runGit(["branch", "--show-current"]);
|
|
104
|
+
if (branch === null) return null;
|
|
105
|
+
if (branch) return branch;
|
|
137
106
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
107
|
+
const sha = await runGit(["rev-parse", "--short", "HEAD"]);
|
|
108
|
+
return sha ? `${sha} (detached)` : "detached";
|
|
109
|
+
}
|
|
141
110
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Fetch git status asynchronously
|
|
113
|
+
*/
|
|
114
|
+
async function fetchGitStatus(): Promise<{ staged: number; unstaged: number; untracked: number } | null> {
|
|
115
|
+
const output = await runGit(["status", "--porcelain"], 500);
|
|
116
|
+
if (output === null) return null;
|
|
117
|
+
return parseGitStatusOutput(output);
|
|
148
118
|
}
|
|
149
119
|
|
|
150
120
|
/**
|
|
@@ -174,8 +144,8 @@ export function getCurrentBranch(providerBranch: string | null): string | null {
|
|
|
174
144
|
});
|
|
175
145
|
}
|
|
176
146
|
|
|
177
|
-
// Return
|
|
178
|
-
return cachedBranch
|
|
147
|
+
// Return stale cache while refreshing; only use provider before first fetch
|
|
148
|
+
return cachedBranch ? cachedBranch.branch : providerBranch;
|
|
179
149
|
}
|
|
180
150
|
|
|
181
151
|
/**
|
package/index.ts
CHANGED
|
@@ -550,14 +550,23 @@ export default function powerlineFooter(pi: ExtensionAPI) {
|
|
|
550
550
|
function setupCustomEditor(ctx: any) {
|
|
551
551
|
// Import CustomEditor dynamically and create wrapper
|
|
552
552
|
import("@mariozechner/pi-coding-agent").then(({ CustomEditor }) => {
|
|
553
|
-
|
|
553
|
+
let currentEditor: any = null;
|
|
554
|
+
let autocompleteFixed = false;
|
|
555
|
+
|
|
556
|
+
const editorFactory = (tui: any, editorTheme: any, keybindings: any) => {
|
|
554
557
|
// Create custom editor that overrides render for status bar below content
|
|
555
558
|
const editor = new CustomEditor(tui, editorTheme, keybindings);
|
|
559
|
+
currentEditor = editor;
|
|
556
560
|
|
|
557
|
-
// Override handleInput to dismiss welcome on first keypress
|
|
558
561
|
const originalHandleInput = editor.handleInput.bind(editor);
|
|
559
562
|
editor.handleInput = (data: string) => {
|
|
560
|
-
|
|
563
|
+
if (!autocompleteFixed && !(editor as any).autocompleteProvider) {
|
|
564
|
+
autocompleteFixed = true;
|
|
565
|
+
ctx.ui.setEditorComponent(editorFactory);
|
|
566
|
+
currentEditor?.handleInput(data);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
// Dismiss welcome overlay/header (use setTimeout to avoid re-entrancy)
|
|
561
570
|
setTimeout(() => dismissWelcome(ctx), 0);
|
|
562
571
|
originalHandleInput(data);
|
|
563
572
|
};
|
|
@@ -632,7 +641,9 @@ export default function powerlineFooter(pi: ExtensionAPI) {
|
|
|
632
641
|
};
|
|
633
642
|
|
|
634
643
|
return editor;
|
|
635
|
-
}
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
ctx.ui.setEditorComponent(editorFactory);
|
|
636
647
|
|
|
637
648
|
// Set up footer data provider access (needed for git branch, extension statuses)
|
|
638
649
|
// Status bar is rendered inside the editor override, footer is empty
|