Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 21 additions & 20 deletions src/gadgets/EditFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ Replaced 1 occurrence.
2 |
3 | const CONFIG = {
4 | debug: false,
> 5 | timeout: 1000,
< 5 | timeout: 1000,
6 | retries: 3,
7 | };

Expand Down Expand Up @@ -158,7 +158,7 @@ Replaced 2 occurrences.
1 | // API constants
2 | export const API_URL = "https://api.example.com";
3 |
> 4 | export const MAX_RETRIES = 3;
< 4 | export const MAX_RETRIES = 3;
5 | export const TIMEOUT = 5000;

--- AFTER ---
Expand All @@ -173,7 +173,7 @@ Replaced 2 occurrences.
9 | // Client constants
10 | export const CLIENT_URL = "https://client.example.com";
11 |
> 12 | export const MAX_RETRIES = 3;
< 12 | export const MAX_RETRIES = 3;
13 | export const CLIENT_TIMEOUT = 3000;

--- AFTER ---
Expand Down Expand Up @@ -206,7 +206,7 @@ Replaced 1 occurrence.
--- BEFORE ---
1 | {
2 | "name": "example",
> 3 | "enabled": false,
< 3 | "enabled": false,
4 | "count": 0
5 | }

Expand All @@ -232,7 +232,7 @@ Replaced 1 occurrence.
Inserted 1 line at line 1.

--- BEFORE (around line 1) ---
> 1 | import { foo } from 'bar';
< 1 | import { foo } from 'bar';
2 | import { baz } from 'qux';
3 |

Expand Down Expand Up @@ -265,7 +265,7 @@ Inserted 3 lines at line 10.
7 | }
8 |
9 | // Utils below
> 10 | function process(data: string) {
< 10 | function process(data: string) {
11 | return data.trim();
12 | }

Expand Down Expand Up @@ -333,7 +333,7 @@ Removed 1 line (line 3).
--- BEFORE ---
1 | import { foo } from 'foo';
2 | import { bar } from 'bar';
> 3 | import { unused } from 'unused';
< 3 | import { unused } from 'unused';
4 | import { baz } from 'baz';
5 |

Expand Down Expand Up @@ -366,12 +366,12 @@ Removed 6 lines (lines 5-10).
2 |
3 | export function keepThis() {}
4 |
> 5 | /** @deprecated */
> 6 | function oldFunc(x: number) {
> 7 | console.log('deprecated');
> 8 | return x * 2;
> 9 | }
> 10 |
< 5 | /** @deprecated */
< 6 | function oldFunc(x: number) {
< 7 | console.log('deprecated');
< 8 | return x * 2;
< 9 | }
< 10 |
11 | export function keepThisToo() {}

--- AFTER ---
Expand Down Expand Up @@ -401,9 +401,9 @@ Removed 3 lines (lines 2-4).

--- BEFORE ---
1 | {
> 2 | "// NOTE": "Remove this later",
> 3 | "// TODO": "Clean up config",
> 4 | "// FIXME": "Legacy value",
< 2 | "// NOTE": "Remove this later",
< 3 | "// TODO": "Clean up config",
< 4 | "// FIXME": "Legacy value",
5 | "enabled": true,
6 | "timeout": 5000
7 | }
Expand Down Expand Up @@ -481,7 +481,7 @@ Removed 3 lines (lines 2-4).
beforeContexts.push({
startLine: match.startLine,
endLine: match.endLine,
context: this.formatContext(originalLines, match.startLine, match.endLine, 5),
context: this.formatContext(originalLines, match.startLine, match.endLine, 5, '<'),
});
}

Expand Down Expand Up @@ -533,7 +533,7 @@ Removed 3 lines (lines 2-4).
const effectiveLine = Math.min(line, lines.length + 1);

// Store before context
const beforeContext = this.formatContext(lines, effectiveLine, effectiveLine, 3);
const beforeContext = this.formatContext(lines, effectiveLine, effectiveLine, 3, '<');

// Insert lines
const newLines = [
Expand Down Expand Up @@ -611,7 +611,7 @@ Removed 3 lines (lines 2-4).
const removedCount = effectiveEndLine - startLine + 1;

// Store before context with highlight on lines to remove
const beforeContext = this.formatContext(lines, startLine, effectiveEndLine, 3);
const beforeContext = this.formatContext(lines, startLine, effectiveEndLine, 3, '<');

// Remove lines
const newLines = [...lines.slice(0, startLine - 1), ...lines.slice(effectiveEndLine)];
Expand Down Expand Up @@ -749,6 +749,7 @@ Removed 3 lines (lines 2-4).
startLine: number,
endLine: number,
contextLines = 5,
editMarker = '>',
): string {
const rangeStart = Math.max(0, startLine - 1 - contextLines);
const rangeEnd = Math.min(lines.length, endLine + contextLines);
Expand All @@ -757,7 +758,7 @@ Removed 3 lines (lines 2-4).
for (let i = rangeStart; i < rangeEnd; i++) {
const lineNum = i + 1;
const isEdited = lineNum >= startLine && lineNum <= endLine;
const marker = isEdited ? '>' : ' ';
const marker = isEdited ? editMarker : ' ';
const paddedNum = String(lineNum).padStart(4);
result.push(`${marker}${paddedNum} | ${lines[i]}`);
}
Expand Down
69 changes: 62 additions & 7 deletions src/gadgets/tmux.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,36 @@ class TmuxControlClient {
};
}

