mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-24 06:33:01 -04:00
feat(tui): highlight Bash commands
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
MouseEvent,
|
||||
PasteEvent,
|
||||
decodePasteBytes,
|
||||
getTreeSitterClient,
|
||||
type ColorInput,
|
||||
type KeyEvent,
|
||||
} from "@opentui/core"
|
||||
@@ -98,6 +99,7 @@ export type PromptRef = {
|
||||
}
|
||||
|
||||
const DRAFT_RETENTION_MIN_CHARS = 20
|
||||
const SHELL_SYNTAX_HIGHLIGHT_REF = 65_535
|
||||
|
||||
function randomIndex(count: number) {
|
||||
if (count <= 0) return 0
|
||||
@@ -340,6 +342,47 @@ export function Prompt(props: PromptProps) {
|
||||
})
|
||||
let disposed = false
|
||||
let pasteQueue = Promise.resolve()
|
||||
let syntaxHighlightVersion = 0
|
||||
|
||||
createEffect(() => {
|
||||
const mode = store.mode
|
||||
const content = store.prompt.text
|
||||
const style = syntax()
|
||||
const version = ++syntaxHighlightVersion
|
||||
|
||||
if (input && !input.isDestroyed) input.editBuffer.removeHighlightsByRef(SHELL_SYNTAX_HIGHLIGHT_REF)
|
||||
if (mode !== "shell" || !content || !input || input.isDestroyed) return
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
getTreeSitterClient()
|
||||
.highlightOnce(content, "bash")
|
||||
.then((result) => {
|
||||
if (version !== syntaxHighlightVersion || !result.highlights || !input || input.isDestroyed) return
|
||||
|
||||
const bytes = new TextEncoder().encode(content)
|
||||
const decoder = new TextDecoder()
|
||||
result.highlights.forEach(([start, end, group]) => {
|
||||
const styleId = style.getStyleId(group)
|
||||
if (styleId === null) return
|
||||
const before = decoder.decode(bytes.subarray(0, start))
|
||||
const highlighted = decoder.decode(bytes.subarray(start, end))
|
||||
input.editBuffer.addHighlightByCharRange({
|
||||
start: promptOffsetWidth(before) - (before.match(/\n/g)?.length ?? 0),
|
||||
end:
|
||||
promptOffsetWidth(before) +
|
||||
promptOffsetWidth(highlighted) -
|
||||
((before + highlighted).match(/\n/g)?.length ?? 0),
|
||||
styleId,
|
||||
priority: 0,
|
||||
hlRef: SHELL_SYNTAX_HIGHLIGHT_REF,
|
||||
})
|
||||
})
|
||||
renderer.requestRender()
|
||||
})
|
||||
.catch(() => {})
|
||||
}, 50)
|
||||
onCleanup(() => clearTimeout(timeout))
|
||||
})
|
||||
|
||||
function enqueuePaste(run: (changed: () => boolean) => Promise<void>) {
|
||||
pasteQueue = pasteQueue
|
||||
|
||||
@@ -1938,6 +1938,7 @@ function RevertMessage(props: {
|
||||
|
||||
function ShellMessage(props: { message: Extract<SessionMessageInfo, { type: "shell" }> }) {
|
||||
const theme = useTheme("elevated")
|
||||
const { currentSyntax: syntax } = useThemes()
|
||||
const output = createMemo(() => stripAnsi(props.message.output?.output.trim() ?? ""))
|
||||
|
||||
return (
|
||||
@@ -1952,7 +1953,16 @@ function ShellMessage(props: { message: Extract<SessionMessageInfo, { type: "she
|
||||
customBorderChars={SplitBorder.customBorderChars}
|
||||
borderColor={theme.background.default}
|
||||
>
|
||||
<text fg={theme.text.default}>$ {props.message.command}</text>
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text fg={theme.text.default}>$</text>
|
||||
<code
|
||||
conceal={false}
|
||||
fg={theme.text.default}
|
||||
filetype="bash"
|
||||
syntaxStyle={syntax()}
|
||||
content={props.message.command}
|
||||
/>
|
||||
</box>
|
||||
<Show when={output()}>
|
||||
<text fg={theme.text.subdued}>{output()}</text>
|
||||
</Show>
|
||||
@@ -2784,6 +2794,7 @@ const SHELL_DISPLAY_LIMIT = 1024 * 1024
|
||||
|
||||
function Shell(props: ToolProps) {
|
||||
const theme = useTheme()
|
||||
const { currentSyntax: syntax } = useThemes()
|
||||
const ctx = use()
|
||||
const client = useClient()
|
||||
const data = useData()
|
||||
@@ -2915,11 +2926,20 @@ function Shell(props: ToolProps) {
|
||||
fallback={
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text fg={theme.text.default}>{prompt()}</text>
|
||||
<text fg={theme.text.default}>{limitedInput()}</text>
|
||||
<code
|
||||
conceal={false}
|
||||
fg={theme.text.default}
|
||||
filetype="bash"
|
||||
syntaxStyle={syntax()}
|
||||
content={limitedInput()}
|
||||
/>
|
||||
</box>
|
||||
}
|
||||
>
|
||||
<Spinner color={color()}>{limitedInput()}</Spinner>
|
||||
<box flexDirection="row" gap={1}>
|
||||
<Spinner color={color()} />
|
||||
<code conceal={false} fg={color()} filetype="bash" syntaxStyle={syntax()} content={limitedInput()} />
|
||||
</box>
|
||||
</Show>
|
||||
<Show when={limitedOutput()}>
|
||||
<text fg={theme.text.subdued}>{limitedOutput()}</text>
|
||||
|
||||
Reference in New Issue
Block a user