From 212fde3162d067e1ea5725c4d79754e4d97bd162 Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Sun, 18 Oct 2026 13:11:47 +0530 Subject: [PATCH] fix: prevent crash when opening file:// links from LSP tooltips Open file:// links in LSP hover/signature docs in the editor, and guard the native openInBrowser against file URIs and launch failures. --- src/cm/lsp/tooltipExtensions.ts | 38 +++++++++++++++++++ .../android/com/foxdebug/system/System.java | 22 ++++++++++- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/cm/lsp/tooltipExtensions.ts b/src/cm/lsp/tooltipExtensions.ts index 7cab0e1c34..2e73a787d6 100644 --- a/src/cm/lsp/tooltipExtensions.ts +++ b/src/cm/lsp/tooltipExtensions.ts @@ -27,6 +27,7 @@ import { type ViewUpdate, } from "@codemirror/view"; import { highlightCode } from "@lezer/highlight"; +import { navigateToReference } from "components/referencesPanel/utils"; import type { HoverParams, ServerCapabilities, @@ -348,6 +349,41 @@ function closeHoverIfNeeded(view: EditorView): void { } } +// Matches `#12`, `#L12`, `#L12,5`, `#12:5` style line/column fragments. +const FILE_LINK_FRAGMENT = /^#L?(\d+)(?:[,:Cc](\d+))?/; + +/** + * Open `file://` links from LSP documentation in the editor instead of + * letting them bubble to the global handler, which would hand them to + * the system browser. + */ +function handleDocumentationLinks(dom: HTMLElement, view: EditorView): void { + dom.addEventListener("click", (event) => { + const link = (event.target as Element | null)?.closest?.("a[href]"); + if (!link) return; + const href = link.getAttribute("href") || ""; + if (!/^file:/i.test(href)) return; + + event.preventDefault(); + event.stopPropagation(); + + const hashIndex = href.indexOf("#"); + const uri = hashIndex === -1 ? href : href.slice(0, hashIndex); + const match = + hashIndex === -1 ? null : FILE_LINK_FRAGMENT.exec(href.slice(hashIndex)); + const position = { + line: match ? Math.max(0, Number(match[1]) - 1) : 0, + character: match?.[2] ? Math.max(0, Number(match[2]) - 1) : 0, + }; + + closeHoverIfNeeded(view); + void navigateToReference({ + uri, + range: { start: position, end: position }, + }); + }); +} + function hoverRequest(plugin: LSPPlugin, pos: number) { if (!clientHasCapability(plugin.client, "hoverProvider")) { return Promise.resolve(null); @@ -417,6 +453,7 @@ function lspTooltipSource( results[index].result.contents, ); } + handleDocumentationLinks(dom, view); return { dom }; }, above: true, @@ -618,6 +655,7 @@ function drawSignatureTooltip( const docs = dom.appendChild(document.createElement("div")); docs.className = "cm-lsp-signature-documentation cm-lsp-documentation"; docs.innerHTML = plugin.docToHTML(signature.documentation); + handleDocumentationLinks(docs, view); } } diff --git a/src/plugins/system/android/com/foxdebug/system/System.java b/src/plugins/system/android/com/foxdebug/system/System.java index 58e9f53335..146755da62 100644 --- a/src/plugins/system/android/com/foxdebug/system/System.java +++ b/src/plugins/system/android/com/foxdebug/system/System.java @@ -1682,8 +1682,26 @@ private void getAppInfo(CallbackContext callback) { } private void openInBrowser(String src, CallbackContext callback) { - Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(src)); - activity.startActivity(browserIntent); + if (src == null || src.isEmpty()) { + callback.error("No URL provided."); + return; + } + + Uri uri = Uri.parse(src); + if ("file".equalsIgnoreCase(uri.getScheme())) { + // file:// URIs can't leave the app (FileUriExposedException). + callback.error("Cannot open file URI in browser: " + src); + return; + } + + try { + Intent browserIntent = new Intent(Intent.ACTION_VIEW, uri); + activity.startActivity(browserIntent); + callback.success(); + } catch (Exception e) { + Log.e("System", "Failed to open URL: " + src, e); + callback.error(e.getMessage()); + } } private void launchApp(