This commit is contained in:
isomo 2025-12-23 15:42:47 +08:00 committed by GitHub
commit a56cab1ade
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 74 additions and 1 deletions

View file

@ -0,0 +1,37 @@
import { createMemo } from "solid-js"
import { DialogSelect } from "@tui/ui/dialog-select"
import { useDialog } from "@tui/ui/dialog"
import type { PromptInfo } from "./prompt/history"
export interface DialogHistorySearchProps {
items: PromptInfo[]
onSelect: (item: PromptInfo) => void
}
export function DialogHistorySearch(props: DialogHistorySearchProps) {
const dialog = useDialog()
const options = createMemo(() => {
const seen = new Set<string>()
return props.items
.slice()
.reverse()
.filter((item) => {
if (!item.input.trim().length) return false
if (seen.has(item.input)) return false
seen.add(item.input)
return true
})
.map((item) => ({
title: item.input,
value: item,
description: undefined,
onSelect: () => {
props.onSelect(item)
dialog.clear()
},
}))
})
return <DialogSelect title="Search History" placeholder="Type to search..." options={options()} />
}

View file

@ -25,7 +25,7 @@ export type PromptInfo = {
)[]
}
const MAX_HISTORY_ENTRIES = 50
const MAX_HISTORY_ENTRIES = 100
export const { use: usePromptHistory, provider: PromptHistoryProvider } = createSimpleContext({
name: "PromptHistory",
@ -61,6 +61,9 @@ export const { use: usePromptHistory, provider: PromptHistoryProvider } = create
})
return {
get items() {
return store.history
},
move(direction: 1 | -1, input: string) {
if (!store.history.length) return undefined
const current = store.history.at(store.index)
@ -103,6 +106,9 @@ export const { use: usePromptHistory, provider: PromptHistoryProvider } = create
appendFile(historyFile.name!, JSON.stringify(entry) + "\n").catch(() => {})
},
reset() {
setStore("index", 0)
},
}
},
})

View file

@ -27,6 +27,7 @@ import { useDialog } from "@tui/ui/dialog"
import { DialogProvider as DialogProviderConnect } from "../dialog-provider"
import { DialogAlert } from "../../ui/dialog-alert"
import { useToast } from "../../ui/toast"
import { DialogHistorySearch } from "../dialog-history-search"
export type PromptProps = {
sessionID?: string
@ -788,6 +789,25 @@ export function Prompt(props: PromptProps) {
}
if (store.mode === "normal") autocomplete.onKeyDown(e)
if (!autocomplete.visible) {
if (keybind.match("history_search", e)) {
e.preventDefault()
const historyItems = history.items
dialog.replace(() => (
<DialogHistorySearch
items={historyItems}
onSelect={(item) => {
input.setText(item.input)
setStore("prompt", item)
setStore("mode", item.mode ?? "normal")
restoreExtmarksFromParts(item.parts)
input.cursorOffset = input.plainText.length
history.reset()
}}
/>
))
return
}
if (
(keybind.match("history_previous", e) && input.cursorOffset === 0) ||
(keybind.match("history_next", e) && input.cursorOffset === input.plainText.length)

View file

@ -561,6 +561,7 @@ export namespace Config {
.describe("Delete word backward in input"),
history_previous: z.string().optional().default("up").describe("Previous history item"),
history_next: z.string().optional().default("down").describe("Next history item"),
history_search: z.string().optional().default("ctrl+r").describe("Search history"),
session_child_cycle: z.string().optional().default("<leader>right").describe("Next child session"),
session_child_cycle_reverse: z.string().optional().default("<leader>left").describe("Previous child session"),
session_parent: z.string().optional().default("<leader>up").describe("Go to parent session"),

View file

@ -1114,6 +1114,10 @@ export type KeybindsConfig = {
* Next history item
*/
history_next?: string
/**
* Search history
*/
history_search?: string
/**
* Next child session
*/

View file

@ -7623,6 +7623,11 @@
"default": "down",
"type": "string"
},
"history_search": {
"description": "Search history",
"default": "ctrl+r",
"type": "string"
},
"session_child_cycle": {
"description": "Next child session",
"default": "<leader>right",