Skip to content

Commit fe7be3f

Browse files
feat(tui): add ctrl+r history search with fuzzy filtering
1 parent 8717146 commit fe7be3f

File tree

6 files changed

+83
-11
lines changed

6 files changed

+83
-11
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { createMemo } from "solid-js"
2+
import { DialogSelect } from "@tui/ui/dialog-select"
3+
import { useDialog } from "@tui/ui/dialog"
4+
import type { PromptInfo } from "./prompt/history"
5+
6+
export interface DialogHistorySearchProps {
7+
items: PromptInfo[]
8+
onSelect: (item: PromptInfo) => void
9+
}
10+
11+
export function DialogHistorySearch(props: DialogHistorySearchProps) {
12+
const dialog = useDialog()
13+
14+
const options = createMemo(() => {
15+
const seen = new Set<string>()
16+
return props.items
17+
.slice()
18+
.reverse()
19+
.filter((item) => {
20+
if (!item.input.trim().length) return false
21+
if (seen.has(item.input)) return false
22+
seen.add(item.input)
23+
return true
24+
})
25+
.map((item) => ({
26+
title: item.input,
27+
value: item,
28+
description: undefined,
29+
onSelect: () => {
30+
props.onSelect(item)
31+
dialog.clear()
32+
},
33+
}))
34+
})
35+
36+
return <DialogSelect title="Search History" placeholder="Type to search..." options={options()} />
37+
}

packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,18 @@ export type PromptInfo = {
1414
| Omit<FilePart, "id" | "messageID" | "sessionID">
1515
| Omit<AgentPart, "id" | "messageID" | "sessionID">
1616
| (Omit<TextPart, "id" | "messageID" | "sessionID"> & {
17-
source?: {
18-
text: {
19-
start: number
20-
end: number
21-
value: string
22-
}
17+
source?: {
18+
text: {
19+
start: number
20+
end: number
21+
value: string
2322
}
24-
})
23+
}
24+
})
2525
)[]
2626
}
2727

28-
const MAX_HISTORY_ENTRIES = 50
28+
const MAX_HISTORY_ENTRIES = 100
2929

3030
export const { use: usePromptHistory, provider: PromptHistoryProvider } = createSimpleContext({
3131
name: "PromptHistory",
@@ -51,7 +51,7 @@ export const { use: usePromptHistory, provider: PromptHistoryProvider } = create
5151
// Rewrite file with only valid entries to self-heal corruption
5252
if (lines.length > 0) {
5353
const content = lines.map((line) => JSON.stringify(line)).join("\n") + "\n"
54-
writeFile(historyFile.name!, content).catch(() => {})
54+
writeFile(historyFile.name!, content).catch(() => { })
5555
}
5656
})
5757

@@ -61,6 +61,9 @@ export const { use: usePromptHistory, provider: PromptHistoryProvider } = create
6161
})
6262

6363
return {
64+
get items() {
65+
return store.history
66+
},
6467
move(direction: 1 | -1, input: string) {
6568
if (!store.history.length) return undefined
6669
const current = store.history.at(store.index)
@@ -97,11 +100,14 @@ export const { use: usePromptHistory, provider: PromptHistoryProvider } = create
97100

98101
if (trimmed) {
99102
const content = store.history.map((line) => JSON.stringify(line)).join("\n") + "\n"
100-
writeFile(historyFile.name!, content).catch(() => {})
103+
writeFile(historyFile.name!, content).catch(() => { })
101104
return
102105
}
103106

104-
appendFile(historyFile.name!, JSON.stringify(entry) + "\n").catch(() => {})
107+
appendFile(historyFile.name!, JSON.stringify(entry) + "\n").catch(() => { })
108+
},
109+
reset() {
110+
setStore("index", 0)
105111
},
106112
}
107113
},

packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { useDialog } from "@tui/ui/dialog"
2727
import { DialogProvider as DialogProviderConnect } from "../dialog-provider"
2828
import { DialogAlert } from "../../ui/dialog-alert"
2929
import { useToast } from "../../ui/toast"
30+
import { DialogHistorySearch } from "../dialog-history-search"
3031

3132
export type PromptProps = {
3233
sessionID?: string
@@ -763,6 +764,24 @@ export function Prompt(props: PromptProps) {
763764
}
764765
if (store.mode === "normal") autocomplete.onKeyDown(e)
765766
if (!autocomplete.visible) {
767+
if (keybind.match("history_search", e)) {
768+
e.preventDefault()
769+
const historyItems = history.items
770+
dialog.replace(() => (
771+
<DialogHistorySearch
772+
items={historyItems}
773+
onSelect={(item) => {
774+
input.setText(item.input)
775+
setStore("prompt", item)
776+
setStore("mode", item.mode ?? "normal")
777+
restoreExtmarksFromParts(item.parts)
778+
history.reset()
779+
}}
780+
/>
781+
))
782+
return
783+
}
784+
766785
if (
767786
(keybind.match("history_previous", e) && input.cursorOffset === 0) ||
768787
(keybind.match("history_next", e) && input.cursorOffset === input.plainText.length)

packages/opencode/src/config/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,7 @@ export namespace Config {
558558
.describe("Delete word backward in input"),
559559
history_previous: z.string().optional().default("up").describe("Previous history item"),
560560
history_next: z.string().optional().default("down").describe("Next history item"),
561+
history_search: z.string().optional().default("ctrl+r").describe("Search history"),
561562
session_child_cycle: z.string().optional().default("<leader>right").describe("Next child session"),
562563
session_child_cycle_reverse: z.string().optional().default("<leader>left").describe("Previous child session"),
563564
terminal_suspend: z.string().optional().default("ctrl+z").describe("Suspend terminal"),

packages/sdk/js/src/v2/gen/types.gen.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,6 +1106,10 @@ export type KeybindsConfig = {
11061106
* Next history item
11071107
*/
11081108
history_next?: string
1109+
/**
1110+
* Search history
1111+
*/
1112+
history_search?: string
11091113
/**
11101114
* Next child session
11111115
*/

packages/sdk/openapi.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7407,6 +7407,11 @@
74077407
"default": "down",
74087408
"type": "string"
74097409
},
7410+
"history_search": {
7411+
"description": "Search history",
7412+
"default": "ctrl+r",
7413+
"type": "string"
7414+
},
74107415
"session_child_cycle": {
74117416
"description": "Next child session",
74127417
"default": "<leader>right",

0 commit comments

Comments
 (0)