mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-20 03:00:29 +00:00
fix: align input editor with message content padding
Adds paddingX option to Editor component and hardcodes paddingX: 1 in coding-agent editors so the cursor/text aligns with chat message content.
This commit is contained in:
parent
20f5fcc79d
commit
48ea444bc4
7 changed files with 48 additions and 16 deletions
|
|
@ -15,6 +15,7 @@
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed crash during auto-compaction when summarization fails (e.g., quota exceeded). Now displays error message instead of crashing ([#792](https://github.com/badlogic/pi-mono/issues/792))
|
- Fixed crash during auto-compaction when summarization fails (e.g., quota exceeded). Now displays error message instead of crashing ([#792](https://github.com/badlogic/pi-mono/issues/792))
|
||||||
|
- Input editor now aligns with message content padding ([#791](https://github.com/badlogic/pi-mono/pull/791) by [@ferologics](https://github.com/ferologics))
|
||||||
- Fixed `--no-extensions` flag not preventing extension discovery ([#776](https://github.com/badlogic/pi-mono/issues/776))
|
- Fixed `--no-extensions` flag not preventing extension discovery ([#776](https://github.com/badlogic/pi-mono/issues/776))
|
||||||
- Fixed extension messages rendering twice on startup when `pi.sendMessage({ display: true })` is called during `session_start` ([#765](https://github.com/badlogic/pi-mono/pull/765) by [@dannote](https://github.com/dannote))
|
- Fixed extension messages rendering twice on startup when `pi.sendMessage({ display: true })` is called during `session_start` ([#765](https://github.com/badlogic/pi-mono/pull/765) by [@dannote](https://github.com/dannote))
|
||||||
- Fixed `PI_CODING_AGENT_DIR` env var not expanding tilde (`~`) to home directory ([#778](https://github.com/badlogic/pi-mono/pull/778) by [@aliou](https://github.com/aliou))
|
- Fixed `PI_CODING_AGENT_DIR` env var not expanding tilde (`~`) to home directory ([#778](https://github.com/badlogic/pi-mono/pull/778) by [@aliou](https://github.com/aliou))
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Editor, type EditorTheme, type TUI } from "@mariozechner/pi-tui";
|
import { Editor, type EditorOptions, type EditorTheme, type TUI } from "@mariozechner/pi-tui";
|
||||||
import type { AppAction, KeybindingsManager } from "../../../core/keybindings.js";
|
import type { AppAction, KeybindingsManager } from "../../../core/keybindings.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -15,8 +15,8 @@ export class CustomEditor extends Editor {
|
||||||
/** Handler for extension-registered shortcuts. Returns true if handled. */
|
/** Handler for extension-registered shortcuts. Returns true if handled. */
|
||||||
public onExtensionShortcut?: (data: string) => boolean;
|
public onExtensionShortcut?: (data: string) => boolean;
|
||||||
|
|
||||||
constructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) {
|
constructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?: EditorOptions) {
|
||||||
super(tui, theme);
|
super(tui, theme, options);
|
||||||
this.keybindings = keybindings;
|
this.keybindings = keybindings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,15 @@ import { spawnSync } from "node:child_process";
|
||||||
import * as fs from "node:fs";
|
import * as fs from "node:fs";
|
||||||
import * as os from "node:os";
|
import * as os from "node:os";
|
||||||
import * as path from "node:path";
|
import * as path from "node:path";
|
||||||
import { Container, Editor, getEditorKeybindings, Spacer, Text, type TUI } from "@mariozechner/pi-tui";
|
import {
|
||||||
|
Container,
|
||||||
|
Editor,
|
||||||
|
type EditorOptions,
|
||||||
|
getEditorKeybindings,
|
||||||
|
Spacer,
|
||||||
|
Text,
|
||||||
|
type TUI,
|
||||||
|
} from "@mariozechner/pi-tui";
|
||||||
import type { KeybindingsManager } from "../../../core/keybindings.js";
|
import type { KeybindingsManager } from "../../../core/keybindings.js";
|
||||||
import { getEditorTheme, theme } from "../theme/theme.js";
|
import { getEditorTheme, theme } from "../theme/theme.js";
|
||||||
import { DynamicBorder } from "./dynamic-border.js";
|
import { DynamicBorder } from "./dynamic-border.js";
|
||||||
|
|
@ -27,6 +35,7 @@ export class ExtensionEditorComponent extends Container {
|
||||||
prefill: string | undefined,
|
prefill: string | undefined,
|
||||||
onSubmit: (value: string) => void,
|
onSubmit: (value: string) => void,
|
||||||
onCancel: () => void,
|
onCancel: () => void,
|
||||||
|
options?: EditorOptions,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
|
@ -44,7 +53,7 @@ export class ExtensionEditorComponent extends Container {
|
||||||
this.addChild(new Spacer(1));
|
this.addChild(new Spacer(1));
|
||||||
|
|
||||||
// Create editor
|
// Create editor
|
||||||
this.editor = new Editor(tui, getEditorTheme());
|
this.editor = new Editor(tui, getEditorTheme(), options);
|
||||||
if (prefill) {
|
if (prefill) {
|
||||||
this.editor.setText(prefill);
|
this.editor.setText(prefill);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -241,7 +241,7 @@ export class InteractiveMode {
|
||||||
this.statusContainer = new Container();
|
this.statusContainer = new Container();
|
||||||
this.widgetContainer = new Container();
|
this.widgetContainer = new Container();
|
||||||
this.keybindings = KeybindingsManager.create();
|
this.keybindings = KeybindingsManager.create();
|
||||||
this.defaultEditor = new CustomEditor(this.ui, getEditorTheme(), this.keybindings);
|
this.defaultEditor = new CustomEditor(this.ui, getEditorTheme(), this.keybindings, { paddingX: 1 });
|
||||||
this.editor = this.defaultEditor;
|
this.editor = this.defaultEditor;
|
||||||
this.editorContainer = new Container();
|
this.editorContainer = new Container();
|
||||||
this.editorContainer.addChild(this.editor as Component);
|
this.editorContainer.addChild(this.editor as Component);
|
||||||
|
|
@ -1150,6 +1150,7 @@ export class InteractiveMode {
|
||||||
this.hideExtensionEditor();
|
this.hideExtensionEditor();
|
||||||
resolve(undefined);
|
resolve(undefined);
|
||||||
},
|
},
|
||||||
|
{ paddingX: 1 },
|
||||||
);
|
);
|
||||||
|
|
||||||
this.editorContainer.clear();
|
this.editorContainer.clear();
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `EditorOptions` with optional `paddingX` for horizontal content padding ([#791](https://github.com/badlogic/pi-mono/pull/791) by [@ferologics](https://github.com/ferologics))
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Hardware cursor is now disabled by default for better terminal compatibility. Set `PI_HARDWARE_CURSOR=1` to enable (replaces `PI_NO_HARDWARE_CURSOR=1` which disabled it).
|
- Hardware cursor is now disabled by default for better terminal compatibility. Set `PI_HARDWARE_CURSOR=1` to enable (replaces `PI_NO_HARDWARE_CURSOR=1` which disabled it).
|
||||||
|
|
|
||||||
|
|
@ -242,6 +242,10 @@ export interface EditorTheme {
|
||||||
selectList: SelectListTheme;
|
selectList: SelectListTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EditorOptions {
|
||||||
|
paddingX?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class Editor implements Component, Focusable {
|
export class Editor implements Component, Focusable {
|
||||||
private state: EditorState = {
|
private state: EditorState = {
|
||||||
lines: [""],
|
lines: [""],
|
||||||
|
|
@ -254,6 +258,7 @@ export class Editor implements Component, Focusable {
|
||||||
|
|
||||||
protected tui: TUI;
|
protected tui: TUI;
|
||||||
private theme: EditorTheme;
|
private theme: EditorTheme;
|
||||||
|
private paddingX: number = 0;
|
||||||
|
|
||||||
// Store last render width for cursor navigation
|
// Store last render width for cursor navigation
|
||||||
private lastWidth: number = 80;
|
private lastWidth: number = 80;
|
||||||
|
|
@ -287,10 +292,12 @@ export class Editor implements Component, Focusable {
|
||||||
public onChange?: (text: string) => void;
|
public onChange?: (text: string) => void;
|
||||||
public disableSubmit: boolean = false;
|
public disableSubmit: boolean = false;
|
||||||
|
|
||||||
constructor(tui: TUI, theme: EditorTheme) {
|
constructor(tui: TUI, theme: EditorTheme, options: EditorOptions = {}) {
|
||||||
this.tui = tui;
|
this.tui = tui;
|
||||||
this.theme = theme;
|
this.theme = theme;
|
||||||
this.borderColor = theme.borderColor;
|
this.borderColor = theme.borderColor;
|
||||||
|
const paddingX = options.paddingX ?? 0;
|
||||||
|
this.paddingX = Number.isFinite(paddingX) ? Math.max(0, Math.floor(paddingX)) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAutocompleteProvider(provider: AutocompleteProvider): void {
|
setAutocompleteProvider(provider: AutocompleteProvider): void {
|
||||||
|
|
@ -364,13 +371,17 @@ export class Editor implements Component, Focusable {
|
||||||
}
|
}
|
||||||
|
|
||||||
render(width: number): string[] {
|
render(width: number): string[] {
|
||||||
|
const maxPadding = Math.max(0, Math.floor((width - 1) / 2));
|
||||||
|
const paddingX = Math.min(this.paddingX, maxPadding);
|
||||||
|
const contentWidth = Math.max(1, width - paddingX * 2);
|
||||||
|
|
||||||
// Store width for cursor navigation
|
// Store width for cursor navigation
|
||||||
this.lastWidth = width;
|
this.lastWidth = contentWidth;
|
||||||
|
|
||||||
const horizontal = this.borderColor("─");
|
const horizontal = this.borderColor("─");
|
||||||
|
|
||||||
// Layout the text - use full width
|
// Layout the text - use content width
|
||||||
const layoutLines = this.layoutText(width);
|
const layoutLines = this.layoutText(contentWidth);
|
||||||
|
|
||||||
// Calculate max visible lines: 30% of terminal height, minimum 5 lines
|
// Calculate max visible lines: 30% of terminal height, minimum 5 lines
|
||||||
const terminalRows = this.tui.terminal.rows;
|
const terminalRows = this.tui.terminal.rows;
|
||||||
|
|
@ -395,6 +406,8 @@ export class Editor implements Component, Focusable {
|
||||||
const visibleLines = layoutLines.slice(this.scrollOffset, this.scrollOffset + maxVisibleLines);
|
const visibleLines = layoutLines.slice(this.scrollOffset, this.scrollOffset + maxVisibleLines);
|
||||||
|
|
||||||
const result: string[] = [];
|
const result: string[] = [];
|
||||||
|
const leftPadding = " ".repeat(paddingX);
|
||||||
|
const rightPadding = leftPadding;
|
||||||
|
|
||||||
// Render top border (with scroll indicator if scrolled down)
|
// Render top border (with scroll indicator if scrolled down)
|
||||||
if (this.scrollOffset > 0) {
|
if (this.scrollOffset > 0) {
|
||||||
|
|
@ -432,7 +445,7 @@ export class Editor implements Component, Focusable {
|
||||||
// lineVisibleWidth stays the same - we're replacing, not adding
|
// lineVisibleWidth stays the same - we're replacing, not adding
|
||||||
} else {
|
} else {
|
||||||
// Cursor is at the end - check if we have room for the space
|
// Cursor is at the end - check if we have room for the space
|
||||||
if (lineVisibleWidth < width) {
|
if (lineVisibleWidth < contentWidth) {
|
||||||
// We have room - add highlighted space
|
// We have room - add highlighted space
|
||||||
const cursor = "\x1b[7m \x1b[0m";
|
const cursor = "\x1b[7m \x1b[0m";
|
||||||
displayText = before + marker + cursor;
|
displayText = before + marker + cursor;
|
||||||
|
|
@ -458,10 +471,10 @@ export class Editor implements Component, Focusable {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate padding based on actual visible width
|
// Calculate padding based on actual visible width
|
||||||
const padding = " ".repeat(Math.max(0, width - lineVisibleWidth));
|
const padding = " ".repeat(Math.max(0, contentWidth - lineVisibleWidth));
|
||||||
|
|
||||||
// Render the line (no side borders, just horizontal lines above and below)
|
// Render the line (no side borders, just horizontal lines above and below)
|
||||||
result.push(displayText + padding);
|
result.push(`${leftPadding}${displayText}${padding}${rightPadding}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render bottom border (with scroll indicator if more content below)
|
// Render bottom border (with scroll indicator if more content below)
|
||||||
|
|
@ -476,8 +489,12 @@ export class Editor implements Component, Focusable {
|
||||||
|
|
||||||
// Add autocomplete list if active
|
// Add autocomplete list if active
|
||||||
if (this.isAutocompleting && this.autocompleteList) {
|
if (this.isAutocompleting && this.autocompleteList) {
|
||||||
const autocompleteResult = this.autocompleteList.render(width);
|
const autocompleteResult = this.autocompleteList.render(contentWidth);
|
||||||
result.push(...autocompleteResult);
|
for (const line of autocompleteResult) {
|
||||||
|
const lineWidth = visibleWidth(line);
|
||||||
|
const linePadding = " ".repeat(Math.max(0, contentWidth - lineWidth));
|
||||||
|
result.push(`${leftPadding}${line}${linePadding}${rightPadding}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ export {
|
||||||
// Components
|
// Components
|
||||||
export { Box } from "./components/box.js";
|
export { Box } from "./components/box.js";
|
||||||
export { CancellableLoader } from "./components/cancellable-loader.js";
|
export { CancellableLoader } from "./components/cancellable-loader.js";
|
||||||
export { Editor, type EditorTheme } from "./components/editor.js";
|
export { Editor, type EditorOptions, type EditorTheme } from "./components/editor.js";
|
||||||
export { Image, type ImageOptions, type ImageTheme } from "./components/image.js";
|
export { Image, type ImageOptions, type ImageTheme } from "./components/image.js";
|
||||||
export { Input } from "./components/input.js";
|
export { Input } from "./components/input.js";
|
||||||
export { Loader } from "./components/loader.js";
|
export { Loader } from "./components/loader.js";
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue