From bdd30f8d0426797bf533815d51b40a59cae15304 Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Thu, 25 Jun 2026 12:54:29 +0530 Subject: [PATCH 1/9] feat: lazy load things which are low-risk --- src/cm/lsp/connectionState.js | 28 +++++++++++++++ src/components/lspInfoDialog/index.js | 31 +++-------------- src/lib/prettierFormatter.js | 2 +- src/lib/registerPrettierFormatter.js | 46 +++++++++++++++++++++++++ src/main.js | 49 +++++++++++++++++++-------- 5 files changed, 113 insertions(+), 43 deletions(-) create mode 100644 src/cm/lsp/connectionState.js create mode 100644 src/lib/registerPrettierFormatter.js diff --git a/src/cm/lsp/connectionState.js b/src/cm/lsp/connectionState.js new file mode 100644 index 000000000..29158a9f0 --- /dev/null +++ b/src/cm/lsp/connectionState.js @@ -0,0 +1,28 @@ +import serverRegistry from "./serverRegistry"; + +function getCurrentFileLanguage() { + try { + const file = window.editorManager?.activeFile; + if (!file || file.type !== "editor") return null; + return file.currentMode?.toLowerCase() || null; + } catch { + return null; + } +} + +function getServersForCurrentFile() { + const language = getCurrentFileLanguage(); + if (!language) return []; + + try { + return serverRegistry.getServersForLanguage(language); + } catch { + return []; + } +} + +function hasConnectedServers() { + return getServersForCurrentFile().length > 0; +} + +export { getServersForCurrentFile, hasConnectedServers }; diff --git a/src/components/lspInfoDialog/index.js b/src/components/lspInfoDialog/index.js index 31b57b381..190cbe0ec 100644 --- a/src/components/lspInfoDialog/index.js +++ b/src/components/lspInfoDialog/index.js @@ -1,7 +1,10 @@ import "./styles.scss"; import lspClientManager from "cm/lsp/clientManager"; +import { + getServersForCurrentFile, + hasConnectedServers, +} from "cm/lsp/connectionState"; import { getServerStats } from "cm/lsp/serverLauncher"; -import serverRegistry from "cm/lsp/serverRegistry"; import toast from "components/toast"; import actionStack from "lib/actionStack"; import restoreTheme from "lib/restoreTheme"; @@ -146,27 +149,6 @@ function getActiveClients() { } } -function getCurrentFileLanguage() { - try { - const file = window.editorManager?.activeFile; - if (!file || file.type !== "editor") return null; - return file.currentMode?.toLowerCase() || null; - } catch { - return null; - } -} - -function getServersForCurrentFile() { - const language = getCurrentFileLanguage(); - if (!language) return []; - - try { - return serverRegistry.getServersForLanguage(language); - } catch { - return []; - } -} - function getServerStatus(serverId) { const activeClients = getActiveClients(); const client = activeClients.find((c) => c.server?.id === serverId); @@ -693,10 +675,5 @@ function showLspInfoDialog() { } } -function hasConnectedServers() { - const relevantServers = getServersForCurrentFile(); - return relevantServers.length > 0; -} - export { addLspLog, getLspLogs, hasConnectedServers, showLspInfoDialog }; export default showLspInfoDialog; diff --git a/src/lib/prettierFormatter.js b/src/lib/prettierFormatter.js index 4b93fa0f6..02b1b7068 100644 --- a/src/lib/prettierFormatter.js +++ b/src/lib/prettierFormatter.js @@ -113,7 +113,7 @@ export function registerPrettierFormatter() { ); } -async function formatActiveFileWithPrettier() { +export async function formatActiveFileWithPrettier() { const file = editorManager?.activeFile; const editor = editorManager?.editor; if (!file || file.type !== "editor" || !editor) return false; diff --git a/src/lib/registerPrettierFormatter.js b/src/lib/registerPrettierFormatter.js new file mode 100644 index 000000000..b89fb1d1c --- /dev/null +++ b/src/lib/registerPrettierFormatter.js @@ -0,0 +1,46 @@ +const PRETTIER_ID = "prettier"; +const PRETTIER_NAME = "Prettier"; + +const SUPPORTED_EXTENSIONS = [ + "js", + "cjs", + "mjs", + "jsx", + "ts", + "tsx", + "json", + "json5", + "css", + "scss", + "less", + "html", + "htm", + "vue", + "md", + "markdown", + "mdx", + "yaml", + "yml", + "graphql", + "gql", +]; + +export function registerPrettierFormatter() { + if (!window?.acode) return; + const alreadyRegistered = acode.formatters.some( + ({ id }) => id === PRETTIER_ID, + ); + if (alreadyRegistered) return; + + acode.registerFormatter( + PRETTIER_ID, + SUPPORTED_EXTENSIONS, + async () => { + const { formatActiveFileWithPrettier } = await import( + /* webpackChunkName: "prettierFormatter" */ "lib/prettierFormatter" + ); + return formatActiveFileWithPrettier(); + }, + PRETTIER_NAME, + ); +} diff --git a/src/main.js b/src/main.js index bc90e0f86..3904047bd 100644 --- a/src/main.js +++ b/src/main.js @@ -15,6 +15,7 @@ import "handlers/editorWorkaround"; import fsOperation from "fileSystem"; import sidebarApps from "sidebarApps"; import { setKeyBindings } from "cm/commandRegistry"; +import { hasConnectedServers } from "cm/lsp/connectionState"; import { getModeForPath, getModes, @@ -22,12 +23,9 @@ import { initModes, } from "cm/modelist"; import Contextmenu from "components/contextmenu"; -import { hasConnectedServers } from "components/lspInfoDialog"; import Sidebar from "components/sidebar"; -import { TerminalManager } from "components/terminal"; import tile from "components/tile"; import toast from "components/toast"; -import tutorial from "components/tutorial"; import confirm from "dialogs/confirm"; import intentHandler, { processPendingIntents } from "handlers/intent"; import keyboardHandler, { keydownState } from "handlers/keyboard"; @@ -38,8 +36,6 @@ import actionStack from "lib/actionStack"; import adRewards from "lib/adRewards"; import ajax from "lib/ajax"; import applySettings from "lib/applySettings"; -import checkFiles from "lib/checkFiles"; -import checkPluginsUpdate from "lib/checkPluginsUpdate"; import { canSaveFile } from "lib/commands"; import config from "lib/config"; import EditorFile from "lib/editorFile"; @@ -51,14 +47,11 @@ import loadPlugins from "lib/loadPlugins"; import Logger from "lib/logger"; import notificationManager from "lib/notificationManager"; import openFolder, { addedFolder } from "lib/openFolder"; -import { registerPrettierFormatter } from "lib/prettierFormatter"; +import { registerPrettierFormatter } from "lib/registerPrettierFormatter"; import restoreFiles from "lib/restoreFiles"; import settings from "lib/settings"; import startAd, { hideAd } from "lib/startAd"; import mustache from "mustache"; -import plugins from "pages/plugins"; -import openWelcomeTab from "pages/welcome"; -import otherSettings from "settings/appSettings"; import themes from "theme/list"; import { initHighlighting } from "utils/codeHighlight"; import { getEncoding, initEncodings } from "utils/encodings"; @@ -398,6 +391,9 @@ async function onDeviceReady() { }, ); } + const { default: checkPluginsUpdate } = await import( + /* webpackChunkName: "checkPluginsUpdate" */ "lib/checkPluginsUpdate" + ); checkPluginsUpdate() .then((updates) => { if (!updates.length) return; @@ -406,7 +402,10 @@ async function onDeviceReady() { `${updates.length} plugin${updates.length > 1 ? "s" : ""} ${updates.length > 1 ? "have" : "has"} new version${updates.length > 1 ? "s" : ""} available.`, { icon: "extension", - action: () => { + action: async () => { + const { default: plugins } = await import( + /* webpackChunkName: "plugins" */ "pages/plugins" + ); plugins(updates); }, }, @@ -635,6 +634,9 @@ async function loadApp() { window.log("info", "Started app and its services..."); if (!files.length) { + const { default: openWelcomeTab } = await import( + /* webpackChunkName: "welcome" */ "pages/welcome" + ); openWelcomeTab(); } @@ -678,9 +680,16 @@ async function loadApp() { initFileList(); - TerminalManager.restorePersistedSessions().catch((error) => { - console.error("Terminal restoration failed:", error); - }); + import(/* webpackChunkName: "terminal" */ "components/terminal").then( + ({ TerminalManager }) => { + TerminalManager.restorePersistedSessions().catch((error) => { + console.error("Terminal restoration failed:", error); + }); + }, + (error) => { + console.error("Failed to load terminal module:", error); + }, + ); /** * @@ -844,8 +853,13 @@ function createFileMenu({ top, bottom, toggler }) { return $menu; } -function showTutorials() { +async function showTutorials() { if (window.innerWidth > 750) { + const [{ default: tutorial }, { default: otherSettings }] = + await Promise.all([ + import(/* webpackChunkName: "tutorial" */ "components/tutorial"), + import(/* webpackChunkName: "appSettings" */ "settings/appSettings"), + ]); tutorial("quicktools-tutorials", (hide) => { const onclick = () => { otherSettings(); @@ -890,7 +904,12 @@ async function pauseHandler() { function resumeHandler() { adRewards.handleResume(); if (!settings.value.checkFiles) return; - checkFiles(); + import(/* webpackChunkName: "checkFiles" */ "lib/checkFiles").then( + ({ default: checkFiles }) => checkFiles(), + (error) => { + console.error("Failed to load checkFiles module:", error); + }, + ); } function createAceModelistCompatModule() { From 7247a6b781f31110aba55cff6b2f4b4f5c6f9d43 Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Thu, 25 Jun 2026 13:10:19 +0530 Subject: [PATCH 2/9] few more low risk ones --- src/lib/acode.js | 38 +++++++------ src/lib/commands.js | 106 ++++++++++++++++++++++++----------- src/lib/openFolder.js | 11 +++- src/main.js | 9 +-- src/pages/welcome/welcome.js | 1 - 5 files changed, 106 insertions(+), 59 deletions(-) diff --git a/src/lib/acode.js b/src/lib/acode.js index da96ae52e..cdc28e208 100644 --- a/src/lib/acode.js +++ b/src/lib/acode.js @@ -47,7 +47,6 @@ import prompt from "dialogs/prompt"; import select from "dialogs/select"; import { addIntentHandler, removeIntentHandler } from "handlers/intent"; import keyboardHandler from "handlers/keyboard"; -import purchaseListener from "handlers/purchase"; import windowResize from "handlers/windowResize"; import actionStack from "lib/actionStack"; import commands from "lib/commands"; @@ -67,7 +66,6 @@ import projects from "lib/projects"; import selectionMenu from "lib/selectionMenu"; import appSettings from "lib/settings"; import FileBrowser from "pages/fileBrowser"; -import formatterSettings from "settings/formatterSettings"; import ThemeBuilder from "theme/builder"; import themes from "theme/list"; import Color from "utils/color"; @@ -582,20 +580,25 @@ class Acode { if (isPaid && !purchaseToken) { if (!product) throw new Error("Product not found"); - return helpers.checkAPIStatus().then((apiStatus) => { - if (!apiStatus) { - alert(strings.error, strings.api_error); - return; - } - - iap.setPurchaseUpdatedListener( - ...purchaseListener(onpurchase, onerror), - ); - return helpers.promisify( - iap.purchase, - product.productId, - ); - }); + return helpers + .checkAPIStatus() + .then(async (apiStatus) => { + if (!apiStatus) { + alert(strings.error, strings.api_error); + return; + } + + const { default: purchaseListener } = await import( + /* webpackChunkName: "purchaseHandler" */ "handlers/purchase" + ); + iap.setPurchaseUpdatedListener( + ...purchaseListener(onpurchase, onerror), + ); + return helpers.promisify( + iap.purchase, + product.productId, + ); + }); } }) .then(() => { @@ -792,6 +795,9 @@ class Acode { } if (selectIfNull) { + const { default: formatterSettings } = await import( + /* webpackChunkName: "formatterSettings" */ "settings/formatterSettings" + ); formatterSettings(modeName); this.#afterSelectFormatter(modeName); } else { diff --git a/src/lib/commands.js b/src/lib/commands.js index 9c7aa21b6..731a4e930 100644 --- a/src/lib/commands.js +++ b/src/lib/commands.js @@ -1,27 +1,11 @@ import fsOperation from "fileSystem"; import { selectAll } from "@codemirror/commands"; import Sidebar from "components/sidebar"; -import { TerminalManager } from "components/terminal"; -import color from "dialogs/color"; import confirm from "dialogs/confirm"; import prompt from "dialogs/prompt"; import select from "dialogs/select"; import actions from "handlers/quickTools"; import recents from "lib/recents"; -import About from "pages/about"; -import FileBrowser from "pages/fileBrowser"; -import plugins from "pages/plugins"; -import Problems from "pages/problems/problems"; -import openWelcomeTab from "pages/welcome/welcome"; -import changeEncoding from "palettes/changeEncoding"; -import changeMode from "palettes/changeMode"; -import changeTheme from "palettes/changeTheme"; -import commandPalette from "palettes/commandPalette"; -import findFile from "palettes/findFile"; -import browser from "plugins/browser"; -import help from "settings/helpSettings"; -import mainSettings from "settings/mainSettings"; -import { runAllTests } from "test/tester"; import { getColorRange } from "utils/color/regex"; import helpers from "utils/helpers"; import Url from "utils/Url"; @@ -35,6 +19,10 @@ import saveState from "./saveState"; import appSettings from "./settings"; import showFileInfo from "./showFileInfo"; +const loadFileBrowser = async () => + (await import(/* webpackChunkName: "fileBrowser" */ "pages/fileBrowser")) + .default; + function getTabCloseSelectionOptions() { return { unsavedWarning: @@ -140,6 +128,9 @@ async function closeTabs(files, options = {}) { export default { async "run-tests"() { + const { runAllTests } = await import( + /* webpackChunkName: "tester" */ "test/tester" + ); await runAllTests(); }, async "close-all-tabs"() { @@ -187,7 +178,10 @@ export default { if (!appSettings.value.checkFiles) return; checkFiles(); }, - "command-palette"() { + async "command-palette"() { + const { default: commandPalette } = await import( + /* webpackChunkName: "commandPalette" */ "palettes/commandPalette" + ); commandPalette(); }, "disable-fullscreen"() { @@ -198,7 +192,10 @@ export default { app.classList.add("fullscreen-mode"); this["resize-editor"](); }, - encoding() { + async encoding() { + const { default: changeEncoding } = await import( + /* webpackChunkName: "changeEncoding" */ "palettes/changeEncoding" + ); changeEncoding(); }, exit() { @@ -207,10 +204,14 @@ export default { "edit-with"() { editorManager.activeFile.editWith(); }, - "find-file"() { + async "find-file"() { + const { default: findFile } = await import( + /* webpackChunkName: "findFile" */ "palettes/findFile" + ); findFile(); }, - files() { + async files() { + const FileBrowser = await loadFileBrowser(); FileBrowser("both", strings["file browser"]) .then(FileBrowser.open) .catch(FileBrowser.openError); @@ -256,30 +257,44 @@ export default { editorManager.files[fileIndex].makeActive(); }, - open(page) { + async open(page) { switch (page) { case "settings": - mainSettings(); + ( + await import( + /* webpackChunkName: "mainSettings" */ "settings/mainSettings" + ) + ).default(); break; case "help": - help(); + ( + await import( + /* webpackChunkName: "helpSettings" */ "settings/helpSettings" + ) + ).default(); break; case "problems": - Problems(); + ( + await import( + /* webpackChunkName: "problems" */ "pages/problems/problems" + ) + ).default(); break; case "plugins": - plugins(); + ( + await import(/* webpackChunkName: "plugins" */ "pages/plugins") + ).default(); break; case "file_browser": - FileBrowser(); + (await loadFileBrowser())(); break; case "about": - About(); + (await import(/* webpackChunkName: "about" */ "pages/about")).default(); break; default: @@ -290,14 +305,16 @@ export default { "open-with"() { editorManager.activeFile.openWith(); }, - "open-file"() { + async "open-file"() { editorManager.editor.contentDOM.blur(); + const FileBrowser = await loadFileBrowser(); FileBrowser("file") .then(FileBrowser.openFile) .catch(FileBrowser.openFileError); }, - "open-folder"() { + async "open-folder"() { editorManager.editor.contentDOM.blur(); + const FileBrowser = await loadFileBrowser(); FileBrowser("folder") .then(FileBrowser.openFolder) .catch(FileBrowser.openFolderError); @@ -338,7 +355,10 @@ export default { // TODO : Codemirror //editorManager.editor.resize(true); }, - "open-inapp-browser"(url) { + async "open-inapp-browser"(url) { + const { default: browser } = await import( + /* webpackChunkName: "browserPlugin" */ "plugins/browser" + ); browser.open(url); }, run() { @@ -433,13 +453,22 @@ export default { helpers.error(error); } }, - syntax() { + async syntax() { + const { default: changeMode } = await import( + /* webpackChunkName: "changeMode" */ "palettes/changeMode" + ); changeMode(); }, - "change-app-theme"() { + async "change-app-theme"() { + const { default: changeTheme } = await import( + /* webpackChunkName: "changeTheme" */ "palettes/changeTheme" + ); changeTheme("app"); }, - "change-editor-theme"() { + async "change-editor-theme"() { + const { default: changeTheme } = await import( + /* webpackChunkName: "changeTheme" */ "palettes/changeTheme" + ); changeTheme("editor"); }, "toggle-fullscreen"() { @@ -472,6 +501,9 @@ export default { const wasFocused = editorManager.activeFile.focused; let res; try { + const { default: color } = await import( + /* webpackChunkName: "colorDialog" */ "dialogs/color" + ); res = await color(defaultColor, () => { if (wasFocused) { editor.focus(); @@ -637,6 +669,9 @@ Additional Info: }, async "new-terminal"() { try { + const { TerminalManager } = await import( + /* webpackChunkName: "terminal" */ "components/terminal" + ); await TerminalManager.createServerTerminal(); } catch (error) { console.error("Failed to create terminal:", error); @@ -649,7 +684,10 @@ Additional Info: ); RunningProcesses(); }, - welcome() { + async welcome() { + const { default: openWelcomeTab } = await import( + /* webpackChunkName: "welcome" */ "pages/welcome/welcome" + ); openWelcomeTab(); }, async "toggle-inspector"() { diff --git a/src/lib/openFolder.js b/src/lib/openFolder.js index 6b96c74f9..7f094282e 100644 --- a/src/lib/openFolder.js +++ b/src/lib/openFolder.js @@ -3,7 +3,6 @@ import sidebarApps from "sidebarApps"; import collapsableList from "components/collapsableList"; import FileTree from "components/fileTree"; import Sidebar from "components/sidebar"; -import { TerminalManager } from "components/terminal"; import tile from "components/tile"; import toast from "components/toast"; import alert from "dialogs/alert"; @@ -11,7 +10,6 @@ import confirm from "dialogs/confirm"; import prompt from "dialogs/prompt"; import select from "dialogs/select"; import escapeStringRegexp from "escape-string-regexp"; -import FileBrowser from "pages/fileBrowser"; import helpers from "utils/helpers"; import Path from "utils/Path"; import Uri from "utils/Uri"; @@ -29,6 +27,10 @@ const isAcodeTerminalPublicSafUri = (value = "") => const isTerminalSafUri = (value = "") => isTermuxSafUri(value) || isAcodeTerminalPublicSafUri(value); +const loadFileBrowser = async () => + (await import(/* webpackChunkName: "fileBrowser" */ "pages/fileBrowser")) + .default; + const getTerminalPaths = () => { const packageName = window.BuildInfo?.packageName || "com.foxdebug.acode"; const dataDir = `/data/user/0/${packageName}`; @@ -539,6 +541,9 @@ function execOperation(type, action, url, $target, name) { async function openInTerminal() { try { + const { TerminalManager } = await import( + /* webpackChunkName: "terminal" */ "components/terminal" + ); const prootPath = convertToProotPath(url); const terminal = await TerminalManager.createTerminal({ name: `Terminal - ${name}`, @@ -897,6 +902,7 @@ function execOperation(type, action, url, $target, name) { async function insertFile() { startLoading(); try { + const FileBrowser = await loadFileBrowser(); const file = await FileBrowser("file", strings["insert file"]); const sourceFs = fsOperation(file.url); const data = await sourceFs.readFile(); @@ -923,6 +929,7 @@ function execOperation(type, action, url, $target, name) { } async function open() { + const FileBrowser = await loadFileBrowser(); FileBrowser.openFolder({ url, name, diff --git a/src/main.js b/src/main.js index 3904047bd..773df510f 100644 --- a/src/main.js +++ b/src/main.js @@ -6,6 +6,7 @@ import "res/icons/style.css"; import "res/file-icons/style.css"; import "styles/overrideAceStyle.scss"; import "styles/wideScreen.scss"; +import "pages/welcome/welcome.scss"; import "lib/polyfill"; import "cm/supportedModes"; @@ -36,6 +37,7 @@ import actionStack from "lib/actionStack"; import adRewards from "lib/adRewards"; import ajax from "lib/ajax"; import applySettings from "lib/applySettings"; +import checkFiles from "lib/checkFiles"; import { canSaveFile } from "lib/commands"; import config from "lib/config"; import EditorFile from "lib/editorFile"; @@ -904,12 +906,7 @@ async function pauseHandler() { function resumeHandler() { adRewards.handleResume(); if (!settings.value.checkFiles) return; - import(/* webpackChunkName: "checkFiles" */ "lib/checkFiles").then( - ({ default: checkFiles }) => checkFiles(), - (error) => { - console.error("Failed to load checkFiles module:", error); - }, - ); + checkFiles(); } function createAceModelistCompatModule() { diff --git a/src/pages/welcome/welcome.js b/src/pages/welcome/welcome.js index 0ede6cf44..d3958fd1e 100644 --- a/src/pages/welcome/welcome.js +++ b/src/pages/welcome/welcome.js @@ -1,4 +1,3 @@ -import "./welcome.scss"; import { getResolvedKeyBindings } from "cm/commandRegistry"; import Logo from "components/logo"; import config from "lib/config"; From 832a3c1efe4d77f48b80bc37afe673fac60ebc81 Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Thu, 25 Jun 2026 13:31:28 +0530 Subject: [PATCH 3/9] fix --- src/lib/commands.js | 5 +---- src/lib/lazyImports.js | 5 +++++ src/lib/openFolder.js | 5 +---- src/lib/prettierFormatter.js | 43 ------------------------------------ src/main.js | 1 + 5 files changed, 8 insertions(+), 51 deletions(-) create mode 100644 src/lib/lazyImports.js diff --git a/src/lib/commands.js b/src/lib/commands.js index 731a4e930..2b2a1cc80 100644 --- a/src/lib/commands.js +++ b/src/lib/commands.js @@ -12,6 +12,7 @@ import Url from "utils/Url"; import checkFiles from "./checkFiles"; import config from "./config"; import EditorFile from "./editorFile"; +import { loadFileBrowser } from "./lazyImports"; import openFile from "./openFile"; import openFolder from "./openFolder"; import run from "./run"; @@ -19,10 +20,6 @@ import saveState from "./saveState"; import appSettings from "./settings"; import showFileInfo from "./showFileInfo"; -const loadFileBrowser = async () => - (await import(/* webpackChunkName: "fileBrowser" */ "pages/fileBrowser")) - .default; - function getTabCloseSelectionOptions() { return { unsavedWarning: diff --git a/src/lib/lazyImports.js b/src/lib/lazyImports.js new file mode 100644 index 000000000..d296f1a9e --- /dev/null +++ b/src/lib/lazyImports.js @@ -0,0 +1,5 @@ +export async function loadFileBrowser() { + return ( + await import(/* webpackChunkName: "fileBrowser" */ "pages/fileBrowser") + ).default; +} diff --git a/src/lib/openFolder.js b/src/lib/openFolder.js index 7f094282e..312c0f52f 100644 --- a/src/lib/openFolder.js +++ b/src/lib/openFolder.js @@ -16,6 +16,7 @@ import Uri from "utils/Uri"; import Url from "utils/Url"; import config from "./config"; import * as FileList from "./fileList"; +import { loadFileBrowser } from "./lazyImports"; import openFile from "./openFile"; import recents from "./recents"; import appSettings from "./settings"; @@ -27,10 +28,6 @@ const isAcodeTerminalPublicSafUri = (value = "") => const isTerminalSafUri = (value = "") => isTermuxSafUri(value) || isAcodeTerminalPublicSafUri(value); -const loadFileBrowser = async () => - (await import(/* webpackChunkName: "fileBrowser" */ "pages/fileBrowser")) - .default; - const getTerminalPaths = () => { const packageName = window.BuildInfo?.packageName || "com.foxdebug.acode"; const dataDir = `/data/user/0/${packageName}`; diff --git a/src/lib/prettierFormatter.js b/src/lib/prettierFormatter.js index 02b1b7068..fb1a3b86c 100644 --- a/src/lib/prettierFormatter.js +++ b/src/lib/prettierFormatter.js @@ -14,8 +14,6 @@ import prettier from "prettier/standalone"; import helpers from "utils/helpers"; import Url from "utils/Url"; -const PRETTIER_ID = "prettier"; -const PRETTIER_NAME = "Prettier"; const CONFIG_FILENAMES = [ ".prettierrc", ".prettierrc.json", @@ -72,47 +70,6 @@ const MODE_TO_PARSER = { javascript: "babel", }; -const SUPPORTED_EXTENSIONS = [ - "js", - "cjs", - "mjs", - "jsx", - "ts", - "tsx", - "json", - "json5", - "css", - "scss", - "less", - "html", - "htm", - "vue", - "md", - "markdown", - "mdx", - "yaml", - "yml", - "graphql", - "gql", -]; - -/** - * Register Prettier formatter with Acode instance - */ -export function registerPrettierFormatter() { - if (!window?.acode) return; - const alreadyRegistered = acode.formatters.some( - ({ id }) => id === PRETTIER_ID, - ); - if (alreadyRegistered) return; - acode.registerFormatter( - PRETTIER_ID, - SUPPORTED_EXTENSIONS, - () => formatActiveFileWithPrettier(), - PRETTIER_NAME, - ); -} - export async function formatActiveFileWithPrettier() { const file = editorManager?.activeFile; const editor = editorManager?.editor; diff --git a/src/main.js b/src/main.js index 773df510f..004d1fe07 100644 --- a/src/main.js +++ b/src/main.js @@ -6,6 +6,7 @@ import "res/icons/style.css"; import "res/file-icons/style.css"; import "styles/overrideAceStyle.scss"; import "styles/wideScreen.scss"; +// Editor tabs use a shadow root that only links build/main.css. import "pages/welcome/welcome.scss"; import "lib/polyfill"; From abbbaf2e012cc36dc9a1da400b1032a9b88441eb Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Fri, 26 Jun 2026 17:11:30 +0530 Subject: [PATCH 4/9] keep quicktools toggler hidden by default so it doesnt flash due to lazy load --- src/components/quickTools/footer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/quickTools/footer.js b/src/components/quickTools/footer.js index 0ea0a0aef..68460f013 100644 --- a/src/components/quickTools/footer.js +++ b/src/components/quickTools/footer.js @@ -70,7 +70,7 @@ export const $footer = ; /**@type {HTMLElement} */ export const $toggler = ( ); From 5320e7d8858f2ef4fcf1275676db303627369131 Mon Sep 17 00:00:00 2001 From: Ajit Kumar Date: Fri, 26 Jun 2026 17:24:24 +0530 Subject: [PATCH 5/9] fix: logo and some improvements (#2397) Co-authored-by: Ajit Kumar Co-authored-by: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> --- rspack.config.js | 7 +- src/components/logo/style.scss | 14 +- src/dialogs/style.scss | 19 +- src/lib/languageModeRecommendations.js | 13 +- src/pages/welcome/welcome.js | 4 +- src/pages/welcome/welcome.scss | 416 +++++++++++++------------ src/test/sanity.tests.js | 19 ++ 7 files changed, 268 insertions(+), 224 deletions(-) diff --git a/rspack.config.js b/rspack.config.js index 4cf1ebf9a..c8e64e72d 100644 --- a/rspack.config.js +++ b/rspack.config.js @@ -94,10 +94,15 @@ module.exports = (env, options) => { resourceQuery: /raw/, type: 'asset/source', }, + { + test: /\.(png|svg|jpg|jpeg|ico|webp)(\?.*)?$/, + resourceQuery: /inline/, + type: 'asset/inline', + }, // Asset files { test: /\.(png|svg|jpg|jpeg|ico|ttf|webp|eot|woff|webm|mp4|wav)(\?.*)?$/, - resourceQuery: { not: [/raw/] }, + resourceQuery: { not: [/raw/, /inline/] }, type: 'asset/resource', }, // Regular CSS/SCSS files diff --git a/src/components/logo/style.scss b/src/components/logo/style.scss index 90f9f8a3c..01568da03 100644 --- a/src/components/logo/style.scss +++ b/src/components/logo/style.scss @@ -6,7 +6,7 @@ margin: 0 auto; &::after { - content: ''; + content: ""; position: absolute; top: 0; left: 0; @@ -19,13 +19,19 @@ } &::before { - content: ''; + content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 100%; - background: radial-gradient(circle, rgb(68, 153, 254, 0.5), rgb(68, 153, 254, 0.1), rgb(68, 153, 254, 0), rgb(68, 153, 254, 0)); + background: radial-gradient( + circle, + rgb(68, 153, 254, 0.5), + rgb(68, 153, 254, 0.1), + rgb(68, 153, 254, 0), + rgb(68, 153, 254, 0) + ); overflow: visible; } -} \ No newline at end of file +} diff --git a/src/dialogs/style.scss b/src/dialogs/style.scss index 2db23f2bb..d6a6f14cd 100644 --- a/src/dialogs/style.scss +++ b/src/dialogs/style.scss @@ -104,12 +104,12 @@ } } - &+.mask { + & + .mask { z-index: 111; background-color: rgb(0, 0, 0); opacity: 0.4; - ~.mask { + ~ .mask { opacity: 0; } } @@ -119,7 +119,7 @@ transform: translate(-50%, -50%) scale(0.95) translateZ(0); opacity: 0; - &+.mask { + & + .mask { opacity: 0; } } @@ -212,7 +212,10 @@ user-select: none; color: var(--popup-text-color, #333); opacity: 0.5; - transition: opacity 0.15s, background-color 0.15s, color 0.15s; + transition: + opacity 0.15s, + background-color 0.15s, + color 0.15s; &:hover { opacity: 0.8; @@ -283,13 +286,13 @@ align-items: center; .loader { - width: 30px; - height: 30px; + width: 24px; + height: 24px; color: rgb(153, 153, 255); - color: var(--primary-color); + color: var(--popup-active-color); display: flex; flex-shrink: 0; - margin: 0 10px; + margin-left: 10px; svg { width: 100%; diff --git a/src/lib/languageModeRecommendations.js b/src/lib/languageModeRecommendations.js index 48143e177..d534e2bf1 100644 --- a/src/lib/languageModeRecommendations.js +++ b/src/lib/languageModeRecommendations.js @@ -11,15 +11,16 @@ function withSupportedEditor(url) { return `${url}${separator}supported_editor=${config.SUPPORTED_EDITOR}`; } -function getSearchKeyword(filename) { - const ext = Path.extname(filename || "") - .replace(/^\./, "") +export function getLanguageModeRecommendationSearchKeyword(filename) { + const basename = Path.basename(filename || "") .trim() .toLowerCase(); + const ext = Path.extname(basename).replace(/^\./, "").trim().toLowerCase(); + const keyword = ext || (basename.startsWith(".") ? basename.slice(1) : ""); - if (!/^[a-z0-9][a-z0-9._+-]*$/.test(ext)) return ""; + if (!/^[a-z0-9][a-z0-9._+-]*$/.test(keyword)) return ""; - return ext; + return keyword; } function getIssueUrl(keyword) { @@ -89,7 +90,7 @@ class LanguageModeRecommendations { const filename = file.filename || ""; if (!hasPlainTextFallback(modeInfo, filename)) return; - const keyword = getSearchKeyword(filename); + const keyword = getLanguageModeRecommendationSearchKeyword(filename); if ( !keyword || this.notifiedKeywords.has(keyword) || diff --git a/src/pages/welcome/welcome.js b/src/pages/welcome/welcome.js index d3958fd1e..6bccb37a5 100644 --- a/src/pages/welcome/welcome.js +++ b/src/pages/welcome/welcome.js @@ -1,5 +1,5 @@ import { getResolvedKeyBindings } from "cm/commandRegistry"; -import Logo from "components/logo"; +import logoSrc from "components/logo/logo.png?inline"; import config from "lib/config"; import EditorFile from "lib/editorFile"; @@ -44,7 +44,7 @@ function createWelcomeContent() {
{/* Hero Section */}
- +

Welcome to Acode

Powerful code editor for Android

diff --git a/src/pages/welcome/welcome.scss b/src/pages/welcome/welcome.scss index 5a1a95840..36e7f2c98 100644 --- a/src/pages/welcome/welcome.scss +++ b/src/pages/welcome/welcome.scss @@ -1,232 +1,242 @@ #welcome-tab { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + min-height: 100%; + height: auto; + padding: 32px 24px; + overflow-y: auto; + overflow-x: hidden; + background-color: var(--secondary-color); + + // Hero Header + .welcome-header { + display: flex; + align-items: center; + gap: 16px; + margin-bottom: 48px; + + .logo { + width: 48px; + height: auto; + max-height: 48px; + flex-shrink: 0; + } + + .welcome-header-text { + h1 { + font-size: 20px; + font-weight: 600; + color: var(--primary-text-color); + margin: 0 0 4px 0; + letter-spacing: -0.3px; + } + + .tagline { + font-size: 13px; + color: color-mix(in srgb, var(--secondary-text-color) 60%, transparent); + margin: 0; + } + } + } + + // Section Styles + .welcome-section { + width: 100%; + max-width: 400px; + margin-bottom: 32px; + + .section-label { + font-size: 11px; + font-weight: 600; + letter-spacing: 1px; + color: color-mix(in srgb, var(--secondary-text-color) 50%, transparent); + margin: 0 0 12px 0; + padding-left: 4px; + } + } + + // Action List + .action-list { display: flex; flex-direction: column; + gap: 2px; + } + + .action-row { + display: flex; align-items: center; - justify-content: flex-start; - min-height: 100%; - height: auto; - padding: 32px 24px; - overflow-y: auto; - overflow-x: hidden; - background-color: var(--secondary-color); - - // Hero Header - .welcome-header { - display: flex; - align-items: center; - gap: 16px; - margin-bottom: 48px; - - .logo { - width: 64px; - height: 64px; - flex-shrink: 0; - - &::after { - background-size: 48px; - } - - &::before { - background: radial-gradient(circle, - color-mix(in srgb, var(--button-background-color) 30%, transparent) 0%, - transparent 70%); - } - } - - .welcome-header-text { - h1 { - font-size: 20px; - font-weight: 600; - color: var(--primary-text-color); - margin: 0 0 4px 0; - letter-spacing: -0.3px; - } - - .tagline { - font-size: 13px; - color: color-mix(in srgb, var(--secondary-text-color) 60%, transparent); - margin: 0; - } - } + gap: 12px; + padding: 10px 12px; + border-radius: 6px; + cursor: pointer; + transition: background-color 0.15s ease; + + &:hover, + &:active { + background-color: color-mix( + in srgb, + var(--popup-background-color) 40%, + transparent + ); } - // Section Styles - .welcome-section { - width: 100%; - max-width: 400px; - margin-bottom: 32px; - - .section-label { - font-size: 11px; - font-weight: 600; - letter-spacing: 1px; - color: color-mix(in srgb, var(--secondary-text-color) 50%, transparent); - margin: 0 0 12px 0; - padding-left: 4px; - } + .icon { + font-size: 16px; + color: color-mix(in srgb, var(--secondary-text-color) 70%, transparent); + width: 20px; + text-align: center; } - // Action List - .action-list { - display: flex; - flex-direction: column; - gap: 2px; + .action-label { + flex: 1; + font-size: 14px; + color: var(--secondary-text-color); } - .action-row { - display: flex; - align-items: center; - gap: 12px; - padding: 10px 12px; - border-radius: 6px; - cursor: pointer; - transition: background-color 0.15s ease; - - &:hover, - &:active { - background-color: color-mix(in srgb, var(--popup-background-color) 40%, transparent); - } - - .icon { - font-size: 16px; - color: color-mix(in srgb, var(--secondary-text-color) 70%, transparent); - width: 20px; - text-align: center; - } - - .action-label { - flex: 1; - font-size: 14px; - color: var(--secondary-text-color); - } - - .action-shortcut { - font-size: 12px; - font-family: 'Roboto Mono', monospace; - color: color-mix(in srgb, var(--secondary-text-color) 40%, transparent); - letter-spacing: 0.5px; - } + .action-shortcut { + font-size: 12px; + font-family: "Roboto Mono", monospace; + color: color-mix(in srgb, var(--secondary-text-color) 40%, transparent); + letter-spacing: 0.5px; + } + } + + // Links Section + .welcome-links { + margin-top: 16px; + + .link-row { + display: flex; + flex-wrap: wrap; + gap: 8px; + justify-content: flex-start; } - // Links Section - .welcome-links { - margin-top: 16px; - - .link-row { - display: flex; - flex-wrap: wrap; - gap: 8px; - justify-content: flex-start; - } - - .link-item { - display: inline-flex; - align-items: center; - gap: 8px; - padding: 10px 16px; - border-radius: 8px; - text-decoration: none; - color: var(--secondary-text-color); - font-size: 13px; - font-weight: 500; - transition: all 0.15s ease; - background-color: color-mix(in srgb, var(--popup-background-color) 25%, transparent); - border: 1px solid color-mix(in srgb, var(--border-color) 20%, transparent); - - &:hover, - &:active { - background-color: color-mix(in srgb, var(--popup-background-color) 50%, transparent); - border-color: color-mix(in srgb, var(--border-color) 40%, transparent); - transform: translateY(-1px); - } - - .icon { - font-size: 16px; - color: color-mix(in srgb, var(--secondary-text-color) 80%, transparent); - } - } + .link-item { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 10px 16px; + border-radius: 8px; + text-decoration: none; + color: var(--secondary-text-color); + font-size: 13px; + font-weight: 500; + transition: all 0.15s ease; + background-color: color-mix( + in srgb, + var(--popup-background-color) 25%, + transparent + ); + border: 1px solid color-mix(in srgb, var(--border-color) 20%, transparent); + + &:hover, + &:active { + background-color: color-mix( + in srgb, + var(--popup-background-color) 50%, + transparent + ); + border-color: color-mix(in srgb, var(--border-color) 40%, transparent); + transform: translateY(-1px); + } + + .icon { + font-size: 16px; + color: color-mix(in srgb, var(--secondary-text-color) 80%, transparent); + } } + } } - @supports not (gap: 1px) { - .welcome-header > * + * { margin-left: 16px; } - .action-list > * + * { margin-top: 2px; } - .action-row > * + * { margin-left: 12px; } - .link-row > * { margin-right: 8px; margin-bottom: 8px; } - .link-item > * + * { margin-left: 8px; } +@supports not (gap: 1px) { + .welcome-header > * + * { + margin-left: 16px; + } + .action-list > * + * { + margin-top: 2px; + } + .action-row > * + * { + margin-left: 12px; + } + .link-row > * { + margin-right: 8px; + margin-bottom: 8px; } + .link-item > * + * { + margin-left: 8px; + } +} // Responsive adjustments for smaller screens @media (max-width: 360px) { - #welcome-tab { - padding: 24px 16px; - - .welcome-header { - flex-direction: column; - text-align: center; - margin-bottom: 36px; - - .logo { - width: 56px; - height: 56px; - - &::after { - background-size: 40px; - } - } - - .welcome-header-text h1 { - font-size: 18px; - } - } - - .welcome-section { - margin-bottom: 24px; - } - - .action-row { - padding: 8px 10px; - - .action-label { - font-size: 13px; - } - - .action-shortcut { - font-size: 11px; - } - } - - .welcome-links .link-row { - justify-content: center; - } + #welcome-tab { + padding: 24px 16px; + + .welcome-header { + flex-direction: column; + text-align: center; + margin-bottom: 36px; + + .logo { + width: 40px; + max-height: 40px; + } + + .welcome-header-text h1 { + font-size: 18px; + } + } + + .welcome-section { + margin-bottom: 24px; + } + + .action-row { + padding: 8px 10px; + + .action-label { + font-size: 13px; + } + + .action-shortcut { + font-size: 11px; + } } + + .welcome-links .link-row { + justify-content: center; + } + } } // Larger screens - center content better @media (min-width: 600px) { - #welcome-tab { - .welcome-section { - max-width: 480px; - } - - .action-row { - padding: 12px 16px; - } + #welcome-tab { + .welcome-section { + max-width: 480px; } + + .action-row { + padding: 12px 16px; + } + } } // Discord icon .icon.discord { - position: relative; - - &::before { - content: ''; - display: block; - width: 16px; - height: 16px; - background-image: url(../../pages/about/discord.svg); - background-size: contain; - background-repeat: no-repeat; - background-position: center; - } -} \ No newline at end of file + position: relative; + + &::before { + content: ""; + display: block; + width: 16px; + height: 16px; + background-image: url(../../pages/about/discord.svg); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + } +} diff --git a/src/test/sanity.tests.js b/src/test/sanity.tests.js index 6b5938475..61971c8d9 100644 --- a/src/test/sanity.tests.js +++ b/src/test/sanity.tests.js @@ -1,3 +1,4 @@ +import { getLanguageModeRecommendationSearchKeyword } from "../lib/languageModeRecommendations"; import { TestRunner } from "./tester"; export async function runSanityTests(writeOutput) { @@ -63,6 +64,24 @@ export async function runSanityTests(writeOutput) { test.assert(!(value < 5), "Negation should work"); }); + runner.test("Language mode recommendation keywords", (test) => { + test.assertEqual( + getLanguageModeRecommendationSearchKeyword(".gitignore"), + "gitignore", + "Dotfiles without extensions should use the dotfile name", + ); + test.assertEqual( + getLanguageModeRecommendationSearchKeyword("src/main.js"), + "js", + "Normal files should use the file extension", + ); + test.assertEqual( + getLanguageModeRecommendationSearchKeyword("README"), + "", + "Extensionless non-dotfiles should not request plugin recommendations", + ); + }); + // Run all tests return await runner.run(writeOutput); } From 9ce5cd8194f62cf92a4d7dc6be23ec75c2e34cf9 Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Fri, 26 Jun 2026 17:41:10 +0530 Subject: [PATCH 6/9] fix: folder persistent regression --- src/lib/openFolder.js | 24 ++++++++++++------------ src/main.js | 5 ++++- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/lib/openFolder.js b/src/lib/openFolder.js index 312c0f52f..21e21b4f8 100644 --- a/src/lib/openFolder.js +++ b/src/lib/openFolder.js @@ -183,22 +183,22 @@ function openFolder(_path, opts = {}) { }, }; + if (typeof listFiles !== "boolean") { + listFiles = appSettings.value.fileBrowser?.listFiles ?? true; + } + + folder.listFiles = listFiles; + addedFolder.push(folder); + editorManager.emit("update", "add-folder"); editorManager.onupdate("add-folder", event); editorManager.emit("add-folder", event); - (async () => { - if (typeof listFiles !== "boolean") { - listFiles = appSettings.value.fileBrowser?.listFiles ?? true; - } - - if (listFiles) { - FileList.addRoot({ url: _path, name: title }); - } - - folder.listFiles = listFiles; - addedFolder.push(folder); - })(); + if (listFiles) { + FileList.addRoot({ url: _path, name: title }).catch((err) => { + console.error("Failed to add root to FileList:", err); + }); + } if (listState[_path]) { $root.expand(); diff --git a/src/main.js b/src/main.js index 004d1fe07..bb285801d 100644 --- a/src/main.js +++ b/src/main.js @@ -681,6 +681,7 @@ async function loadApp() { onEditorUpdate(undefined, false); } + acode.exec("save-state"); initFileList(); import(/* webpackChunkName: "terminal" */ "components/terminal").then( @@ -733,7 +734,9 @@ async function loadApp() { return; } - if (saveState) acode.exec("save-state"); + if (saveState && sessionStorage.getItem("isfilesRestored") === "true") { + acode.exec("save-state"); + } } async function onFileUpdate() { From 7046b4f92c19faef96fd5d7fd8666979dec63225 Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Fri, 26 Jun 2026 22:00:52 +0530 Subject: [PATCH 7/9] improved lsp logs and fix lsp settings --- src/cm/lsp/clientManager.ts | 5 + src/cm/lsp/connectionState.js | 6 +- src/cm/lsp/index.ts | 8 ++ src/cm/lsp/logs.ts | 88 +++++++++++++++ src/cm/lsp/runtimeActions.ts | 33 ++++-- src/cm/lsp/runtimes/builtinAlpine.ts | 4 + src/cm/lsp/runtimes/externalWebSocket.ts | 5 +- src/cm/lsp/serverLauncher.ts | 10 ++ src/cm/lsp/transport.ts | 16 +++ src/cm/lsp/types.ts | 1 + src/components/lspInfoDialog/index.js | 132 +---------------------- 11 files changed, 165 insertions(+), 143 deletions(-) create mode 100644 src/cm/lsp/logs.ts diff --git a/src/cm/lsp/clientManager.ts b/src/cm/lsp/clientManager.ts index 1720a86ac..196ef14a7 100644 --- a/src/cm/lsp/clientManager.ts +++ b/src/cm/lsp/clientManager.ts @@ -18,6 +18,7 @@ import Url from "utils/Url"; import { clearDiagnosticsEffect } from "./diagnostics"; import { supportsBuiltinFormatting } from "./formattingSupport"; import { inlayHintsExtension } from "./inlayHints"; +import { addLspLog } from "./logs"; import { acodeRenameKeymap } from "./rename"; import { selectRuntimeProvider } from "./runtimeProviders"; import serverRegistry from "./serverRegistry"; @@ -689,6 +690,7 @@ export class LspClientManager { level = "info"; } const logFn = console[level] ?? console.info; + addLspLog(server.id, level === "log" ? "info" : level, message); logFn(`[LSP:${server.id}] ${message}`); return true; }, @@ -716,6 +718,7 @@ export class LspClientManager { icon: type === 1 ? "error" : "warningreport_problem", type: type === 1 ? "error" : "warning", }); + addLspLog(server.id, type === 1 ? "error" : "warn", message); logLspInfo(`[LSP:${server.id}] ${message}`); return true; } @@ -728,6 +731,7 @@ export class LspClientManager { icon: type === 4 ? "autorenew" : "info", duration: 5000, }); + addLspLog(server.id, "info", message); logLspInfo(`[LSP:${server.id}] ${message}`); return true; }, @@ -906,6 +910,7 @@ export class LspClientManager { ); } logLspInfo(`[LSP:${server.id}] initialized`); + addLspLog(server.id, "info", "Initialized"); client.__acodeLoggedInfo = true; } } catch (error) { diff --git a/src/cm/lsp/connectionState.js b/src/cm/lsp/connectionState.js index 29158a9f0..123665709 100644 --- a/src/cm/lsp/connectionState.js +++ b/src/cm/lsp/connectionState.js @@ -25,4 +25,8 @@ function hasConnectedServers() { return getServersForCurrentFile().length > 0; } -export { getServersForCurrentFile, hasConnectedServers }; +export { + getCurrentFileLanguage, + getServersForCurrentFile, + hasConnectedServers, +}; diff --git a/src/cm/lsp/index.ts b/src/cm/lsp/index.ts index 41654762b..904c6c31c 100644 --- a/src/cm/lsp/index.ts +++ b/src/cm/lsp/index.ts @@ -55,6 +55,14 @@ export { inlayHintsEditorExtension, inlayHintsExtension, } from "./inlayHints"; +export { + addLspLog, + clearLspLogs, + getLspLogs, + onLspLog, + type LspLogEntry, + type LspLogLevel, +} from "./logs"; export { closeReferencesPanel, findAllReferences, diff --git a/src/cm/lsp/logs.ts b/src/cm/lsp/logs.ts new file mode 100644 index 000000000..eb5dacd8d --- /dev/null +++ b/src/cm/lsp/logs.ts @@ -0,0 +1,88 @@ +export type LspLogLevel = "debug" | "info" | "log" | "warn" | "error" | "stderr"; + +export interface LspLogEntry { + timestamp: Date; + level: LspLogLevel; + message: string; + details?: unknown; +} + +const MAX_LOGS = 200; +const logsByServer = new Map(); +const listeners = new Set<(serverId: string, entry: LspLogEntry) => void>(); +const IGNORED_LOG_PATTERNS = [ + /\$\/progress\b/i, + /\bProgress:/i, + /\bwindow\/workDoneProgress\/create\b/i, + /\bAuto-responded to window\/workDoneProgress\/create\b/i, +]; + +function stripAnsi(value: string): string { + return value.replace(/\x1b\[[0-9;]*m/g, ""); +} + +function normalizeMessage(message: unknown): string { + let text: string; + if (typeof message === "string") { + text = message; + } else if (message instanceof Error) { + text = message.message; + } else { + try { + text = JSON.stringify(message); + } catch { + text = String(message); + } + } + return stripAnsi(String(text || "")) + .replace(/\[LSP:[^\]]+\]\s*/g, "") + .replace(/\[LSP-STDERR:[^\]]+\]\s*/g, "") + .replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?\s*/g, "") + .replace(/\s*(INFO|WARN|ERROR|DEBUG|TRACE)\s+/gi, "") + .replace(/[a-z_]+::[a-z_]+:\s*/gi, "") + .trim(); +} + +function shouldIgnoreLog(message: string): boolean { + return IGNORED_LOG_PATTERNS.some((pattern) => pattern.test(message)); +} + +export function addLspLog( + serverId: string, + level: LspLogLevel, + message: unknown, + details?: unknown, +): void { + const id = String(serverId || "").trim(); + if (!id) return; + + const normalized = normalizeMessage(message); + if (!normalized || shouldIgnoreLog(normalized)) return; + + const logs = logsByServer.get(id) || []; + const entry: LspLogEntry = { + timestamp: new Date(), + level, + message: normalized, + details, + }; + logs.push(entry); + if (logs.length > MAX_LOGS) logs.splice(0, logs.length - MAX_LOGS); + logsByServer.set(id, logs); + listeners.forEach((listener) => listener(id, entry)); +} + +export function getLspLogs(serverId: string): LspLogEntry[] { + return logsByServer.get(String(serverId || "").trim()) || []; +} + +export function clearLspLogs(serverId: string): void { + logsByServer.delete(String(serverId || "").trim()); +} + +export function onLspLog( + listener: (serverId: string, entry: LspLogEntry) => void, +): () => void { + listeners.add(listener); + return () => listeners.delete(listener); +} diff --git a/src/cm/lsp/runtimeActions.ts b/src/cm/lsp/runtimeActions.ts index 17455c9aa..2fe36af8a 100644 --- a/src/cm/lsp/runtimeActions.ts +++ b/src/cm/lsp/runtimeActions.ts @@ -9,26 +9,33 @@ import type { function getSettingsContext( server: LspServerDefinition, context: LspRuntimeContext = {}, + runtimeAction: LspRuntimeContext["runtimeAction"] = "command", ): LspRuntimeContext { return { ...context, serverId: context.serverId || server.id, allowNonTerminalWorkspace: true, + runtimeAction, }; } async function getProvider( server: LspServerDefinition, context: LspRuntimeContext = {}, + runtimeAction?: LspRuntimeContext["runtimeAction"], ): Promise { - return selectRuntimeProvider(server, getSettingsContext(server, context)); + return selectRuntimeProvider( + server, + getSettingsContext(server, context, runtimeAction), + ); } export async function checkRuntimeServerInstallation( server: LspServerDefinition, context?: LspRuntimeContext, ): Promise { - const provider = await getProvider(server, context); + const settingsContext = getSettingsContext(server, context, "checkInstallation"); + const provider = await getProvider(server, context, "checkInstallation"); if (!provider?.checkInstallation) { return { status: "unknown", @@ -38,7 +45,7 @@ export async function checkRuntimeServerInstallation( message: "The selected runtime does not provide installation checks.", }; } - return provider.checkInstallation(server, getSettingsContext(server, context)); + return provider.checkInstallation(server, settingsContext); } export async function installRuntimeServer( @@ -47,11 +54,12 @@ export async function installRuntimeServer( options: { promptConfirm?: boolean } = {}, context?: LspRuntimeContext, ): Promise { - const provider = await getProvider(server, context); + const settingsContext = getSettingsContext(server, context, "install"); + const provider = await getProvider(server, context, "install"); if (!provider?.install) { throw new Error("The selected runtime does not support installation."); } - return provider.install(server, getSettingsContext(server, context), mode, options); + return provider.install(server, settingsContext, mode, options); } export async function uninstallRuntimeServer( @@ -59,11 +67,12 @@ export async function uninstallRuntimeServer( options: { promptConfirm?: boolean } = {}, context?: LspRuntimeContext, ): Promise { - const provider = await getProvider(server, context); + const settingsContext = getSettingsContext(server, context, "uninstall"); + const provider = await getProvider(server, context, "uninstall"); if (!provider?.uninstall) { throw new Error("The selected runtime does not support uninstall."); } - return provider.uninstall(server, getSettingsContext(server, context), options); + return provider.uninstall(server, settingsContext, options); } export async function getRuntimeInstallCommand( @@ -71,11 +80,12 @@ export async function getRuntimeInstallCommand( mode: "install" | "update" = "install", context?: LspRuntimeContext, ): Promise { - const provider = await getProvider(server, context); + const settingsContext = getSettingsContext(server, context, "command"); + const provider = await getProvider(server, context, "command"); return ( provider?.getInstallCommand?.( server, - getSettingsContext(server, context), + settingsContext, mode, ) ?? null ); @@ -85,11 +95,12 @@ export async function getRuntimeUninstallCommand( server: LspServerDefinition, context?: LspRuntimeContext, ): Promise { - const provider = await getProvider(server, context); + const settingsContext = getSettingsContext(server, context, "command"); + const provider = await getProvider(server, context, "command"); return ( provider?.getUninstallCommand?.( server, - getSettingsContext(server, context), + settingsContext, ) ?? null ); } diff --git a/src/cm/lsp/runtimes/builtinAlpine.ts b/src/cm/lsp/runtimes/builtinAlpine.ts index 5e158bb6d..499ba1545 100644 --- a/src/cm/lsp/runtimes/builtinAlpine.ts +++ b/src/cm/lsp/runtimes/builtinAlpine.ts @@ -56,6 +56,10 @@ export const builtinAlpineRuntimeProvider: LspRuntimeProvider = { server: LspServerDefinition, context: LspRuntimeContext, ): boolean { + if (context.runtimeAction && server.launcher) { + return true; + } + return ( !!server.launcher && (canUseRealPath(context) || diff --git a/src/cm/lsp/runtimes/externalWebSocket.ts b/src/cm/lsp/runtimes/externalWebSocket.ts index 90659bf8b..0d0d7e6ad 100644 --- a/src/cm/lsp/runtimes/externalWebSocket.ts +++ b/src/cm/lsp/runtimes/externalWebSocket.ts @@ -17,7 +17,10 @@ export const externalWebSocketRuntimeProvider: LspRuntimeProvider = { label: "External WebSocket", priority: -50, - canHandle(server) { + canHandle(server, context) { + if (context.runtimeAction && server.launcher) { + return false; + } return server.transport?.kind === "websocket" && !!server.transport.url; }, diff --git a/src/cm/lsp/serverLauncher.ts b/src/cm/lsp/serverLauncher.ts index 87f0a1852..c7db21e10 100644 --- a/src/cm/lsp/serverLauncher.ts +++ b/src/cm/lsp/serverLauncher.ts @@ -16,6 +16,7 @@ import { } from "./runtimes/axsBridge"; import { getServerBundle } from "./serverCatalog"; import notificationManager from "lib/notificationManager"; +import { addLspLog } from "./logs"; import type { InstallCheckResult, InstallStatus, @@ -278,6 +279,7 @@ export async function canReuseExistingServer( (await checkServerAliveViaWebSocket(url, 1000))); if (alive) { + addLspLog(server.id, "info", `Reusing existing server on port ${portInfo.port}`); console.info( `[LSP:${server.id}] Reusing existing server on port ${portInfo.port}`, ); @@ -287,6 +289,7 @@ export async function canReuseExistingServer( console.info( `[LSP:${server.id}] Found stale port file, will start new server`, ); + addLspLog(server.id, "warn", "Found stale port file, starting a new server"); return null; } @@ -909,8 +912,10 @@ async function startInteractiveServer( const callback: ExecutorCallback = (type, data) => { if (type === "stderr") { if (/proot warning/i.test(data)) return; + addLspLog(serverId, "stderr", data); console.warn(`[LSP:${serverId}] ${data}`); } else if (type === "stdout" && data && data.trim()) { + addLspLog(serverId, "info", data); console.info(`[LSP:${serverId}] ${data}`); // Detect when the axs proxy signals it's listening if (/listening on/i.test(data)) { @@ -919,6 +924,7 @@ async function startInteractiveServer( } }; const uuid = await executor.start(command, callback, true); + addLspLog(serverId, "info", `Started shell process ${uuid}`); managedServers.set(serverId, { uuid, command, @@ -1115,6 +1121,7 @@ export async function ensureServerRunning( console.info( `[LSP:${server.id}] Auto-discovered port ${discoveredPort}`, ); + addLspLog(server.id, "info", `Auto-discovered port ${discoveredPort}`); // Update managed server entry with the port const entry = managedServers.get(key); if (entry) { @@ -1137,12 +1144,14 @@ export async function ensureServerRunning( if (!announcedServers.has(key)) { console.info(`[LSP:${server.id}] ${server.label} connected`); + addLspLog(server.id, "info", `${server.label} connected`); announcedServers.add(key); } return { uuid, discoveredPort }; } catch (error) { console.error(`Failed to start language server ${server.id}`, error); const errorMessage = error instanceof Error ? error.message : String(error); + addLspLog(server.id, "error", errorMessage || "Connection failed"); lspStatusBar.show({ message: errorMessage || "Connection failed", title: `${server.label} failed`, @@ -1173,6 +1182,7 @@ export async function ensureServerRunning( export function stopManagedServer(serverId: string): void { const entry = managedServers.get(serverId); if (!entry) return; + addLspLog(serverId, "info", "Stopping managed server"); const executor = getExecutor(); executor.stop(entry.uuid).catch((error: Error) => { console.warn(`Failed to stop language server ${serverId}`, error); diff --git a/src/cm/lsp/transport.ts b/src/cm/lsp/transport.ts index 3a9d0a71c..5697782b9 100644 --- a/src/cm/lsp/transport.ts +++ b/src/cm/lsp/transport.ts @@ -4,6 +4,7 @@ */ import type { Transport } from "@codemirror/lsp-client"; +import { addLspLog } from "./logs"; import type { LspServerDefinition, TransportContext, @@ -44,6 +45,7 @@ function createWebSocketTransport( console.info( `[LSP:${server.id}] Using auto-discovered port ${context.dynamicPort}`, ); + addLspLog(server.id, "info", `Using auto-discovered port ${context.dynamicPort}`); } // URL is only required when not using dynamic port @@ -175,17 +177,24 @@ function createWebSocketTransport( const wasClean = event.wasClean || event.code === 1000; if (wasClean) { console.info(`[LSP:${server.id}] WebSocket closed cleanly`); + addLspLog(server.id, "info", "WebSocket closed cleanly"); return; } console.warn( `[LSP:${server.id}] WebSocket closed unexpectedly (code: ${event.code})`, ); + addLspLog( + server.id, + "warn", + `WebSocket closed unexpectedly (code: ${event.code})`, + ); if (enableReconnect && reconnectAttempts < maxReconnectAttempts) { scheduleReconnect(); } else if (reconnectAttempts >= maxReconnectAttempts) { console.error(`[LSP:${server.id}] Max reconnection attempts reached`); + addLspLog(server.id, "error", "Max reconnection attempts reached"); } } @@ -195,6 +204,7 @@ function createWebSocketTransport( const reason = errorEvent?.message || errorEvent?.type || "connection error"; console.error(`[LSP:${server.id}] WebSocket error: ${reason}`); + addLspLog(server.id, "error", `WebSocket error: ${reason}`); } function scheduleReconnect(): void { @@ -209,6 +219,11 @@ function createWebSocketTransport( console.info( `[LSP:${server.id}] Reconnecting in ${delay}ms (attempt ${reconnectAttempts}/${maxReconnectAttempts})`, ); + addLspLog( + server.id, + "info", + `Reconnecting in ${delay}ms (attempt ${reconnectAttempts}/${maxReconnectAttempts})`, + ); reconnectTimer = setTimeout(() => { reconnectTimer = null; @@ -228,6 +243,7 @@ function createWebSocketTransport( connected = true; reconnectAttempts = 0; console.info(`[LSP:${server.id}] Reconnected successfully`); + addLspLog(server.id, "info", "Reconnected successfully"); if (socket) { socket.onopen = null; } diff --git a/src/cm/lsp/types.ts b/src/cm/lsp/types.ts index 98174106f..1ea75b204 100644 --- a/src/cm/lsp/types.ts +++ b/src/cm/lsp/types.ts @@ -103,6 +103,7 @@ export interface LspRuntimeContext extends TransportContext { serverId?: string; workspaceKind?: WorkspaceKind; allowNonTerminalWorkspace?: boolean; + runtimeAction?: "checkInstallation" | "install" | "uninstall" | "command"; } export type LspClientScope = "workspace" | "document"; diff --git a/src/components/lspInfoDialog/index.js b/src/components/lspInfoDialog/index.js index 190cbe0ec..c6e0afeb4 100644 --- a/src/components/lspInfoDialog/index.js +++ b/src/components/lspInfoDialog/index.js @@ -1,9 +1,11 @@ import "./styles.scss"; import lspClientManager from "cm/lsp/clientManager"; import { + getCurrentFileLanguage, getServersForCurrentFile, hasConnectedServers, } from "cm/lsp/connectionState"; +import { addLspLog, clearLspLogs, getLspLogs } from "cm/lsp/logs"; import { getServerStats } from "cm/lsp/serverLauncher"; import toast from "components/toast"; import actionStack from "lib/actionStack"; @@ -11,136 +13,6 @@ import restoreTheme from "lib/restoreTheme"; let dialogInstance = null; -const lspLogs = new Map(); -const MAX_LOGS = 200; -const logListeners = new Set(); -const IGNORED_LOG_PATTERNS = [ - /\$\/progress\b/i, - /\bProgress:/i, - /\bwindow\/workDoneProgress\/create\b/i, - /\bAuto-responded to window\/workDoneProgress\/create\b/i, -]; - -function shouldIgnoreLog(message) { - if (typeof message !== "string") return false; - return IGNORED_LOG_PATTERNS.some((pattern) => pattern.test(message)); -} - -function addLspLog(serverId, level, message, details = null) { - if (shouldIgnoreLog(message)) { - return; - } - - if (!lspLogs.has(serverId)) { - lspLogs.set(serverId, []); - } - const logs = lspLogs.get(serverId); - const entry = { - timestamp: new Date(), - level, - message, - details, - }; - logs.push(entry); - if (logs.length > MAX_LOGS) { - logs.shift(); - } - logListeners.forEach((fn) => fn(serverId, entry)); -} - -function getLspLogs(serverId) { - return lspLogs.get(serverId) || []; -} - -function clearLspLogs(serverId) { - lspLogs.delete(serverId); -} - -const originalConsoleInfo = console.info; -const originalConsoleWarn = console.warn; -const originalConsoleError = console.error; - -function stripAnsi(str) { - if (typeof str !== "string") return str; - return str.replace(/\x1b\[[0-9;]*m/g, ""); -} - -function extractServerId(message) { - const cleaned = stripAnsi(message); - // Match [LSP:serverId] format - const lspMatch = cleaned?.match?.(/\[LSP:([^\]]+)\]/); - if (lspMatch) return lspMatch[1]; - - // Match [LSP-STDERR:program] format from axs proxy - const stderrMatch = cleaned?.match?.(/\[LSP-STDERR:([^\]]+)\]/); - if (stderrMatch) { - const program = stderrMatch[1]; - return program; - } - - return null; -} - -function extractLogMessage(message) { - const cleaned = stripAnsi(message); - // Strip [LSP:...] and [LSP-STDERR:...] prefixes - // Strip ISO timestamps like 2026-02-05T08:26:24.745443Z - // Strip log levels like INFO, WARN, ERROR and the source like axs::lsp: - return ( - cleaned - ?.replace?.(/\[LSP:[^\]]+\]\s*/, "") - ?.replace?.(/\[LSP-STDERR:[^\]]+\]\s*/, "") - ?.replace?.(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?\s*/g, "") - ?.replace?.(/\s*(INFO|WARN|ERROR|DEBUG|TRACE)\s+/gi, "") - ?.replace?.(/[a-z_]+::[a-z_]+:\s*/gi, "") - ?.trim() || cleaned - ); -} - -console.info = function (...args) { - originalConsoleInfo.apply(console, args); - const msg = args[0]; - if ( - typeof msg === "string" && - (msg.includes("[LSP:") || msg.includes("[LSP-STDERR:")) - ) { - const serverId = extractServerId(msg); - if (serverId) { - addLspLog(serverId, "info", extractLogMessage(msg)); - } - } -}; - -console.warn = function (...args) { - originalConsoleWarn.apply(console, args); - const msg = args[0]; - if ( - typeof msg === "string" && - (msg.includes("[LSP:") || msg.includes("[LSP-STDERR:")) - ) { - const serverId = extractServerId(msg); - if (serverId) { - // stderr from axs is logged as warn, mark it appropriately - const isStderr = msg.includes("[LSP-STDERR:"); - addLspLog(serverId, isStderr ? "stderr" : "warn", extractLogMessage(msg)); - } - } -}; - -console.error = function (...args) { - originalConsoleError.apply(console, args); - const msg = args[0]; - if ( - typeof msg === "string" && - (msg.includes("[LSP:") || msg.includes("[LSP-STDERR:")) - ) { - const serverId = extractServerId(msg); - if (serverId) { - addLspLog(serverId, "error", extractLogMessage(msg)); - } - } -}; - function getActiveClients() { try { return lspClientManager.getActiveClients(); From 6c181ec0d08c6dc7e684bbeb1ab3e9edeb07e93e Mon Sep 17 00:00:00 2001 From: Ajit Kumar Date: Sat, 27 Jun 2026 04:48:37 +0530 Subject: [PATCH 8/9] fix: small ui issue in plugin page --- src/pages/plugin/plugin.scss | 68 ++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 15 deletions(-) diff --git a/src/pages/plugin/plugin.scss b/src/pages/plugin/plugin.scss index 3a55e7350..a33259b5f 100644 --- a/src/pages/plugin/plugin.scss +++ b/src/pages/plugin/plugin.scss @@ -5,6 +5,7 @@ max-width: 800px; padding: 20px; margin: 0 auto; + box-sizing: border-box; .plugin-header { display: grid; @@ -379,26 +380,63 @@ } @supports not (gap: 1px) { - .plugin-header > * { + .plugin-header>* { margin-right: 20px; margin-bottom: 20px; } - .plugin-header > *:last-child { + + .plugin-header>*:last-child { margin-right: 0; margin-bottom: 0; } - .title-wrapper > * + * { margin-left: 16px; } - .source-indicator > * + * { margin-left: 6px; } - .plugin-meta > * + * { margin-left: 16px; } - .meta-item > * + * { margin-left: 4px; } - .metrics-row > * + * { margin-left: 16px; } - .metric > * + * { margin-left: 4px; } - .keywords > * + * { margin-left: 6px; } - .legacy-editor-warning > * + * { margin-left: 6px; } - .action-buttons > * + * { margin-left: 8px; } - .info:not(.icon) > * + * { margin-left: 5px; } - .btn > * + * { margin-left: 6px; } - .contributor > * + * { margin-left: 12px; } + + .title-wrapper>*+* { + margin-left: 16px; + } + + .source-indicator>*+* { + margin-left: 6px; + } + + .plugin-meta>*+* { + margin-left: 16px; + } + + .meta-item>*+* { + margin-left: 4px; + } + + .metrics-row>*+* { + margin-left: 16px; + } + + .metric>*+* { + margin-left: 4px; + } + + .keywords>*+* { + margin-left: 6px; + } + + .legacy-editor-warning>*+* { + margin-left: 6px; + } + + .action-buttons>*+* { + margin-left: 8px; + } + + .info:not(.icon)>*+* { + margin-left: 5px; + } + + .btn>*+* { + margin-left: 6px; + } + + .contributor>*+* { + margin-left: 12px; + } } @media (max-width: 768px) { @@ -556,4 +594,4 @@ } } } -} +} \ No newline at end of file From 25fef29fe90ffc2dc979e87e40fb9dd085fbbdf3 Mon Sep 17 00:00:00 2001 From: Ajit Kumar Date: Sat, 27 Jun 2026 18:19:29 +0530 Subject: [PATCH 9/9] fix: greptile review (#2401) Co-authored-by: Ajit Kumar --- src/cm/lsp/clientManager.ts | 2 ++ src/cm/lsp/codeActions.ts | 10 ++++++++++ src/cm/lsp/logs.ts | 28 ++++++++++++++++++++++++++++ src/cm/lsp/rename.ts | 8 ++++++++ src/cm/lsp/transport.ts | 1 + src/cm/lsp/workspace.ts | 16 ++++++++++++++++ 6 files changed, 65 insertions(+) diff --git a/src/cm/lsp/clientManager.ts b/src/cm/lsp/clientManager.ts index 196ef14a7..f95f539cc 100644 --- a/src/cm/lsp/clientManager.ts +++ b/src/cm/lsp/clientManager.ts @@ -268,6 +268,7 @@ interface ResolvedRuntimeTarget { } interface ExtendedLSPClient extends LSPClient { + __acodeServerId?: string; __acodeLoggedInfo?: boolean; } @@ -888,6 +889,7 @@ export class LspClientManager { ); await transportHandle.ready; client = new LSPClient(clientConfig) as ExtendedLSPClient; + client.__acodeServerId = server.id; connectClient(client, transportHandle.transport, initializationOptions); await client.initializing; if (!client.__acodeLoggedInfo) { diff --git a/src/cm/lsp/codeActions.ts b/src/cm/lsp/codeActions.ts index 7f5905761..736909dbd 100644 --- a/src/cm/lsp/codeActions.ts +++ b/src/cm/lsp/codeActions.ts @@ -12,6 +12,7 @@ import type { WorkspaceEdit, } from "vscode-languageserver-types"; import type { Position, Range } from "./types"; +import { addLspLogFor } from "./logs"; import type AcodeWorkspace from "./workspace"; type CodeActionResponse = (CodeAction | Command)[] | null; @@ -116,6 +117,7 @@ async function resolveCodeAction( ); return resolved ?? action; } catch (error) { + addLspLogFor(plugin, "warn", "Code action resolve failed", error); console.warn("[LSP:CodeAction] Failed to resolve:", error); return action; } @@ -138,6 +140,7 @@ async function executeCommand( // -32601 = Method not implemented (expected for some LSP servers) const lspError = error as { code?: number }; if (lspError?.code !== -32601) { + addLspLogFor(plugin, "warn", "Code action command execution failed", error); console.warn("[LSP:CodeAction] Command execution failed:", error); } return false; @@ -173,6 +176,11 @@ async function applyChangesToFile( const displayedView = await workspace.displayFile(uri); if (!displayedView?.state?.doc) { + addLspLogFor( + workspace.client, + "warn", + `Code action could not open file: ${uri}`, + ); console.warn(`[LSP:CodeAction] Could not open file: ${uri}`); return false; } @@ -326,6 +334,7 @@ export async function fetchCodeActions( return items; } catch (error) { + addLspLogFor(plugin, "error", "Code action fetch failed", error); console.error("[LSP:CodeAction] Failed to fetch:", error); return []; } @@ -349,6 +358,7 @@ export async function executeCodeAction( // Handle CodeAction return applyCodeAction(view, item.action); } catch (error) { + addLspLogFor(plugin, "error", "Code action execution failed", error); console.error("[LSP:CodeAction] Failed to execute:", error); return false; } diff --git a/src/cm/lsp/logs.ts b/src/cm/lsp/logs.ts index eb5dacd8d..dca83a9d1 100644 --- a/src/cm/lsp/logs.ts +++ b/src/cm/lsp/logs.ts @@ -72,6 +72,34 @@ export function addLspLog( listeners.forEach((listener) => listener(id, entry)); } +export function getLspLogServerId(source: unknown): string | null { + const client = + source && + typeof source === "object" && + Object.prototype.hasOwnProperty.call(source, "client") + ? (source as { client?: unknown }).client + : source; + const metadata = client as + | { __acodeServerId?: unknown } + | null + | undefined; + const serverId = metadata?.__acodeServerId; + return typeof serverId === "string" && serverId.trim() + ? serverId.trim() + : null; +} + +export function addLspLogFor( + source: unknown, + level: LspLogLevel, + message: unknown, + details?: unknown, +): void { + const serverId = getLspLogServerId(source); + if (!serverId) return; + addLspLog(serverId, level, message, details); +} + export function getLspLogs(serverId: string): LspLogEntry[] { return logsByServer.get(String(serverId || "").trim()) || []; } diff --git a/src/cm/lsp/rename.ts b/src/cm/lsp/rename.ts index 1ba871f91..6ce0ff212 100644 --- a/src/cm/lsp/rename.ts +++ b/src/cm/lsp/rename.ts @@ -7,6 +7,7 @@ import { } from "@codemirror/view"; import prompt from "dialogs/prompt"; import type * as lsp from "vscode-languageserver-protocol"; +import { addLspLogFor } from "./logs"; import type AcodeWorkspace from "./workspace"; interface RenameParams { @@ -106,6 +107,7 @@ async function performRename(view: EditorView): Promise { } } } catch (error) { + addLspLogFor(plugin, "warn", "Rename prepare failed; using word", error); console.warn("[LSP:Rename] prepareRename failed, using word:", error); } } @@ -133,6 +135,7 @@ async function performRename(view: EditorView): Promise { try { await doRename(view, String(newName), wordRange.from); } catch (error) { + addLspLogFor(plugin, "error", "Rename failed", error); console.error("[LSP:Rename] Rename failed:", error); const errorMessage = error instanceof Error ? error.message : "Failed to rename symbol"; @@ -176,6 +179,7 @@ async function applyChangesToFile( const displayedView = await workspace.displayFile(uri); if (!displayedView?.state?.doc) { + addLspLogFor(workspace.client, "warn", `Rename could not open file: ${uri}`); console.warn(`[LSP:Rename] Could not open file: ${uri}`); return false; } @@ -211,6 +215,7 @@ async function doRename( ); if (!response) { + addLspLogFor(plugin, "info", "Rename returned no changes"); console.info("[LSP:Rename] No changes returned from server"); return; } @@ -255,10 +260,13 @@ async function doRename( console.info( `[LSP:Rename] Renamed to "${newName}" in ${filesChanged} file(s)`, ); + addLspLogFor(plugin, "info", `Renamed to "${newName}" in ${filesChanged} file(s)`); } export const renameSymbol: Command = (view) => { performRename(view).catch((error) => { + const plugin = LSPPlugin.get(view); + addLspLogFor(plugin, "error", "Rename command failed", error); console.error("[LSP:Rename] Rename command failed:", error); }); return true; diff --git a/src/cm/lsp/transport.ts b/src/cm/lsp/transport.ts index 5697782b9..d760893ac 100644 --- a/src/cm/lsp/transport.ts +++ b/src/cm/lsp/transport.ts @@ -250,6 +250,7 @@ function createWebSocketTransport( }; } catch (error) { console.error(`[LSP:${server.id}] Reconnection failed`, error); + addLspLog(server.id, "error", "Reconnection failed", error); if (reconnectAttempts < maxReconnectAttempts) { scheduleReconnect(); } diff --git a/src/cm/lsp/workspace.ts b/src/cm/lsp/workspace.ts index 01a071dee..0ac350ff8 100644 --- a/src/cm/lsp/workspace.ts +++ b/src/cm/lsp/workspace.ts @@ -3,6 +3,7 @@ import { LSPPlugin, Workspace } from "@codemirror/lsp-client"; import type { Text, TransactionSpec } from "@codemirror/state"; import type { EditorView } from "@codemirror/view"; import { getModeForPath } from "cm/modelist"; +import { addLspLogFor, type LspLogLevel } from "./logs"; import type { WorkspaceFileUpdate, WorkspaceOptions } from "./types"; class AcodeWorkspaceFile implements WorkspaceFile { @@ -55,6 +56,10 @@ export default class AcodeWorkspace extends Workspace { this.options = options; } + #log(level: LspLogLevel, message: string, details?: unknown): void { + addLspLogFor(this.client, level, message, details); + } + #getOrCreateFile( uri: string, languageId: string, @@ -110,6 +115,11 @@ export default class AcodeWorkspace extends Workspace { return String(mode.name).toLowerCase(); } } catch (error) { + this.#log( + "warn", + `Workspace failed to resolve language id for ${uri}`, + error, + ); console.warn( `[LSP:Workspace] Failed to resolve language id for ${uri}`, error, @@ -182,6 +192,7 @@ export default class AcodeWorkspace extends Workspace { // File is not open - try to open it and apply the update this.#applyUpdateToClosedFile(uri, update).catch((error) => { + this.#log("warn", `Workspace failed to apply update: ${uri}`, error); console.warn(`[LSP:Workspace] Failed to apply update: ${uri}`, error); }); } @@ -202,6 +213,7 @@ export default class AcodeWorkspace extends Workspace { fileView.dispatch(update); } } catch (error) { + this.#log("error", `Workspace failed to apply update: ${uri}`, error); console.error(`[LSP:Workspace] Failed to apply update: ${uri}`, error); } } @@ -211,6 +223,7 @@ export default class AcodeWorkspace extends Workspace { try { return await this.options.displayFile(uri); } catch (error) { + this.#log("error", "Workspace failed to display file", error); console.error("[LSP:Workspace] Failed to display file", error); } } @@ -234,6 +247,7 @@ export default class AcodeWorkspace extends Workspace { }; if (!client.connected || !client.transport) { + this.#log("warn", "Workspace cannot send notification: not connected"); console.warn(`[LSP:Workspace] Cannot send notification: not connected`); return; } @@ -268,6 +282,7 @@ export default class AcodeWorkspace extends Workspace { }, }); console.info(`[LSP:Workspace] Added workspace folder: ${uri}`); + this.#log("info", `Workspace folder added: ${uri}`); return true; } @@ -284,6 +299,7 @@ export default class AcodeWorkspace extends Workspace { }, }); console.info(`[LSP:Workspace] Removed workspace folder: ${uri}`); + this.#log("info", `Workspace folder removed: ${uri}`); return true; } }