mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-06 07:29:42 +02:00
Merge pull request #331 from dignfei/fix/shell-history-performance
fix(terminal): bound shell history replay
This commit is contained in:
+51
-31
@@ -1568,6 +1568,8 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
|
||||
private reconnectGuard: Set<string> = new Set();
|
||||
|
||||
private trueColorConfigured = false;
|
||||
/** tmux 3.7+ can resize pane history after creation; older releases cannot. */
|
||||
private liveHistoryResizeSupported: boolean | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -1586,6 +1588,26 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
|
||||
return tmuxCommand(this.tmuxSocket);
|
||||
}
|
||||
|
||||
private supportsLiveHistoryResize(): boolean {
|
||||
if (this.liveHistoryResizeSupported !== null) return this.liveHistoryResizeSupported;
|
||||
|
||||
try {
|
||||
const output = execSync(`${this.tmux()} -V`, {
|
||||
encoding: 'utf8',
|
||||
timeout: EXEC_TIMEOUT_MS,
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
});
|
||||
const match = output.match(/(?:^|\D)(\d+)\.(\d+)/);
|
||||
const major = match ? Number(match[1]) : 0;
|
||||
const minor = match ? Number(match[2]) : 0;
|
||||
this.liveHistoryResizeSupported = major > 3 || (major === 3 && minor >= 7);
|
||||
} catch {
|
||||
// Unknown versions take the legacy path required by tmux <3.7.
|
||||
this.liveHistoryResizeSupported = false;
|
||||
}
|
||||
return this.liveHistoryResizeSupported;
|
||||
}
|
||||
|
||||
// Load saved sessions from disk (NEVER called in test mode)
|
||||
private loadSessions(): void {
|
||||
if (IS_TEST_MODE) return;
|
||||
@@ -1926,7 +1948,16 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
|
||||
// launched in TMUX_LAUNCH_CWD (/tmp) rather than the real workingDir: a FUSE/rclone
|
||||
// mount that isn't ready yet makes `getcwd` fail and breaks the spawn (see #110). The
|
||||
// pane cd's into workingDir below via respawn-pane.
|
||||
execSync(`${this.tmux()} new-session -ds "${muxName}" -c ${TMUX_LAUNCH_CWD}`, {
|
||||
// tmux <3.7 allocates history only at pane creation, so its global default
|
||||
// must be set immediately BEFORE new-session. tmux 3.7+ can resize a pane
|
||||
// after creation; target only the new session there because changing the
|
||||
// global option can resize (and when lowered, trim) unrelated live panes.
|
||||
const safeHistoryLimit =
|
||||
Number.isSafeInteger(historyLimit) && historyLimit > 0 ? Math.trunc(historyLimit) : DEFAULT_TMUX_HISTORY_LIMIT;
|
||||
const createSessionCommand = this.supportsLiveHistoryResize()
|
||||
? `${this.tmux()} new-session -ds "${muxName}" -c ${TMUX_LAUNCH_CWD} \\; set-option -t "${muxName}" history-limit ${safeHistoryLimit}`
|
||||
: `${this.tmux()} set-option -g history-limit ${safeHistoryLimit} \\; new-session -ds "${muxName}" -c ${TMUX_LAUNCH_CWD} \\; set-option -t "${muxName}" history-limit ${safeHistoryLimit}`;
|
||||
execSync(createSessionCommand, {
|
||||
cwd: TMUX_LAUNCH_CWD,
|
||||
timeout: EXEC_TIMEOUT_MS,
|
||||
stdio: 'ignore',
|
||||
@@ -1991,16 +2022,6 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
|
||||
.catch(() => {
|
||||
/* Already set globally as fallback */
|
||||
}),
|
||||
// Raise tmux scrollback from its 2000-line default so re-attach preserves
|
||||
// more context. Intentionally exceeds the xterm-side DEFAULT_SCROLLBACK (50k
|
||||
// in constants.js), which stays lower to protect browser/mobile memory.
|
||||
execAsync(`${this.tmux()} set-option -t "${muxName}" history-limit ${historyLimit}`, {
|
||||
timeout: EXEC_TIMEOUT_MS,
|
||||
})
|
||||
.then(() => {})
|
||||
.catch(() => {
|
||||
/* Non-critical — falls back to tmux default */
|
||||
}),
|
||||
];
|
||||
|
||||
// Enable 24-bit true color passthrough — server-wide, set once per lifetime
|
||||
@@ -2124,7 +2145,6 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
|
||||
resumeSessionId,
|
||||
envOverrides,
|
||||
effort,
|
||||
historyLimit = DEFAULT_TMUX_HISTORY_LIMIT,
|
||||
remote,
|
||||
docker,
|
||||
name,
|
||||
@@ -2135,16 +2155,6 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
|
||||
|
||||
if (!isValidMuxName(muxName) || !isValidPath(workingDir)) return null;
|
||||
|
||||
// Re-apply the configured tmux history-limit after respawn (kept in sync
|
||||
// with the live setting via setHistoryLimit()).
|
||||
if (!IS_TEST_MODE) {
|
||||
await execAsync(`${this.tmux()} set-option -t ${shellescape(muxName)} history-limit ${historyLimit}`, {
|
||||
timeout: EXEC_TIMEOUT_MS,
|
||||
}).catch(() => {
|
||||
/* Non-critical — keeps existing tmux history-limit */
|
||||
});
|
||||
}
|
||||
|
||||
// Resolve CLI binary directory based on mode
|
||||
const { pathExport } = this.buildPathExport(mode);
|
||||
|
||||
@@ -3003,9 +3013,9 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a tmux history-limit to all tracked sessions (e.g. when the user
|
||||
* changes the terminal-history setting). Invalid limits fall back to the
|
||||
* default. Best-effort per session.
|
||||
* Apply a tmux history limit. tmux 3.7+ safely targets tracked live sessions;
|
||||
* older releases can only change the global default for future panes. Invalid
|
||||
* limits fall back to the default.
|
||||
*/
|
||||
async setHistoryLimit(limit: number): Promise<void> {
|
||||
const safeLimit = Number.isSafeInteger(limit) && limit > 0 ? Math.trunc(limit) : DEFAULT_TMUX_HISTORY_LIMIT;
|
||||
@@ -3014,12 +3024,22 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
|
||||
return;
|
||||
}
|
||||
|
||||
const updates = Array.from(this.sessions.values()).map((session) =>
|
||||
execAsync(`${this.tmux()} set-option -t ${shellescape(session.muxName)} history-limit ${safeLimit}`, {
|
||||
timeout: EXEC_TIMEOUT_MS,
|
||||
})
|
||||
);
|
||||
await Promise.allSettled(updates);
|
||||
if (this.supportsLiveHistoryResize()) {
|
||||
const updates = Array.from(this.sessions.values()).map((session) =>
|
||||
execAsync(`${this.tmux()} set-option -t ${shellescape(session.muxName)} history-limit ${safeLimit}`, {
|
||||
timeout: EXEC_TIMEOUT_MS,
|
||||
})
|
||||
);
|
||||
await Promise.allSettled(updates);
|
||||
return;
|
||||
}
|
||||
|
||||
await execAsync(`${this.tmux()} set-option -g history-limit ${safeLimit}`, {
|
||||
timeout: EXEC_TIMEOUT_MS,
|
||||
}).catch(() => {
|
||||
// No tmux server yet is fine: legacy createSession sets the same default
|
||||
// immediately before it creates the first pane.
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user