pi-zentui 0.2.0 → 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.
package/extensions/zentui/git.ts
CHANGED
|
@@ -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"], {
|
|
92
|
-
|
|
93
|
-
|
|
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 {
|
|
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
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
+
}
|