@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed keybinding hints to show Option instead of Alt on macOS ([#4289](https://github.com/earendil-works/pi/issues/4289)).
|
||||||
- Fixed the interactive update notification to render the changelog as an OSC 8 hyperlink when the terminal supports hyperlinks ([#4280](https://github.com/earendil-works/pi/issues/4280)).
|
- Fixed the interactive update notification to render the changelog as an OSC 8 hyperlink when the terminal supports hyperlinks ([#4280](https://github.com/earendil-works/pi/issues/4280)).
|
||||||
|
|
||||||
## [0.74.0] - 2026-05-07
|
## [0.74.0] - 2026-05-07
|
||||||
|
|||||||
@@ -5,20 +5,44 @@
|
|||||||
import { getKeybindings, type Keybinding, type KeyId } from "@earendil-works/pi-tui";
|
import { getKeybindings, type Keybinding, type KeyId } from "@earendil-works/pi-tui";
|
||||||
import { theme } from "../theme/theme.js";
|
import { theme } from "../theme/theme.js";
|
||||||
|
|
||||||
function formatKeys(keys: KeyId[]): string {
|
export interface KeyTextFormatOptions {
|
||||||
|
capitalize?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatKeyPart(part: string, options: KeyTextFormatOptions): string {
|
||||||
|
const displayPart = process.platform === "darwin" && part.toLowerCase() === "alt" ? "option" : part;
|
||||||
|
return options.capitalize ? displayPart.charAt(0).toUpperCase() + displayPart.slice(1) : displayPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatKeyText(key: string, options: KeyTextFormatOptions = {}): string {
|
||||||
|
return key
|
||||||
|
.split("/")
|
||||||
|
.map((k) =>
|
||||||
|
k
|
||||||
|
.split("+")
|
||||||
|
.map((part) => formatKeyPart(part, options))
|
||||||
|
.join("+"),
|
||||||
|
)
|
||||||
|
.join("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatKeys(keys: KeyId[], options: KeyTextFormatOptions = {}): string {
|
||||||
if (keys.length === 0) return "";
|
if (keys.length === 0) return "";
|
||||||
if (keys.length === 1) return keys[0]!;
|
return formatKeyText(keys.join("/"), options);
|
||||||
return keys.join("/");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function keyText(keybinding: Keybinding): string {
|
export function keyText(keybinding: Keybinding): string {
|
||||||
return formatKeys(getKeybindings().getKeys(keybinding));
|
return formatKeys(getKeybindings().getKeys(keybinding));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function keyDisplayText(keybinding: Keybinding): string {
|
||||||
|
return formatKeys(getKeybindings().getKeys(keybinding), { capitalize: true });
|
||||||
|
}
|
||||||
|
|
||||||
export function keyHint(keybinding: Keybinding, description: string): string {
|
export function keyHint(keybinding: Keybinding, description: string): string {
|
||||||
return theme.fg("dim", keyText(keybinding)) + theme.fg("muted", ` ${description}`);
|
return theme.fg("dim", keyText(keybinding)) + theme.fg("muted", ` ${description}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rawKeyHint(key: string, description: string): string {
|
export function rawKeyHint(key: string, description: string): string {
|
||||||
return theme.fg("dim", key) + theme.fg("muted", ` ${description}`);
|
return theme.fg("dim", formatKeyText(key)) + theme.fg("muted", ` ${description}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import type { WarningSettings } from "../../../core/settings-manager.js";
|
import type { WarningSettings } from "../../../core/settings-manager.js";
|
||||||
import { getSelectListTheme, getSettingsListTheme, theme } from "../theme/theme.js";
|
import { getSelectListTheme, getSettingsListTheme, theme } from "../theme/theme.js";
|
||||||
import { DynamicBorder } from "./dynamic-border.js";
|
import { DynamicBorder } from "./dynamic-border.js";
|
||||||
|
import { keyDisplayText } from "./keybinding-hints.js";
|
||||||
|
|
||||||
const SETTINGS_SUBMENU_SELECT_LIST_LAYOUT: SelectListLayoutOptions = {
|
const SETTINGS_SUBMENU_SELECT_LIST_LAYOUT: SelectListLayoutOptions = {
|
||||||
minPrimaryColumnWidth: 12,
|
minPrimaryColumnWidth: 12,
|
||||||
@@ -204,6 +205,7 @@ export class SettingsSelectorComponent extends Container {
|
|||||||
super();
|
super();
|
||||||
|
|
||||||
const supportsImages = getCapabilities().images;
|
const supportsImages = getCapabilities().images;
|
||||||
|
const followUpKey = keyDisplayText("app.message.followUp");
|
||||||
let currentWarnings = { ...config.warnings };
|
let currentWarnings = { ...config.warnings };
|
||||||
|
|
||||||
const items: SettingItem[] = [
|
const items: SettingItem[] = [
|
||||||
@@ -225,8 +227,7 @@ export class SettingsSelectorComponent extends Container {
|
|||||||
{
|
{
|
||||||
id: "follow-up-mode",
|
id: "follow-up-mode",
|
||||||
label: "Follow-up mode",
|
label: "Follow-up mode",
|
||||||
description:
|
description: `${followUpKey} queues follow-up messages until agent stops. 'one-at-a-time': deliver one, wait for response. 'all': deliver all at once.`,
|
||||||
"Alt+Enter queues follow-up messages until agent stops. 'one-at-a-time': deliver one, wait for response. 'all': deliver all at once.",
|
|
||||||
currentValue: config.followUpMode,
|
currentValue: config.followUpMode,
|
||||||
values: ["one-at-a-time", "all"],
|
values: ["one-at-a-time", "all"],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1185,11 +1185,12 @@ export class TreeSelectorComponent extends Container implements Focusable {
|
|||||||
keyText("app.tree.filter.all"),
|
keyText("app.tree.filter.all"),
|
||||||
].join("/");
|
].join("/");
|
||||||
const cycleKeys = `${keyText("app.tree.filter.cycleForward")}/${keyText("app.tree.filter.cycleBackward")}`;
|
const cycleKeys = `${keyText("app.tree.filter.cycleForward")}/${keyText("app.tree.filter.cycleBackward")}`;
|
||||||
|
const branchKeys = `${keyText("app.tree.foldOrUp")}/${keyText("app.tree.unfoldOrDown")}`;
|
||||||
this.addChild(
|
this.addChild(
|
||||||
new TruncatedText(
|
new TruncatedText(
|
||||||
theme.fg(
|
theme.fg(
|
||||||
"muted",
|
"muted",
|
||||||
` ↑/↓: move. ←/→: page. ^←/^→ or Alt+←/Alt+→: fold/branch. ${keyText("app.tree.editLabel")}: label. ${filterKeys}: filters (${cycleKeys} cycle). ${keyText("app.tree.toggleLabelTimestamp")}: label time`,
|
` ↑/↓: move. ←/→: page. ${branchKeys}: fold/branch. ${keyText("app.tree.editLabel")}: label. ${filterKeys}: filters (${cycleKeys} cycle). ${keyText("app.tree.toggleLabelTimestamp")}: label time`,
|
||||||
),
|
),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ import { ExtensionEditorComponent } from "./components/extension-editor.js";
|
|||||||
import { ExtensionInputComponent } from "./components/extension-input.js";
|
import { ExtensionInputComponent } from "./components/extension-input.js";
|
||||||
import { ExtensionSelectorComponent } from "./components/extension-selector.js";
|
import { ExtensionSelectorComponent } from "./components/extension-selector.js";
|
||||||
import { FooterComponent } from "./components/footer.js";
|
import { FooterComponent } from "./components/footer.js";
|
||||||
import { keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
||||||
import { LoginDialogComponent } from "./components/login-dialog.js";
|
import { LoginDialogComponent } from "./components/login-dialog.js";
|
||||||
import { ModelSelectorComponent } from "./components/model-selector.js";
|
import { ModelSelectorComponent } from "./components/model-selector.js";
|
||||||
import { type AuthSelectorProvider, OAuthSelectorComponent } from "./components/oauth-selector.js";
|
import { type AuthSelectorProvider, OAuthSelectorComponent } from "./components/oauth-selector.js";
|
||||||
@@ -5137,33 +5137,18 @@ export class InteractiveMode {
|
|||||||
this.ui.requestRender();
|
this.ui.requestRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Capitalize keybinding for display (e.g., "ctrl+c" -> "Ctrl+C").
|
|
||||||
*/
|
|
||||||
private capitalizeKey(key: string): string {
|
|
||||||
return key
|
|
||||||
.split("/")
|
|
||||||
.map((k) =>
|
|
||||||
k
|
|
||||||
.split("+")
|
|
||||||
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
||||||
.join("+"),
|
|
||||||
)
|
|
||||||
.join("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get capitalized display string for an app keybinding action.
|
* Get capitalized display string for an app keybinding action.
|
||||||
*/
|
*/
|
||||||
private getAppKeyDisplay(action: AppKeybinding): string {
|
private getAppKeyDisplay(action: AppKeybinding): string {
|
||||||
return this.capitalizeKey(keyText(action));
|
return keyDisplayText(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get capitalized display string for an editor keybinding action.
|
* Get capitalized display string for an editor keybinding action.
|
||||||
*/
|
*/
|
||||||
private getEditorKeyDisplay(action: Keybinding): string {
|
private getEditorKeyDisplay(action: Keybinding): string {
|
||||||
return this.capitalizeKey(keyText(action));
|
return keyDisplayText(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleHotkeysCommand(): void {
|
private handleHotkeysCommand(): void {
|
||||||
@@ -5267,7 +5252,7 @@ export class InteractiveMode {
|
|||||||
`;
|
`;
|
||||||
for (const [key, shortcut] of shortcuts) {
|
for (const [key, shortcut] of shortcuts) {
|
||||||
const description = shortcut.description ?? shortcut.extensionPath;
|
const description = shortcut.description ?? shortcut.extensionPath;
|
||||||
const keyDisplay = key.replace(/\b\w/g, (c) => c.toUpperCase());
|
const keyDisplay = formatKeyText(key, { capitalize: true });
|
||||||
hotkeys += `| \`${keyDisplay}\` | ${description} |\n`;
|
hotkeys += `| \`${keyDisplay}\` | ${description} |\n`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user