feat: add context-aware ask keymaps
This commit is contained in:
@@ -172,6 +172,7 @@ function renderController(
|
||||
}
|
||||
|
||||
function handleControllerInput(controller: AskFlowController, data: string) {
|
||||
controller.editor.disableSubmit = !isNativeEditorSubmitEnabled(controller);
|
||||
const command = getInputCommand(
|
||||
controller.state,
|
||||
controller.config,
|
||||
@@ -185,6 +186,16 @@ function handleControllerInput(controller: AskFlowController, data: string) {
|
||||
handleNavigationCommand(controller, command);
|
||||
}
|
||||
|
||||
function isNativeEditorSubmitEnabled(controller: AskFlowController): boolean {
|
||||
if (controller.state.view.kind === "input") {
|
||||
return controller.config.keymaps.editor.submit.includes("enter");
|
||||
}
|
||||
if (controller.state.view.kind === "note") {
|
||||
return controller.config.keymaps.noteEditor.save.includes("enter");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function handleEditingCommand(
|
||||
controller: AskFlowController,
|
||||
command: AskInputCommand,
|
||||
@@ -210,6 +221,10 @@ function handleEditingCommand(
|
||||
closeEditor(controller);
|
||||
return;
|
||||
}
|
||||
if (command.kind === "editSubmit") {
|
||||
submitEditor(controller, controller.editor.getText());
|
||||
return;
|
||||
}
|
||||
if (command.kind === "delegateToEditor") {
|
||||
controller.editor.handleInput(data);
|
||||
refresh(controller);
|
||||
@@ -270,6 +285,7 @@ function handleNavigationCommand(
|
||||
case "editMoveTab":
|
||||
case "editMoveOption":
|
||||
case "editClose":
|
||||
case "editSubmit":
|
||||
case "delegateToEditor":
|
||||
return;
|
||||
default:
|
||||
|
||||
+63
-15
@@ -1,6 +1,9 @@
|
||||
import { matchesKey } from "@mariozechner/pi-tui";
|
||||
import type { AskConfig } from "../config/schema.ts";
|
||||
import {
|
||||
getAskKeyBindings,
|
||||
type AskKeyBinding,
|
||||
getAskContextBindings,
|
||||
getGlobalBindings,
|
||||
matchesBinding,
|
||||
matchesDigitShortcut,
|
||||
} from "../constants/keymaps.ts";
|
||||
@@ -20,6 +23,7 @@ export type AskInputCommand =
|
||||
| { kind: "editMoveTab"; delta: 1 | -1 }
|
||||
| { kind: "editMoveOption"; delta: 1 | -1 }
|
||||
| { kind: "editClose" }
|
||||
| { kind: "editSubmit" }
|
||||
| { kind: "delegateToEditor" }
|
||||
| { kind: "ignore" };
|
||||
|
||||
@@ -29,50 +33,94 @@ export function getInputCommand(
|
||||
data: string,
|
||||
editingText = ""
|
||||
): AskInputCommand {
|
||||
const bindings = getAskKeyBindings(config);
|
||||
if (matchesBinding(data, bindings.dismiss)) {
|
||||
const global = getGlobalBindings(config);
|
||||
if (matchesBinding(data, global.dismiss)) {
|
||||
return { kind: "dismiss" };
|
||||
}
|
||||
if (matchesBinding(data, bindings.settings) && editingText.length === 0) {
|
||||
if (matchesBinding(data, global.settings) && editingText.length === 0) {
|
||||
return { kind: "showSettings" };
|
||||
}
|
||||
|
||||
if (state.view.kind === "input" || state.view.kind === "note") {
|
||||
return getEditingInputCommand(bindings, data, editingText);
|
||||
if (state.view.kind === "input") {
|
||||
return getAnswerEditorInputCommand(config, data, editingText);
|
||||
}
|
||||
if (state.view.kind === "note") {
|
||||
return getNoteEditorInputCommand(config, data, editingText);
|
||||
}
|
||||
|
||||
return getNavigationInputCommand(bindings, data);
|
||||
return getNavigationInputCommand(config, data);
|
||||
}
|
||||
|
||||
function getEditingInputCommand(
|
||||
bindings: ReturnType<typeof getAskKeyBindings>,
|
||||
function getAnswerEditorInputCommand(
|
||||
config: AskConfig,
|
||||
data: string,
|
||||
editingText: string
|
||||
): AskInputCommand {
|
||||
if (matchesBinding(data, bindings.cancel)) {
|
||||
const bindings = getAskContextBindings(config, "editor");
|
||||
if (matchesBinding(data, bindings.submit)) {
|
||||
return isNativeEditorSubmit(data)
|
||||
? { kind: "delegateToEditor" }
|
||||
: { kind: "editSubmit" };
|
||||
}
|
||||
if (matchesBinding(data, bindings.close)) {
|
||||
return { kind: "editClose" };
|
||||
}
|
||||
return getEmptyEditorNavigationCommand(bindings, data, editingText);
|
||||
}
|
||||
|
||||
function getNoteEditorInputCommand(
|
||||
config: AskConfig,
|
||||
data: string,
|
||||
editingText: string
|
||||
): AskInputCommand {
|
||||
const bindings = getAskContextBindings(config, "noteEditor");
|
||||
if (matchesBinding(data, bindings.save)) {
|
||||
return isNativeEditorSubmit(data)
|
||||
? { kind: "delegateToEditor" }
|
||||
: { kind: "editSubmit" };
|
||||
}
|
||||
if (matchesBinding(data, bindings.close)) {
|
||||
return { kind: "editClose" };
|
||||
}
|
||||
return getEmptyEditorNavigationCommand(bindings, data, editingText);
|
||||
}
|
||||
|
||||
function getEmptyEditorNavigationCommand(
|
||||
bindings: {
|
||||
nextTabWhenEmpty: AskKeyBinding;
|
||||
previousTabWhenEmpty: AskKeyBinding;
|
||||
previousOptionWhenEmpty: AskKeyBinding;
|
||||
nextOptionWhenEmpty: AskKeyBinding;
|
||||
},
|
||||
data: string,
|
||||
editingText: string
|
||||
): AskInputCommand {
|
||||
if (editingText.length === 0) {
|
||||
if (matchesBinding(data, bindings.nextTab)) {
|
||||
if (matchesBinding(data, bindings.nextTabWhenEmpty)) {
|
||||
return { kind: "editMoveTab", delta: 1 };
|
||||
}
|
||||
if (matchesBinding(data, bindings.previousTab)) {
|
||||
if (matchesBinding(data, bindings.previousTabWhenEmpty)) {
|
||||
return { kind: "editMoveTab", delta: -1 };
|
||||
}
|
||||
if (matchesBinding(data, bindings.previousOption)) {
|
||||
if (matchesBinding(data, bindings.previousOptionWhenEmpty)) {
|
||||
return { kind: "editMoveOption", delta: -1 };
|
||||
}
|
||||
if (matchesBinding(data, bindings.nextOption)) {
|
||||
if (matchesBinding(data, bindings.nextOptionWhenEmpty)) {
|
||||
return { kind: "editMoveOption", delta: 1 };
|
||||
}
|
||||
}
|
||||
return { kind: "delegateToEditor" };
|
||||
}
|
||||
|
||||
function isNativeEditorSubmit(data: string): boolean {
|
||||
return matchesKey(data, "enter");
|
||||
}
|
||||
|
||||
function getNavigationInputCommand(
|
||||
bindings: ReturnType<typeof getAskKeyBindings>,
|
||||
config: AskConfig,
|
||||
data: string
|
||||
): AskInputCommand {
|
||||
const bindings = getAskContextBindings(config, "main");
|
||||
if (matchesBinding(data, bindings.nextTab)) {
|
||||
return { kind: "moveTab", delta: 1 };
|
||||
}
|
||||
|
||||
+15
-15
@@ -1,12 +1,15 @@
|
||||
import {
|
||||
Key,
|
||||
matchesKey,
|
||||
truncateToWidth,
|
||||
visibleWidth,
|
||||
wrapTextWithAnsi,
|
||||
} from "@mariozechner/pi-tui";
|
||||
import type { AskConfig } from "../config/schema.ts";
|
||||
import type { AskConfigNotice } from "../config/store.ts";
|
||||
import {
|
||||
getAskContextBindings,
|
||||
matchesBinding,
|
||||
renderSettingsFooterKeymaps,
|
||||
} from "../constants/keymaps.ts";
|
||||
|
||||
interface Theme {
|
||||
bg(color: string, text: string): string;
|
||||
@@ -86,26 +89,23 @@ export class AskSettingsList {
|
||||
}
|
||||
|
||||
handleInput(data: string): void {
|
||||
if (
|
||||
data === "?" ||
|
||||
matchesKey(data, Key.ctrl("c")) ||
|
||||
matchesKey(data, Key.escape)
|
||||
) {
|
||||
const bindings = getAskContextBindings(this.config, "settingsModal");
|
||||
if (matchesBinding(data, bindings.close)) {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
if (matchesKey(data, Key.up)) {
|
||||
if (matchesBinding(data, bindings.previousOption)) {
|
||||
this.focusIndex =
|
||||
this.focusIndex === 0 ? SETTINGS.length - 1 : this.focusIndex - 1;
|
||||
this.tui.requestRender();
|
||||
return;
|
||||
}
|
||||
if (matchesKey(data, Key.down)) {
|
||||
if (matchesBinding(data, bindings.nextOption)) {
|
||||
this.focusIndex = (this.focusIndex + 1) % SETTINGS.length;
|
||||
this.tui.requestRender();
|
||||
return;
|
||||
}
|
||||
if (matchesKey(data, Key.enter) || data === " ") {
|
||||
if (matchesBinding(data, bindings.toggle)) {
|
||||
const setting = SETTINGS[this.focusIndex];
|
||||
if (setting) {
|
||||
this.saveSetting(setting.key, !this.getSettingValue(setting.key));
|
||||
@@ -175,15 +175,15 @@ export class AskSettingsList {
|
||||
|
||||
private appendFooter(lines: string[], innerWidth: number): void {
|
||||
if (innerWidth < COMPACT_WIDTH) {
|
||||
lines.push(
|
||||
this.line(this.theme.fg("dim", " Enter/Space to change"), innerWidth)
|
||||
);
|
||||
lines.push(this.line(this.theme.fg("dim", " Esc to close"), innerWidth));
|
||||
const footer = renderSettingsFooterKeymaps(this.config);
|
||||
for (const line of wrapTextWithAnsi(footer, innerWidth - 2)) {
|
||||
lines.push(this.line(this.theme.fg("dim", ` ${line}`), innerWidth));
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.appendWrapped(
|
||||
lines,
|
||||
this.theme.fg("dim", "Enter/Space to change · Esc to close"),
|
||||
this.theme.fg("dim", renderSettingsFooterKeymaps(this.config)),
|
||||
innerWidth
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user