Skip to content

Commit 30bf119

Browse files
committed
fix(inquirerer): use fixed max height for multiline input layout
- Use fixed max of 5 visible input lines for stable layout - Layout calculation and rendering now use the same max height - Show 'ln X/Y' indicator when multiline instead of scroll indicators - Keeps cursor visible with context when scrolling through input - Removes extra indicator rows that were stealing layout space
1 parent bf10167 commit 30bf119

1 file changed

Lines changed: 31 additions & 34 deletions

File tree

packages/inquirerer/src/ui/aicode.ts

Lines changed: 31 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -756,9 +756,10 @@ export class AICodeUI {
756756
lines.push(...conversationLines);
757757
}
758758

759-
// Fill remaining space (account for multiline input)
760-
const inputLineCount = this.lineEditor.lines.length;
761-
const reservedLines = 3 + inputLineCount; // separator + input lines + status
759+
// Fill remaining space (use fixed max input height for stable layout)
760+
// Max input area: 5 lines (or fewer if buffer is smaller)
761+
const maxVisibleInputLines = Math.min(5, this.lineEditor.lines.length);
762+
const reservedLines = 3 + maxVisibleInputLines; // separator + input lines + status
762763
while (lines.length < this.viewportHeight - reservedLines) {
763764
lines.push('');
764765
}
@@ -919,42 +920,40 @@ export class AICodeUI {
919920

920921
/**
921922
* Render the input lines (supports multiline with scrolling)
923+
* Uses a fixed max height (5 lines) to keep layout stable
922924
*/
923925
private renderInputLines(width: number): string[] {
924926
const lines: string[] = [];
925927
const inputText = this.getInput();
926928
const hasContent = inputText.length > 0;
929+
const totalLines = this.lineEditor.lines.length;
927930

928-
// Limit visible input lines to prevent overflow
929-
// Reserve space for: welcome/conversation, separator, status bar
930-
const maxInputLines = Math.min(this.lineEditor.lines.length, Math.max(3, this.viewportHeight - 10));
931+
// Fixed max visible input lines (must match layout calculation in render())
932+
const maxInputLines = 5;
933+
const visibleCount = Math.min(maxInputLines, totalLines);
931934

932935
// Calculate which lines to show (keep cursor line visible)
933936
const cursorLine = this.lineEditor.lineIndex;
934937
let startLine = 0;
935-
let endLine = this.lineEditor.lines.length;
936938

937-
if (this.lineEditor.lines.length > maxInputLines) {
938-
// Center the view around the cursor line
939-
const halfVisible = Math.floor(maxInputLines / 2);
940-
startLine = Math.max(0, cursorLine - halfVisible);
941-
endLine = startLine + maxInputLines;
942-
943-
// Adjust if we're near the end
944-
if (endLine > this.lineEditor.lines.length) {
945-
endLine = this.lineEditor.lines.length;
946-
startLine = Math.max(0, endLine - maxInputLines);
939+
if (totalLines > visibleCount) {
940+
// Keep cursor visible with some context
941+
if (cursorLine < 2) {
942+
startLine = 0;
943+
} else if (cursorLine > totalLines - 3) {
944+
startLine = totalLines - visibleCount;
945+
} else {
946+
startLine = cursorLine - 2;
947947
}
948948
}
949949

950-
// Show scroll indicator at top if there are hidden lines above
951-
if (startLine > 0) {
952-
lines.push(dim(` [${startLine} more lines above]`));
953-
}
950+
const endLine = Math.min(startLine + visibleCount, totalLines);
954951

955952
for (let idx = startLine; idx < endLine; idx++) {
956953
const line = this.lineEditor.lines[idx];
957-
const prefix = idx === 0 ? cyan('> ') : ' ';
954+
// Show > prefix on first visible line if it's line 0, otherwise show line number hint
955+
const isFirstLogicalLine = idx === 0;
956+
const prefix = isFirstLogicalLine ? cyan('> ') : ' ';
958957
let lineContent: string;
959958

960959
if (idx === this.lineEditor.lineIndex && !this.isStreaming) {
@@ -969,25 +968,23 @@ export class AICodeUI {
969968
lineContent = line;
970969
}
971970

972-
// Add send hint on last visible line if there's content and it's the actual last line
973-
if (idx === this.lineEditor.lines.length - 1 && hasContent) {
974-
const hint = dim(' ↵ send');
971+
// Add info on the last visible line
972+
if (idx === endLine - 1 && hasContent) {
973+
// Show line count if multiline, otherwise show send hint
974+
const info = totalLines > 1
975+
? dim(` ln ${cursorLine + 1}/${totalLines}`)
976+
: dim(' ↵ send');
975977
const lineWidth = displayWidth(prefix + lineContent);
976-
const hintWidth = displayWidth(hint);
977-
if (lineWidth + hintWidth < width - 2) {
978-
const padding = width - lineWidth - hintWidth - 2;
979-
lineContent = lineContent + ' '.repeat(Math.max(0, padding)) + hint;
978+
const infoWidth = displayWidth(info);
979+
if (lineWidth + infoWidth < width - 2) {
980+
const padding = width - lineWidth - infoWidth - 2;
981+
lineContent = lineContent + ' '.repeat(Math.max(0, padding)) + info;
980982
}
981983
}
982984

983985
lines.push(prefix + lineContent);
984986
}
985987

986-
// Show scroll indicator at bottom if there are hidden lines below
987-
if (endLine < this.lineEditor.lines.length) {
988-
lines.push(dim(` [${this.lineEditor.lines.length - endLine} more lines below]`));
989-
}
990-
991988
return lines;
992989
}
993990
}

0 commit comments

Comments
 (0)