Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
run: bun run typecheck

- name: Test suite
run: bun test
run: bun run test

- name: PTY integration tests
run: bun run test:integration
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
run: bun run typecheck

- name: Test suite
run: bun test
run: bun run test

- name: PTY integration tests
run: bun run test:integration
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"lint": "oxlint . --deny-warnings",
"lint:fix": "oxlint . --fix",
"prepare": "simple-git-hooks",
"test": "\"${npm_execpath:-bun}\" test",
"test": "\"${npm_execpath:-bun}\" test ./src ./scripts ./test/cli ./test/session",
"test:integration": "\"${npm_execpath:-bun}\" test ./test/pty",
"test:tty-smoke": "HUNK_RUN_TTY_SMOKE=1 \"${npm_execpath:-bun}\" test ./test/smoke",
"check:pack": "bun run ./scripts/check-pack.ts",
Expand Down
4 changes: 0 additions & 4 deletions src/ui/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ export function App({
const filteredFiles = review.visibleFiles;
const selectedFile = review.selectedFile;
const selectedHunkIndex = review.selectedHunkIndex;
const clearFilter = review.clearFilter;
const moveToAnnotatedFile = review.moveToAnnotatedFile;
const moveToAnnotatedHunk = review.moveToAnnotatedHunk;

Expand Down Expand Up @@ -534,13 +533,10 @@ export function App({
activeMenuId,
activateCurrentMenuItem,
canRefreshCurrentInput,
clearFilter,
closeHelp,
closeMenu,
cycleTheme,
filter: review.filter,
focusArea,
focusFiles,
focusFilter,
moveToAnnotatedHunk,
moveToHunk: review.moveToHunk,
Expand Down
16 changes: 16 additions & 0 deletions src/ui/components/chrome/StatusBar.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isEscapeKey } from "../../lib/keyboard";
import type { AppTheme } from "../../themes";

/** Render the active file filter input or current filter summary. */
Expand Down Expand Up @@ -45,6 +46,21 @@ export function StatusBar({
focused={true}
onInput={onFilterInput}
onSubmit={onFilterSubmit}
onKeyDown={(key) => {
if (!isEscapeKey(key)) {
return;
}

key.preventDefault();
key.stopPropagation();

if (filter.length > 0) {
onFilterInput("");
return;
}

onFilterSubmit();
}}
/>
</>
) : filter.length > 0 ? (
Expand Down
50 changes: 23 additions & 27 deletions src/ui/hooks/useAppKeyboardShortcuts.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { KeyEvent } from "@opentui/core";
import { useKeyboard } from "@opentui/react";
import { useRef } from "react";
import type { LayoutMode } from "../../core/types";
import type { MenuId } from "../components/chrome/menu";
import {
isEscapeKey,
isHalfPageDownKey,
isHalfPageUpKey,
isPageDownKey,
Expand All @@ -21,13 +23,10 @@ export interface UseAppKeyboardShortcutsOptions {
activeMenuId: MenuId | null;
activateCurrentMenuItem: () => void;
canRefreshCurrentInput: boolean;
clearFilter: () => void;
closeHelp: () => void;
closeMenu: () => void;
cycleTheme: () => void;
filter: string;
focusArea: FocusArea;
focusFiles: () => void;
focusFilter: () => void;
moveToAnnotatedHunk: (delta: number) => void;
moveToHunk: (delta: number) => void;
Expand Down Expand Up @@ -55,13 +54,10 @@ export function useAppKeyboardShortcuts({
activeMenuId,
activateCurrentMenuItem,
canRefreshCurrentInput,
clearFilter,
closeHelp,
closeMenu,
cycleTheme,
filter,
focusArea,
focusFiles,
focusFilter,
moveToAnnotatedHunk,
moveToHunk,
Expand All @@ -83,6 +79,16 @@ export function useAppKeyboardShortcuts({
toggleSidebar,
triggerRefreshCurrentInput,
}: UseAppKeyboardShortcutsOptions) {
const activeMenuIdRef = useRef(activeMenuId);
const focusAreaRef = useRef(focusArea);
const pagerModeRef = useRef(pagerMode);
const showHelpRef = useRef(showHelp);

activeMenuIdRef.current = activeMenuId;
focusAreaRef.current = focusArea;
pagerModeRef.current = pagerMode;
showHelpRef.current = showHelp;

const runAndCloseMenu = (action: () => void) => {
action();
closeMenu();
Expand All @@ -93,11 +99,11 @@ export function useAppKeyboardShortcuts({
return false;
}

if (pagerMode) {
if (pagerModeRef.current) {
return true;
}

if (activeMenuId) {
if (activeMenuIdRef.current) {
closeMenu();
} else {
openMenu("file");
Expand All @@ -107,7 +113,7 @@ export function useAppKeyboardShortcuts({
};

const handlePagerShortcut = (key: KeyEvent) => {
if (key.name === "q" || key.name === "escape") {
if (key.name === "q" || isEscapeKey(key)) {
requestQuit();
return;
}
Expand Down Expand Up @@ -168,7 +174,7 @@ export function useAppKeyboardShortcuts({
};

const handleHelpShortcut = (key: KeyEvent) => {
if (!showHelp || key.name !== "escape") {
if (!showHelpRef.current || !isEscapeKey(key)) {
return false;
}

Expand All @@ -177,11 +183,11 @@ export function useAppKeyboardShortcuts({
};

const handleMenuShortcut = (key: KeyEvent) => {
if (!activeMenuId) {
if (!activeMenuIdRef.current) {
return false;
}

if (key.name === "escape") {
if (isEscapeKey(key)) {
closeMenu();
return true;
}
Expand Down Expand Up @@ -215,26 +221,16 @@ export function useAppKeyboardShortcuts({
};

const handleFilterShortcut = (key: KeyEvent) => {
if (focusArea !== "filter") {
if (focusAreaRef.current !== "filter") {
return false;
}

if (key.name === "escape") {
if (filter.length > 0) {
clearFilter();
return true;
}

focusFiles();
return true;
}

if (key.name === "tab") {
toggleFocusArea();
return true;
}

// Let the input widget own typing while the filter is focused.
// Let the focused input own filter editing and escape handling.
return true;
};

Expand All @@ -244,13 +240,13 @@ export function useAppKeyboardShortcuts({
return;
}

if (key.name === "?") {
if (key.name === "?" || key.sequence === "?") {
toggleHelp();
closeMenu();
return;
}

if (key.name === "escape") {
if (isEscapeKey(key)) {
requestQuit();
return;
}
Expand Down Expand Up @@ -390,7 +386,7 @@ export function useAppKeyboardShortcuts({
return;
}

if (pagerMode) {
if (pagerModeRef.current) {
handlePagerShortcut(key);
return;
}
Expand Down
5 changes: 5 additions & 0 deletions src/ui/lib/keyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ function isSpaceKey(key: KeyEvent) {
return key.name === "space" || key.name === " " || key.sequence === " ";
}

/** Normalize the escape key aliases emitted by different terminal input paths. */
export function isEscapeKey(key: KeyEvent) {
return key.name === "escape" || key.name === "esc";
}

/** Match any key alias that should scroll forward by a full viewport. */
export function isPageDownKey(key: KeyEvent) {
return (
Expand Down
4 changes: 4 additions & 0 deletions src/ui/lib/ui-lib.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { buildAgentPopoverContent, resolveAgentPopoverPlacement, wrapText } from "./agentPopover";
import { buildAppMenus } from "./appMenus";
import {
isEscapeKey,
isHalfPageDownKey,
isHalfPageUpKey,
isPageDownKey,
Expand Down Expand Up @@ -171,6 +172,8 @@ describe("ui helpers", () => {
});

test("keyboard alias helpers normalize the shared scroll shortcut keys", () => {
expect(isEscapeKey(createKeyEvent({ name: "escape" }))).toBe(true);
expect(isEscapeKey(createKeyEvent({ name: "esc" }))).toBe(true);
expect(isPageDownKey(createKeyEvent({ name: "pagedown" }))).toBe(true);
expect(isPageDownKey(createKeyEvent({ name: "space" }))).toBe(true);
expect(isPageDownKey(createKeyEvent({ name: "f" }))).toBe(true);
Expand All @@ -185,6 +188,7 @@ describe("ui helpers", () => {
expect(isStepDownKey(createKeyEvent({ sequence: "j" }))).toBe(true);
expect(isStepUpKey(createKeyEvent({ name: "up" }))).toBe(true);
expect(isStepUpKey(createKeyEvent({ sequence: "k" }))).toBe(true);
expect(isEscapeKey(createKeyEvent({ name: "q" }))).toBe(false);
expect(isPageDownKey(createKeyEvent({ name: "space", shift: true }))).toBe(false);
expect(isPageDownKey(createKeyEvent({ name: "q" }))).toBe(false);
expect(isShiftSpacePageUpKey(createKeyEvent({ name: "space", shift: false }))).toBe(false);
Expand Down
Loading
Loading