From 3f9085a588c97756d1b45ba1b4a002a80916c776 Mon Sep 17 00:00:00 2001 From: Vedant <41702642+vp275@users.noreply.github.com> Date: Wed, 18 Mar 2026 19:51:56 +0530 Subject: [PATCH] feat: add OSC 8 clickable hyperlinks for file paths in export notifications (#1114) --- src/resources/extensions/gsd/export.ts | 6 +++--- src/resources/extensions/shared/format-utils.ts | 9 +++++++++ src/resources/extensions/shared/mod.ts | 1 + 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/resources/extensions/gsd/export.ts b/src/resources/extensions/gsd/export.ts index b87f25825..5a17f61c1 100644 --- a/src/resources/extensions/gsd/export.ts +++ b/src/resources/extensions/gsd/export.ts @@ -10,7 +10,7 @@ import { } from "./metrics.js"; import type { UnitMetrics } from "./metrics.js"; import { gsdRoot } from "./paths.js"; -import { formatDuration } from "../shared/mod.js"; +import { formatDuration, fileLink } from "../shared/mod.js"; /** * Write an export file directly, without requiring an ExtensionCommandContext. @@ -241,7 +241,7 @@ export async function handleExport(args: string, ctx: ExtensionCommandContext, b }; const outPath = join(exportDir, `export-${timestamp}.json`); writeFileSync(outPath, JSON.stringify(report, null, 2) + "\n", "utf-8"); - ctx.ui.notify(`Exported to ${outPath}`, "success"); + ctx.ui.notify(`Exported to ${fileLink(outPath)}`, "success"); } else { const totals = getProjectTotals(units); const phases = aggregateByPhase(units); @@ -285,6 +285,6 @@ export async function handleExport(args: string, ctx: ExtensionCommandContext, b const outPath = join(exportDir, `export-${timestamp}.md`); writeFileSync(outPath, md, "utf-8"); - ctx.ui.notify(`Exported to ${outPath}`, "success"); + ctx.ui.notify(`Exported to ${fileLink(outPath)}`, "success"); } } diff --git a/src/resources/extensions/shared/format-utils.ts b/src/resources/extensions/shared/format-utils.ts index 9aefd48a8..62e18abf1 100644 --- a/src/resources/extensions/shared/format-utils.ts +++ b/src/resources/extensions/shared/format-utils.ts @@ -105,6 +105,15 @@ export function formatDateShort(iso: string): string { } catch { return iso; } } +// ─── Hyperlinks ────────────────────────────────────────────────────────────── + +/** Wrap text in an OSC 8 hyperlink for terminals that support clickable links. */ +export function fileLink(filePath: string, displayText?: string): string { + const uri = `file://${filePath}`; + const label = displayText ?? filePath; + return `\x1b]8;;${uri}\x07${label}\x1b]8;;\x07`; +} + // ─── ANSI Stripping ─────────────────────────────────────────────────────────── /** Strip ANSI escape sequences from a string. */ diff --git a/src/resources/extensions/shared/mod.ts b/src/resources/extensions/shared/mod.ts index 5da977543..b80c3295d 100644 --- a/src/resources/extensions/shared/mod.ts +++ b/src/resources/extensions/shared/mod.ts @@ -19,6 +19,7 @@ export { fitColumns, sparkline, normalizeStringArray, + fileLink, } from "./format-utils.js"; export { shortcutDesc } from "./terminal.js";