pi-zentui 0.2.1 → 0.2.2

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.
@@ -2,6 +2,7 @@ import { execFile } from "node:child_process";
2
2
  import { promisify } from "node:util";
3
3
 
4
4
  const execFileAsync = promisify(execFile);
5
+ const GIT_COMMAND_TIMEOUT_MS = 2_000;
5
6
 
6
7
  export type GitStatusSummary = {
7
8
  branch?: string;
@@ -88,10 +89,14 @@ export function parseGitStatusPorcelain(stdoutText: string, hasStash: boolean):
88
89
  export async function readGitStatus(cwd: string): Promise<GitStatusSummary> {
89
90
  try {
90
91
  const [{ stdout: statusStdout }, stashResult] = await Promise.all([
91
- execFileAsync("git", ["status", "--porcelain=2", "--branch"], { cwd }),
92
- execFileAsync("git", ["rev-parse", "--verify", "--quiet", "refs/stash"], { cwd }).catch(
93
- () => ({ stdout: "" }),
94
- ),
92
+ execFileAsync("git", ["status", "--porcelain=2", "--branch"], {
93
+ cwd,
94
+ timeout: GIT_COMMAND_TIMEOUT_MS,
95
+ }),
96
+ execFileAsync("git", ["rev-parse", "--verify", "--quiet", "refs/stash"], {
97
+ cwd,
98
+ timeout: GIT_COMMAND_TIMEOUT_MS,
99
+ }).catch(() => ({ stdout: "" })),
95
100
  ]);
96
101
  const stdoutText = typeof statusStdout === "string" ? statusStdout : String(statusStdout);
97
102
  const stashStdout =
@@ -18,7 +18,12 @@ import {
18
18
  } from "./config";
19
19
  import { installFooter } from "./footer";
20
20
  import { emptyGitStatus, readGitStatus } from "./git";
21
- import { type StopProjectRefreshInterval, startProjectRefreshInterval } from "./project-refresh";
21
+ import {
22
+ type ScheduleProjectRefreshOptions,
23
+ type StopProjectRefreshInterval,
24
+ createProjectRefreshScheduler,
25
+ startProjectRefreshInterval,
26
+ } from "./project-refresh";
22
27
  import { readRuntimeInfo } from "./runtime";
23
28
  import { installSelectorBorderStyle } from "./selector-border";
24
29
  import { registerZentuiSettingsCommand } from "./settings-command";
@@ -58,8 +63,6 @@ export default function (pi: ExtensionAPI) {
58
63
  let editorInstallMode: EditorInstallMode = "none";
59
64
  let wrappedEditorFactory: EditorFactory | undefined;
60
65
  let prototypePatchesInstalled = false;
61
- let projectRefreshInFlight = false;
62
- let projectRefreshPending = false;
63
66
 
64
67
  const refresh = () => requestFooterRender?.();
65
68
  const getActiveTheme = () => activeTheme;
@@ -77,22 +80,9 @@ export default function (pi: ExtensionAPI) {
77
80
  state.runtime = runtime;
78
81
  };
79
82
 
80
- const scheduleProjectRefresh = (ctx: ExtensionContext) => {
81
- if (projectRefreshInFlight) {
82
- projectRefreshPending = true;
83
- return;
84
- }
85
-
86
- projectRefreshInFlight = true;
87
- void refreshProjectState(ctx).finally(() => {
88
- projectRefreshInFlight = false;
89
- refresh();
90
- if (projectRefreshPending) {
91
- projectRefreshPending = false;
92
- scheduleProjectRefresh(ctx);
93
- }
94
- });
95
- };
83
+ const projectRefreshScheduler = createProjectRefreshScheduler(refreshProjectState, refresh);
84
+ const scheduleProjectRefresh = (ctx: ExtensionContext, options?: ScheduleProjectRefreshOptions) =>
85
+ projectRefreshScheduler.schedule(ctx, options);
96
86
 
97
87
  const refreshInteractiveState = (ctx: ExtensionContext, project = false) => {
98
88
  if (!ctx.hasUI) return;
@@ -104,8 +94,7 @@ export default function (pi: ExtensionAPI) {
104
94
  const stopProjectRefresh = () => {
105
95
  stopRefreshInterval();
106
96
  stopRefreshInterval = () => {};
107
- projectRefreshInFlight = false;
108
- projectRefreshPending = false;
97
+ projectRefreshScheduler.stop();
109
98
  };
110
99
 
111
100
  const installPrototypePatches = () => {
@@ -213,7 +202,7 @@ export default function (pi: ExtensionAPI) {
213
202
  stopRefreshInterval = startProjectRefreshInterval(currentConfig.projectRefreshIntervalMs, () =>
214
203
  scheduleProjectRefresh(ctx),
215
204
  );
216
- scheduleProjectRefresh(ctx);
205
+ scheduleProjectRefresh(ctx, { force: true });
217
206
  refresh();
218
207
  };
219
208
 
@@ -1,5 +1,16 @@
1
1
  export type StopProjectRefreshInterval = () => void;
2
2
 
3
+ export type ScheduleProjectRefreshOptions = {
4
+ force?: boolean;
5
+ };
6
+
7
+ export type ProjectRefreshScheduler<T> = {
8
+ schedule: (target: T, options?: ScheduleProjectRefreshOptions) => void;
9
+ stop: () => void;
10
+ };
11
+
12
+ export const PROJECT_REFRESH_THROTTLE_MS = 5_000;
13
+
3
14
  export function startProjectRefreshInterval(
4
15
  intervalMs: number,
5
16
  refresh: () => void,
@@ -11,3 +22,83 @@ export function startProjectRefreshInterval(
11
22
 
12
23
  return () => clearInterval(timer);
13
24
  }
25
+
26
+ export function createProjectRefreshScheduler<T>(
27
+ refresh: (target: T) => Promise<void>,
28
+ afterRefresh: () => void,
29
+ throttleMs = PROJECT_REFRESH_THROTTLE_MS,
30
+ ): ProjectRefreshScheduler<T> {
31
+ let refreshInFlight = false;
32
+ let refreshPending = false;
33
+ let pendingTarget: T | undefined;
34
+ let delayedRefresh: ReturnType<typeof setTimeout> | undefined;
35
+ let lastRefreshStartedAt: number | undefined;
36
+ let generation = 0;
37
+
38
+ const clearDelayedRefresh = () => {
39
+ if (!delayedRefresh) return;
40
+ clearTimeout(delayedRefresh);
41
+ delayedRefresh = undefined;
42
+ };
43
+
44
+ const runRefresh = (target: T) => {
45
+ clearDelayedRefresh();
46
+ if (refreshInFlight) {
47
+ refreshPending = true;
48
+ pendingTarget = target;
49
+ return;
50
+ }
51
+
52
+ const currentGeneration = generation;
53
+ refreshInFlight = true;
54
+ lastRefreshStartedAt = Date.now();
55
+ void refresh(target)
56
+ .catch(() => undefined)
57
+ .finally(() => {
58
+ if (currentGeneration !== generation) return;
59
+ refreshInFlight = false;
60
+ afterRefresh();
61
+ if (refreshPending) {
62
+ refreshPending = false;
63
+ const nextTarget = pendingTarget ?? target;
64
+ pendingTarget = undefined;
65
+ schedule(nextTarget);
66
+ }
67
+ });
68
+ };
69
+
70
+ const schedule = (target: T, options: ScheduleProjectRefreshOptions = {}) => {
71
+ if (options.force || throttleMs <= 0 || lastRefreshStartedAt === undefined) {
72
+ runRefresh(target);
73
+ return;
74
+ }
75
+
76
+ const delayMs = Math.max(0, throttleMs - (Date.now() - lastRefreshStartedAt));
77
+ if (delayMs === 0) {
78
+ runRefresh(target);
79
+ return;
80
+ }
81
+
82
+ pendingTarget = target;
83
+ if (delayedRefresh) return;
84
+ delayedRefresh = setTimeout(() => {
85
+ delayedRefresh = undefined;
86
+ const nextTarget = pendingTarget ?? target;
87
+ pendingTarget = undefined;
88
+ runRefresh(nextTarget);
89
+ }, delayMs);
90
+ delayedRefresh.unref?.();
91
+ };
92
+
93
+ return {
94
+ schedule,
95
+ stop() {
96
+ generation += 1;
97
+ clearDelayedRefresh();
98
+ refreshInFlight = false;
99
+ refreshPending = false;
100
+ pendingTarget = undefined;
101
+ lastRefreshStartedAt = undefined;
102
+ },
103
+ };
104
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-zentui",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "A Starship-inspired statusline and Opencode-style TUI for Pi.",
5
5
  "type": "module",
6
6
  "license": "MIT",