/**
* Get session status - checks if command has exited and returns exit code.
* Combines checkExitMarker and isPaneDead for comprehensive detection.
*/
async getSessionStatus(
windowName: string,
): Promise<{ status: 'running' | 'exited' | 'not_found'; exitCode?: number }> {
// Check if window exists first
if (!(await this.windowExists(windowName))) {
return { status: 'not_found' };
}

// Method 1: Check for exit marker in streamed output (most reliable)
const markerResult = this.checkExitMarker(windowName);
if (markerResult.exited) {
return { status: 'exited', exitCode: markerResult.exitCode };
}

// Method 2: Check if pane is dead via tmux
const paneId = this.windowToPaneId.get(windowName);
if (paneId) {
const { dead, exitCode } = await this.isPaneDead(paneId);
if (dead) {
return { status: 'exited', exitCode };
}
}

return { status: 'running' };
}

/**
* Get buffered output for a window
*/
Expand Down Expand Up @@ -732,8 +762,19 @@ Commands are interpreted by bash, so pipes, &&, ||, redirects, and globs all wor
session: 'npm-install',
lines: 25,
},
output: 'session=npm-install lines=25\n\nadded 874 packages in 45s',
comment: 'Check output from running session',
output: 'session=npm-install status=running lines=25\n\nadded 874 packages in 45s',
comment: 'Check output from running session - status=running means command still executing',
},
{
params: {
action: 'capture',
comment: 'Checking if tests completed',
session: 'test-run',
lines: 50,
},
output:
'session=test-run status=exited exit_code=0 lines=50\n\n✓ 15 tests passed\n✓ All tests completed',
comment: 'Capture shows command finished - status=exited with exit code',
},
{
params: {
Expand Down Expand Up @@ -914,21 +955,35 @@ Commands are interpreted by bash, so pipes, &&, ||, redirects, and globs all wor
const client = await getControlClient();
const lines = params.lines ?? 25;

if (!(await client.windowExists(params.session))) {
// Check session status (existence + exit detection)
const sessionStatus = await client.getSessionStatus(params.session);

if (sessionStatus.status === 'not_found') {
return `session=${params.session} status=error\n\nSession '${params.session}' does not exist`;
}

// Try streamed output first, then capture-pane
// Get output (try streamed output first, then capture-pane)
let output = client.getOutput(params.session);
if (!output.trim()) {
output = await client.capturePaneOutput(params.session, lines);
}

// Take last N lines
// Take last N lines and clean up
const outputLines = output.split('\n');
const captured = outputLines.slice(-lines).join('\n').trim();
let captured = outputLines.slice(-lines).join('\n').trim();

// Clean exit marker from output if present
captured = captured
.replace(new RegExp(`${EXIT_MARKER_PREFIX}\\d+${EXIT_MARKER_SUFFIX}\\s*`), '')
.replace(/\nPane is dead \([^)]+\)\s*$/, '')
.trim();

// Report status with exit code if exited
if (sessionStatus.status === 'exited') {
return `session=${params.session} status=exited exit_code=${sessionStatus.exitCode} lines=${lines}\n\n${captured || '(no output)'}`;
}

return `session=${params.session} lines=${lines}\n\n${captured || '(no output yet)'}`;
return `session=${params.session} status=running lines=${lines}\n\n${captured || '(no output yet)'}`;
}

private async handleList(): Promise<string> {
Expand Down
61 changes: 58 additions & 3 deletions src/utils/interactive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,42 @@ function horizontalLine(): string {
}

/**
* Display EditFile params with search/replace as separate colored blocks.
* Display EditFile params with mode-specific formatting.
*/
function displayEditFileParams(params: Record<string, unknown>): void {
// File path
// Comment (rationale for the edit)
if (params.comment) {
console.log(chalk.dim('Comment: ') + chalk.white(String(params.comment)));
}

// File path and mode
if (params.filePath) {
console.log(chalk.dim('File: ') + chalk.cyan(String(params.filePath)));
const modeStr = params.mode ? chalk.yellow(`[${params.mode}]`) : '';
console.log(`${chalk.dim('File: ')}${chalk.cyan(String(params.filePath))} ${modeStr}`);
console.log(horizontalLine());
}

// Mode-specific display
switch (params.mode) {
case 'search_replace':
displaySearchReplaceMode(params);
break;
case 'insert_at_line':
displayInsertAtLineMode(params);
break;
case 'remove_lines':
displayRemoveLinesMode(params);
break;
default:
// Fallback for unknown mode - show as search_replace for backwards compat
displaySearchReplaceMode(params);
}
}

/**
* Display search_replace mode params.
*/
function displaySearchReplaceMode(params: Record<string, unknown>): void {
// Search block (what's being replaced) - red
if (params.search !== undefined) {
console.log(chalk.red('━ Search (to replace):'));
Expand All @@ -36,6 +63,34 @@ function displayEditFileParams(params: Record<string, unknown>): void {
}
}

/**
* Display insert_at_line mode params.
*/
function displayInsertAtLineMode(params: Record<string, unknown>): void {
const lineNum = params.line !== undefined ? String(params.line) : '?';
console.log(chalk.green(`+ Insert BEFORE line ${lineNum}:`));

if (params.content !== undefined) {
for (const line of String(params.content).split('\n')) {
console.log(chalk.green(line));
}
}
}

/**
* Display remove_lines mode params.
*/
function displayRemoveLinesMode(params: Record<string, unknown>): void {
const startLine = params.startLine !== undefined ? String(params.startLine) : '?';
const endLine = params.endLine !== undefined ? String(params.endLine) : '?';

if (startLine === endLine) {
console.log(chalk.red(`━ Remove line ${startLine}`));
} else {
console.log(chalk.red(`━ Remove lines ${startLine}-${endLine}`));
}
}

/**
* Display default params as JSON.
*/
Expand Down