Compare commits

...

1 Commits

Author SHA1 Message Date
Aiden Cline bf112cb49c fix(tui): open rendered links on click 2026-08-14 17:54:09 +00:00
3 changed files with 47 additions and 3 deletions
+8 -1
View File
@@ -44,6 +44,7 @@ import {
type TuiApp,
} from "./context/runtime"
import { DialogProvider, useDialog } from "./ui/dialog"
import { linkAt } from "./ui/link"
import { DialogIntegration } from "./component/dialog-integration"
import { ErrorComponent } from "./component/error-component"
import { PluginRouteMissing } from "./component/plugin-route-missing"
@@ -1270,7 +1271,13 @@ function App(props: { pair?: DialogPairCredentials }) {
evt.preventDefault()
evt.stopPropagation()
}}
onMouseUp={copyOnSelectEnabled() ? () => Selection.copy(renderer, toast, clipboard) : undefined}
onMouseUp={(event) => {
if (copyOnSelectEnabled()) Selection.copy(renderer, toast, clipboard)
if (event.defaultPrevented || event.button !== MouseButton.LEFT || event.isDragging) return
const href = linkAt(renderer.currentRenderBuffer, event.x, event.y)
if (!href) return
open(href).catch(() => {})
}}
>
<box
flexGrow={1}
+11 -2
View File
@@ -1,5 +1,5 @@
import type { JSX } from "solid-js"
import type { RGBA } from "@opentui/core"
import { getLinkId, type OptimizedBuffer, type RGBA } from "@opentui/core"
import open from "open"
export interface LinkProps {
@@ -24,7 +24,8 @@ export function Link(props: LinkProps) {
bg={props.bg}
width={props.width}
wrapMode={props.wrapMode}
onMouseUp={() => {
onMouseUp={(event) => {
event.stopPropagation()
open(props.href).catch(() => {})
}}
>
@@ -32,3 +33,11 @@ export function Link(props: LinkProps) {
</text>
)
}
export function linkAt(buffer: OptimizedBuffer, x: number, y: number) {
if (x < 0 || x >= buffer.width || y < 0 || y >= buffer.height) return
const id = getLinkId(buffer.buffers.attributes[y * buffer.width + x] ?? 0)
if (!id) return
const lib = buffer.lib as typeof buffer.lib & { linkGetUrl(id: number): string }
return lib.linkGetUrl(id) || undefined
}
+28
View File
@@ -0,0 +1,28 @@
import { expect, test } from "bun:test"
import { StyledText, TextRenderable } from "@opentui/core"
import { createTestRenderer, setRendererCapabilities } from "@opentui/core/testing"
import { linkAt } from "../../src/ui/link"
test("resolves terminal hyperlink metadata at the clicked cell", async () => {
const app = await createTestRenderer({ width: 60, height: 4 })
setRendererCapabilities(app.renderer, { hyperlinks: true })
const href = "file:///tmp/example.ts#L12"
app.renderer.root.add(
new TextRenderable(app.renderer, {
content: new StyledText([{ __isChunk: true, text: "example", link: { url: href } }]),
width: "100%",
height: 1,
}),
)
try {
await app.waitForFrame((frame) => frame.includes("example"))
const buffer = app.renderer.currentRenderBuffer
const links = Array.from({ length: buffer.width * buffer.height }, (_, index) =>
linkAt(buffer, index % buffer.width, Math.floor(index / buffer.width)),
)
expect(links).toContain(href)
} finally {
app.renderer.destroy()
}
})