mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
parent
db3f9953ee
commit
e4907b3b09
4 changed files with 45 additions and 29 deletions
|
|
@ -5455,7 +5455,7 @@ export class InteractiveMode {
|
|||
**Navigation**
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| \`${cursorUp}\` / \`${cursorDown}\` / \`${cursorLeft}\` / \`${cursorRight}\` | Move cursor / browse history (Up when empty) |
|
||||
| \`${cursorUp}\` / \`${cursorDown}\` / \`${cursorLeft}\` / \`${cursorRight}\` | Move cursor / browse history |
|
||||
| \`${cursorWordLeft}\` / \`${cursorWordRight}\` | Move by word |
|
||||
| \`${cursorLineStart}\` | Start of line |
|
||||
| \`${cursorLineEnd}\` | End of line |
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
### Fixed
|
||||
|
||||
- Fixed prompt history navigation to restore the current draft when returning from history browsing ([#5494](https://github.com/earendil-works/pi/issues/5494)).
|
||||
- Fixed wrapping for mixed Latin and CJK text so unspaced CJK runs can break at grapheme boundaries without leaving large trailing gaps ([#5495](https://github.com/earendil-works/pi/issues/5495)).
|
||||
|
||||
## [0.79.0] - 2026-06-08
|
||||
|
|
|
|||
|
|
@ -266,6 +266,7 @@ export class Editor implements Component, Focusable {
|
|||
// Prompt history for up/down navigation
|
||||
private history: string[] = [];
|
||||
private historyIndex: number = -1; // -1 = not browsing, 0 = most recent, 1 = older, etc.
|
||||
private historyDraft: EditorState | null = null;
|
||||
|
||||
// Kill ring for Emacs-style kill/yank operations
|
||||
private killRing = new KillRing();
|
||||
|
|
@ -356,10 +357,6 @@ export class Editor implements Component, Focusable {
|
|||
}
|
||||
}
|
||||
|
||||
private isEditorEmpty(): boolean {
|
||||
return this.state.lines.length === 1 && this.state.lines[0] === "";
|
||||
}
|
||||
|
||||
private isOnFirstVisualLine(): boolean {
|
||||
const visualLines = this.buildVisualLineMap(this.lastWidth);
|
||||
const currentVisualLine = this.findCurrentVisualLine(visualLines);
|
||||
|
|
@ -382,18 +379,33 @@ export class Editor implements Component, Focusable {
|
|||
// Capture state when first entering history browsing mode
|
||||
if (this.historyIndex === -1 && newIndex >= 0) {
|
||||
this.pushUndoSnapshot();
|
||||
this.historyDraft = structuredClone(this.state);
|
||||
}
|
||||
|
||||
this.historyIndex = newIndex;
|
||||
|
||||
if (this.historyIndex === -1) {
|
||||
// Returned to "current" state - clear editor
|
||||
this.setTextInternal("");
|
||||
const draft = this.historyDraft;
|
||||
this.historyDraft = null;
|
||||
if (draft) {
|
||||
this.state = draft;
|
||||
this.preferredVisualCol = null;
|
||||
this.snappedFromCursorCol = null;
|
||||
this.scrollOffset = 0;
|
||||
if (this.onChange) this.onChange(this.getText());
|
||||
} else {
|
||||
this.setTextInternal("");
|
||||
}
|
||||
} else {
|
||||
this.setTextInternal(this.history[this.historyIndex] || "", direction === -1 ? "start" : "end");
|
||||
}
|
||||
}
|
||||
|
||||
private exitHistoryBrowsing(): void {
|
||||
this.historyIndex = -1;
|
||||
this.historyDraft = null;
|
||||
}
|
||||
|
||||
/** Internal setText that doesn't reset history state - used by navigateHistory */
|
||||
private setTextInternal(text: string, cursorPlacement: "start" | "end" = "end"): void {
|
||||
const lines = text.split("\n");
|
||||
|
|
@ -758,9 +770,7 @@ export class Editor implements Component, Focusable {
|
|||
|
||||
// Arrow key navigation (with history support)
|
||||
if (kb.matches(data, "tui.editor.cursorUp")) {
|
||||
if (this.isEditorEmpty()) {
|
||||
this.navigateHistory(-1);
|
||||
} else if (this.historyIndex > -1 && this.isOnFirstVisualLine()) {
|
||||
if (this.isOnFirstVisualLine() && this.history.length > 0) {
|
||||
this.navigateHistory(-1);
|
||||
} else if (this.isOnFirstVisualLine()) {
|
||||
// Already at top - jump to start of line
|
||||
|
|
@ -948,7 +958,7 @@ export class Editor implements Component, Focusable {
|
|||
setText(text: string): void {
|
||||
this.cancelAutocomplete();
|
||||
this.lastAction = null;
|
||||
this.historyIndex = -1; // Exit history browsing mode
|
||||
this.exitHistoryBrowsing();
|
||||
const normalized = this.normalizeText(text);
|
||||
// Push undo snapshot if content differs (makes programmatic changes undoable)
|
||||
if (this.getText() !== normalized) {
|
||||
|
|
@ -967,7 +977,7 @@ export class Editor implements Component, Focusable {
|
|||
this.cancelAutocomplete();
|
||||
this.pushUndoSnapshot();
|
||||
this.lastAction = null;
|
||||
this.historyIndex = -1;
|
||||
this.exitHistoryBrowsing();
|
||||
this.insertTextAtCursorInternal(text);
|
||||
}
|
||||
|
||||
|
|
@ -1030,7 +1040,7 @@ export class Editor implements Component, Focusable {
|
|||
|
||||
// All the editor methods from before...
|
||||
private insertCharacter(char: string, skipUndoCoalescing?: boolean): void {
|
||||
this.historyIndex = -1; // Exit history browsing mode
|
||||
this.exitHistoryBrowsing();
|
||||
|
||||
// Undo coalescing (fish-style):
|
||||
// - Consecutive word chars coalesce into one undo unit
|
||||
|
|
@ -1091,7 +1101,7 @@ export class Editor implements Component, Focusable {
|
|||
|
||||
private handlePaste(pastedText: string): void {
|
||||
this.cancelAutocomplete();
|
||||
this.historyIndex = -1; // Exit history browsing mode
|
||||
this.exitHistoryBrowsing();
|
||||
this.lastAction = null;
|
||||
|
||||
this.pushUndoSnapshot();
|
||||
|
|
@ -1159,7 +1169,7 @@ export class Editor implements Component, Focusable {
|
|||
|
||||
private addNewLine(): void {
|
||||
this.cancelAutocomplete();
|
||||
this.historyIndex = -1; // Exit history browsing mode
|
||||
this.exitHistoryBrowsing();
|
||||
this.lastAction = null;
|
||||
|
||||
this.pushUndoSnapshot();
|
||||
|
|
@ -1200,7 +1210,7 @@ export class Editor implements Component, Focusable {
|
|||
this.state = { lines: [""], cursorLine: 0, cursorCol: 0 };
|
||||
this.pastes.clear();
|
||||
this.pasteCounter = 0;
|
||||
this.historyIndex = -1;
|
||||
this.exitHistoryBrowsing();
|
||||
this.scrollOffset = 0;
|
||||
this.undoStack.clear();
|
||||
this.lastAction = null;
|
||||
|
|
@ -1210,7 +1220,7 @@ export class Editor implements Component, Focusable {
|
|||
}
|
||||
|
||||
private handleBackspace(): void {
|
||||
this.historyIndex = -1; // Exit history browsing mode
|
||||
this.exitHistoryBrowsing();
|
||||
this.lastAction = null;
|
||||
|
||||
if (this.state.cursorCol > 0) {
|
||||
|
|
@ -1427,7 +1437,7 @@ export class Editor implements Component, Focusable {
|
|||
}
|
||||
|
||||
private deleteToStartOfLine(): void {
|
||||
this.historyIndex = -1; // Exit history browsing mode
|
||||
this.exitHistoryBrowsing();
|
||||
|
||||
const currentLine = this.state.lines[this.state.cursorLine] || "";
|
||||
|
||||
|
|
@ -1462,7 +1472,7 @@ export class Editor implements Component, Focusable {
|
|||
}
|
||||
|
||||
private deleteToEndOfLine(): void {
|
||||
this.historyIndex = -1; // Exit history browsing mode
|
||||
this.exitHistoryBrowsing();
|
||||
|
||||
const currentLine = this.state.lines[this.state.cursorLine] || "";
|
||||
|
||||
|
|
@ -1494,7 +1504,7 @@ export class Editor implements Component, Focusable {
|
|||
}
|
||||
|
||||
private deleteWordBackwards(): void {
|
||||
this.historyIndex = -1; // Exit history browsing mode
|
||||
this.exitHistoryBrowsing();
|
||||
|
||||
const currentLine = this.state.lines[this.state.cursorLine] || "";
|
||||
|
||||
|
|
@ -1539,7 +1549,7 @@ export class Editor implements Component, Focusable {
|
|||
}
|
||||
|
||||
private deleteWordForward(): void {
|
||||
this.historyIndex = -1; // Exit history browsing mode
|
||||
this.exitHistoryBrowsing();
|
||||
|
||||
const currentLine = this.state.lines[this.state.cursorLine] || "";
|
||||
|
||||
|
|
@ -1581,7 +1591,7 @@ export class Editor implements Component, Focusable {
|
|||
}
|
||||
|
||||
private handleForwardDelete(): void {
|
||||
this.historyIndex = -1; // Exit history browsing mode
|
||||
this.exitHistoryBrowsing();
|
||||
this.lastAction = null;
|
||||
|
||||
const currentLine = this.state.lines[this.state.cursorLine] || "";
|
||||
|
|
@ -1837,7 +1847,7 @@ export class Editor implements Component, Focusable {
|
|||
* Insert text at cursor position (used by yank operations).
|
||||
*/
|
||||
private insertYankedText(text: string): void {
|
||||
this.historyIndex = -1; // Exit history browsing mode
|
||||
this.exitHistoryBrowsing();
|
||||
const lines = text.split("\n");
|
||||
|
||||
if (lines.length === 1) {
|
||||
|
|
@ -1922,7 +1932,7 @@ export class Editor implements Component, Focusable {
|
|||
}
|
||||
|
||||
private undo(): void {
|
||||
this.historyIndex = -1; // Exit history browsing mode
|
||||
this.exitHistoryBrowsing();
|
||||
const snapshot = this.undoStack.pop();
|
||||
if (!snapshot) return;
|
||||
Object.assign(this.state, snapshot);
|
||||
|
|
|
|||
|
|
@ -79,16 +79,20 @@ describe("Editor component", () => {
|
|||
assert.strictEqual(editor.getText(), "first");
|
||||
});
|
||||
|
||||
it("returns to empty editor on Down arrow after browsing history", () => {
|
||||
it("restores draft on Down arrow after browsing history", () => {
|
||||
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||
|
||||
editor.addToHistory("prompt");
|
||||
editor.setText("draft");
|
||||
editor.handleInput("\x1b[D");
|
||||
editor.handleInput("\x1b[D");
|
||||
|
||||
editor.handleInput("\x1b[A"); // Up - shows "prompt"
|
||||
assert.strictEqual(editor.getText(), "prompt");
|
||||
|
||||
editor.handleInput("\x1b[B"); // Down - clears editor
|
||||
assert.strictEqual(editor.getText(), "");
|
||||
editor.handleInput("\x1b[B"); // Down - restores draft
|
||||
assert.strictEqual(editor.getText(), "draft");
|
||||
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 3 });
|
||||
});
|
||||
|
||||
it("navigates forward through history with Down arrow", () => {
|
||||
|
|
@ -97,6 +101,7 @@ describe("Editor component", () => {
|
|||
editor.addToHistory("first");
|
||||
editor.addToHistory("second");
|
||||
editor.addToHistory("third");
|
||||
editor.setText("draft");
|
||||
|
||||
// Go to oldest
|
||||
editor.handleInput("\x1b[A"); // third
|
||||
|
|
@ -110,8 +115,8 @@ describe("Editor component", () => {
|
|||
editor.handleInput("\x1b[B"); // third
|
||||
assert.strictEqual(editor.getText(), "third");
|
||||
|
||||
editor.handleInput("\x1b[B"); // empty
|
||||
assert.strictEqual(editor.getText(), "");
|
||||
editor.handleInput("\x1b[B"); // draft
|
||||
assert.strictEqual(editor.getText(), "draft");
|
||||
});
|
||||
|
||||
it("exits history mode when typing a character", () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue