From 0d14601c0f38deab836aaabe9d5b30a4c6a9f20f Mon Sep 17 00:00:00 2001 From: Rohit Kushwaha Date: Tue, 25 Aug 2026 14:58:03 +0530 Subject: [PATCH 1/6] feat: update docs --- .vitepress/config.mts | 20 ++ docs/advanced-apis/executor.md | 159 ++++++++++++ docs/advanced-apis/lsp.md | 4 +- docs/advanced-apis/system.md | 252 +++++++++++++++++++ docs/advanced-apis/terminal.md | 22 +- docs/advanced-apis/webview.md | 2 +- docs/editor-components/editor-file.md | 198 +++++++++++++-- docs/editor-components/file-index.md | 16 +- docs/editor-components/file-list.md | 4 +- docs/getting-started/create-plugin.md | 2 +- docs/getting-started/intro.md | 2 +- docs/getting-started/understanding-plugin.md | 62 +++-- docs/global-apis/ace.md | 2 +- docs/global-apis/acode.md | 48 +++- docs/global-apis/config.md | 102 ++++++++ docs/global-apis/editor-manager.md | 60 ++++- docs/global-apis/global-utilities.md | 113 +++++++-- docs/plugin-essentials/core-file.md | 66 +++-- docs/plugin-essentials/manifest.md | 6 + docs/plugin-essentials/plugin-context.md | 130 ++++++++++ docs/utilities/code-highlight.md | 118 +-------- docs/utilities/codemirror.md | 10 +- docs/utilities/helpers.md | 202 +++++++++++++++ docs/utilities/open-folder.md | 1 - user-guide/command-palette.md | 2 +- 25 files changed, 1362 insertions(+), 241 deletions(-) create mode 100644 docs/advanced-apis/executor.md create mode 100644 docs/advanced-apis/system.md create mode 100644 docs/global-apis/config.md create mode 100644 docs/plugin-essentials/plugin-context.md create mode 100644 docs/utilities/helpers.md diff --git a/.vitepress/config.mts b/.vitepress/config.mts index 6806882..5e25755 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -96,6 +96,10 @@ export default defineConfig({ text: "Core File", link: "/docs/plugin-essentials/core-file", }, + { + text: "Plugin Context (ctx)", + link: "/docs/plugin-essentials/plugin-context", + }, ], }, { @@ -118,6 +122,10 @@ export default defineConfig({ text: "EditorManager", link: "/docs/global-apis/editor-manager", }, + { + text: "Config", + link: "/docs/global-apis/config", + }, { text: "Other Global Utilities", link: "/docs/global-apis/global-utilities", @@ -188,6 +196,10 @@ export default defineConfig({ text: "File System(fs)", link: "/docs/utilities/fs", }, + { + text: "Helpers", + link: "/docs/utilities/helpers", + }, { text: "URL", link: "/docs/utilities/url", @@ -324,6 +336,14 @@ export default defineConfig({ text: "Terminal", link: "/docs/advanced-apis/terminal", }, + { + text: "Executor", + link: "/docs/advanced-apis/executor", + }, + { + text: "System", + link: "/docs/advanced-apis/system", + }, { text: "LSP", link: "/docs/advanced-apis/lsp", diff --git a/docs/advanced-apis/executor.md b/docs/advanced-apis/executor.md new file mode 100644 index 0000000..8efe062 --- /dev/null +++ b/docs/advanced-apis/executor.md @@ -0,0 +1,159 @@ +# Executor + +The `Executor` API lets you run shell commands on the device without opening a visual terminal session. It supports one-off commands, long-running processes with real-time streaming, stdin writes, and background execution via a foreground service. + +> [!Warning] +> Prefer visible terminals for transparency. Avoid hiding work in the background and do not start long‑running processes without good reason. For interactive or long‑lived tasks, use a [terminal session](./terminal.md) instead. + +## Access + +The global `Executor` is an `Executor` instance (clobbered to `window.Executor` by the terminal plugin). It has a built-in `BackgroundExecutor` instance for background-mode processes. + +```js +const Executor = globalThis.Executor; // Executor instance +const background = Executor.BackgroundExecutor; // BackgroundExecutor instance +``` + +Both instances share the same methods. + +## One-off execution + +### `execute(command, alpine?)` + +- Purpose: Runs a single shell command and waits for it to finish. Output is returned after the process exits (no live streaming of output). +- Parameters: + - `command` (string): The command to run. + - `alpine` (boolean, optional): Run inside the Alpine sandbox when `true`; run in the Android environment when `false`. +- Returns: `Promise` that resolves with stdout on success, or rejects with an error/stderr on failure. + +```js +// Outputting hello on stdout +Executor.execute('echo hello') + .then(console.log) + .catch(console.error); +``` + +or with `async/await`: + +```js +const output = await Executor.execute('echo hello'); +console.log(output); +``` + +> [!Warning] +> Do not run things like an infinite loop or a shell because `execute()` waits for the process to exit and a shell never exits on its own, avoid running those commands with this function. + +## Long-running processes + +### `start(command, onData, alpine?)` + +- Starts a shell process and enables real-time streaming of `stdout`, `stderr`, and `exit`. +- Parameters: + - `command` (string): The command to run (e.g. `"sh"`, `"ls -al"`). + - `onData` (function): `(type, data) => void`. `type` is `"stdout"`, `"stderr"`, or `"exit"` (the process exit code); `data` is the output line or exit code. + - `alpine` (boolean, optional): Run inside the Alpine sandbox when `true`. +- Returns: `Promise` resolving to a unique process UUID used by `write()`, `stop()`, and `isRunning()`. + +```js +const uuid = await Executor.start("sh", (type, data) => { + console.log(`[${type}] ${data}`); +}); +Executor.write(uuid, "echo Hello World\r"); +Executor.stop(uuid); +``` + +### `write(uuid, input)` + +Sends input to a running process's stdin. + +- Returns: `Promise`. + +```js +await Executor.write(uuid, "ls /sdcard\r"); +``` + +### `stop(uuid)` + +Terminates a running process. + +- Returns: `Promise`. + +### `isRunning(uuid)` + +Checks whether a process is still running. + +- Returns: `Promise`. + +```js +if (await Executor.isRunning(uuid)) { + await Executor.stop(uuid); +} +``` + +### `spawnStream(cmd, callback, onError?)` + +Spawns a process and exposes it as a raw WebSocket stream. Once the process is ready the callback is invoked with the connected `WebSocket`; use `ws.send()` to write to stdin and `ws.onmessage` to read stdout. + +- Parameters: + - `cmd` (string[]): Command and arguments (e.g. `["sh", "-c", "echo hi"]`). + - `callback` (function): `(ws) => void`. + - `onError` (function, optional): error handler. + +## Managing processes + +### `listProcesses()` + +Lists the processes currently managed by this Executor. + +- Returns: `Promise>`. `background` is `true` for a `BackgroundExecutor`. + +### `listAllProcesses()` + +Lists all running OS processes under the app's user id. + +- Returns: `Promise>`. + +### `killProcess(pid)` + +Forcefully kills a process by its native PID. + +- Returns: `Promise`. + +## Service control + +### `moveToForeground()` / `moveToBackground()` + +Moves the Executor service between foreground (shows the notification) and background. + +- Returns: `Promise`. + +### `stopService()` + +Stops the Executor service completely. This does **not** guarantee that all running processes are killed - the service just stops being active. The processes will keep running until stopped. + +- Returns: `Promise`. + +## Advanced + +### `loadLibrary(path)` + +Loads a native library from the given path. + +- Returns: `Promise`. + +```js +await Executor.loadLibrary('/path/to/library.so'); +``` + +> [!Warning] +> `loadLibrary()` has been deprecated and is no longer supported on newer Acode versions. + +### `setProotDebug(enabled)` + +Toggles proot debug output (used for the Alpine sandbox). + +- Returns: `Promise`. + +## Related APIs + +- Visual terminal sessions: [Terminal](./terminal.md) diff --git a/docs/advanced-apis/lsp.md b/docs/advanced-apis/lsp.md index 9f720aa..7ea7158 100644 --- a/docs/advanced-apis/lsp.md +++ b/docs/advanced-apis/lsp.md @@ -449,7 +449,7 @@ Returns a `TransportHandle`: { kind: "ready" } ``` -**Error** (worker → main) if initialization fails — rejects `ready` immediately and tears down the worker: +**Error** (worker → main) if initialization fails - rejects `ready` immediately and tears down the worker: ```js { kind: "error", message: "Failed to initialize worker" } @@ -586,7 +586,7 @@ lsp.servers.unregister(SERVER_ID); lsp.runtimes.unregister(RUNTIME_ID); ``` -Use `transport: { kind: "external" }` for worker servers — the runtime returns the real transport handle. Register your own server id; do not replace built-in ids like `html`, `css`, `json`, or `typescript`. +Use `transport: { kind: "external" }` for worker servers - the runtime returns the real transport handle. Register your own server id; do not replace built-in ids like `html`, `css`, `json`, or `typescript`. ### Runtime URI Resolution diff --git a/docs/advanced-apis/system.md b/docs/advanced-apis/system.md new file mode 100644 index 0000000..f63d0af --- /dev/null +++ b/docs/advanced-apis/system.md @@ -0,0 +1,252 @@ +# System + +The `system` module wraps Acode's native Android bridge (`cordova-plugin-system`). It is clobbered to `window.system` and provides low-level device, file, storage, permission, intent, and shortcut utilities that Acode itself uses. + +```js +const system = window.system; +``` + +Most methods are callback-based (`(success, error) => void`). Wrap them with `helpers.promisify` when you prefer promises: + +```js +const helpers = acode.require("helpers"); +const filesDir = await helpers.promisify(system.getFilesDir); +``` + +## Files + +### `getFilesDir(success, error)` + +Resolves the app's internal files directory path. + +```js +const filesDir = await helpers.promisify(system.getFilesDir); +``` + +### `getParentPath(path, success, error)` + +Resolves the parent directory of `path`. + +### `listChildren(path, success, error)` + +Lists the children of a directory path. + +### `mkdirs(path, success, error)` + +Recursively creates directories. + +### `fileExists(path, countSymlinks, success, error)` + +Checks whether a file exists. `countSymlinks` is a boolean passed as a string. + +### `copyToUri(srcUri, destUri, fileName, success, error)` + +Copies a file to a destination uri under `fileName`. + +### `writeText(path, content, success, error)` + +Writes text content to a file path. + +### `deleteFile(path, success, error)` + +Deletes a file path. + +### `createSymlink(target, linkPath, success, error)` + +Creates a symlink at `linkPath` pointing to `target`. + +### `setExec(path, executable, success, error)` + +Marks a file path as executable (`executable` is a boolean passed as a string). + +### `extractAsset(assetName, destinationPath, success, error)` + +Extracts an app asset to a destination path. + +### `getNativeLibraryPath(success, error)` + +Resolves the directory where native libraries are stored. + +## Storage management + +### `isManageExternalStorageDeclared(success, error)` + +Checks whether the app declares all-files access in its manifest. + +### `hasGrantedStorageManager(success, error)` + +Checks whether the app has been granted "All files access". + +### `requestStorageManager(success, error)` + +Requests the "All files access" permission. + +### `manageAllFiles(success, error)` + +Opens the system screen to grant all-files access. + +### `isExternalStorageManager(success, error)` + +Checks whether the app is currently an external storage manager. + +## Permissions + +### `hasPermission(permission, success, error)` + +Checks whether a runtime permission is granted. + +### `requestPermission(permission, success, error)` + +Requests a single runtime permission. + +### `requestPermissions(permissions, success, error)` + +Requests multiple runtime permissions at once. + +## App & device info + +### `getAppInfo(success, error)` + +Resolves information about the Acode app. + +### `getInstaller(success, error)` + +Resolves the package that installed the app (used for `window.appInstallSource`). + +### `getAndroidVersion(success, error)` + +Resolves the Android OS version. + +### `getArch(success, error)` + +Resolves the device architecture (e.g. `arm64-v8a`). + +### `getWebviewInfo(success, error)` + +Resolves WebView information (used by the terminal's engine detection). + +### `isPowerSaveMode(success, error)` + +Checks whether the device is in power-save mode. + +### `getGlobalSetting(key, success, error)` + +Reads a global Android setting by key. + +### `clearCache(success, error)` + +Clears the app's cache. + +## File actions & sharing + +### `fileAction(fileUri, filename, action, mimeType, error?)` + +Launches an Android intent for a file. `action` is one of `VIEW`, `EDIT`, `SEND`, or `RUN` (the app prepends `android.intent.action.`). Arguments are flexible: `system.fileAction(uri, filename, action, mimeType, onFail)`. + +```js +system.fileAction(fileUri, filename, "VIEW", "text/plain"); +``` + +### `shareText(text, success, error)` + +Shares a text string through the system share sheet. + +### `openInBrowser(src)` + +Opens a url in the system browser. + +### `inAppBrowser(url, title, showButtons, disableCache)` + +Opens a url in Acode's in-app browser. Returns an object with `onOpenExternalBrowser` and `onError` callbacks that can be assigned: + +```js +const browser = system.inAppBrowser(url, title, true, false); +browser.onOpenExternalBrowser = (url) => console.log("opened externally", url); +``` + +### `launchApp(app, className, extras?, success?, error?)` + +Launches an Android activity by package and class name, optionally passing intent extras (string/number/boolean values). + +```js +system.launchApp( + "com.example.app", + "com.example.app.MainActivity", + { user: "example", premium: true }, + (msg) => console.log(msg), + (err) => console.error(err), +); +``` + +## Shortcuts + +### `addShortcut(shortcut, success, error)` + +Adds a home-screen shortcut. `shortcut` is `{ id, label, description, icon, action, data }`. + +### `removeShortcut(id, success, error)` + +Removes a shortcut by id. + +### `pinShortcut(id, success, error)` + +Pins a shortcut. + +### `pinFileShortcut(shortcut, success, error)` + +Pins a file shortcut. + +## Intents + +### `getCordovaIntent(success, error)` + +Resolves the intent that launched the app (for handling external open requests). + +### `setIntentHandler(handler, onerror)` + +Registers a handler for intents received while the app is running. `handler` receives the intent data. + +## Text comparison + +Used by the editor's dirty-tracking and file-change detection. Both methods compare in a background thread. + +### `compareFileText(fileUri, encoding, currentText): Promise` + +Reads the file at `fileUri` and compares it to `currentText`. Resolves `true` when the content **differs**, `false` when it matches. + +### `compareTexts(text1, text2): Promise` + +Compares two strings. Resolves `true` when they **differ**, `false` when equal. + +```js +const changed = await system.compareFileText(file.uri, file.encoding, text); +``` + +## UI + +### `setUiTheme(systemBarColor, theme, success?, error?)` + +Sets the Android system bar colors to match a theme. `systemBarColor` is a hex color; `theme` is the theme id. A pure white color is mapped to `#fffffe` so status bar icons stay visible. + +### `setInputType(type, success, error)` + +Changes the soft-keyboard input type. + +### `setNativeContextMenuDisabled(disabled, success, error)` + +Enables or disables the native context menu on the WebView. + +## Rewards + +### `getRewardStatus(success, error)` + +Resolves the current reward status (used by the ad-reward system). + +### `redeemReward(offerId, success, error)` + +Redeems a reward offer. + +## Related APIs + +- Install source & other globals: [Other Global Utilities](../global-apis/global-utilities.md) +- `helpers.promisify` for callback-style methods: [Helpers](../utilities/helpers.md) diff --git a/docs/advanced-apis/terminal.md b/docs/advanced-apis/terminal.md index 462f511..f99a8c9 100644 --- a/docs/advanced-apis/terminal.md +++ b/docs/advanced-apis/terminal.md @@ -155,27 +155,9 @@ This is useful when a plugin needs to decide whether it can use terminal-backed ## Background Execution (No Terminal) -Use the globally available `Executor` when you need to run a one‑off shell command without opening a visual terminal session. +Use the globally available `Executor` to run shell commands without opening a visual terminal session - one-off commands, long-running processes with streaming output, and background-mode execution. -> [!Warning] -> Prefer visible terminals for transparency. Avoid hiding work in the background and do not start long‑running processes via `Executor.execute`. For interactive or long‑lived tasks, use a terminal session instead. - -### `Executor.execute(command, alpine?)` - -- Purpose: Runs a single shell command and waits for it to finish. Output is returned after the process exits (no live streaming of output). -- Parameters: - - `command` (string): The command to run. - - `alpine` (boolean, optional): Run inside the Alpine sandbox when `true`; run in the Android environment when `false`. -- Returns: `Promise` that resolves with stdout on success, or rejects with an error/stderr on failure. - -#### Example - -```js -// Quick directory listing without opening a terminal UI -Executor.execute('ls -l') - .then(console.log) - .catch(console.error); -``` +See [Executor](./executor.md). ## Example: Themed Output Terminal diff --git a/docs/advanced-apis/webview.md b/docs/advanced-apis/webview.md index feba94c..d590077 100644 --- a/docs/advanced-apis/webview.md +++ b/docs/advanced-apis/webview.md @@ -144,7 +144,7 @@ Use `off(event, callback)` to remove a listener. - Modes: `fullscreen` hosts the WebView in its own activity; `hidden` is headless and never displayed, useful for background automation or scraping. - Back button: In fullscreen mode it navigates back through page history first; when nothing is left, the WebView closes and the `closed` event fires. - Hide/Show: `hide()` backgrounds the fullscreen activity without destroying it, so `show()` restores it with the page state intact. -- Cleanup: Instances are not tied to your plugin's lifecycle. Destroy every instance you create — ideally in your plugin's `destroy()` function — so hidden WebViews don't outlive the plugin. +- Cleanup: Instances are not tied to your plugin's lifecycle. Destroy every instance you create - ideally in your plugin's `destroy()` function - so hidden WebViews don't outlive the plugin. - Security: Hosted content is isolated. File and content scheme access is disabled, only `http(s)` URLs can load, and non-http(s) navigation (`file:`, `intent:`, `javascript:`, `tel:`, ...) is always blocked. When `allowNavigation` is `false`, all navigation is blocked. ## Example: Headless Title Fetcher diff --git a/docs/editor-components/editor-file.md b/docs/editor-components/editor-file.md index d69eb9c..8b47eae 100644 --- a/docs/editor-components/editor-file.md +++ b/docs/editor-components/editor-file.md @@ -51,14 +51,26 @@ Both methods are equivalent and accept & return the same parameters. | tabIcon | `string` | Icon class for the file tab | `'file file_type_default'` | | content | string \| [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement) | Custom content element or HTML string. Strings are sanitized using DOMPurify | - | | stylesheets | `string\|string[]` | Custom stylesheets for tab. Can be URL, or CSS string | - | -| highlightStyles | `boolean` | Adopt the static CodeMirror highlight stylesheet into this custom tab's shadow root. Use only when the tab will render `codeHighlight` HTML. Available from **versionCode `1008`** | `false` | | hideQuickTools | `boolean` | Whether to hide quicktools for this tab | `false` | | pinned | `boolean` | Pin the tab to prevent accidental closing | `false` | -| readOnly | `boolean` | Open the file as read-only | `false` | +| readOnly | `boolean` | Open the file as read-only (sets `editable` to `false`) | `false` | | paneId | `string` | Target editor pane id (multi-pane layout) | - | | pane | `object` | Target editor pane instance (multi-pane layout) | - | | isPanePlaceholder | `boolean` | Temporary empty tab for an empty pane | `false` | +#### Version-metadata options + +These options seed the dirty-tracking / disk-conflict system (see [Dirty tracking](#dirty-tracking--disk-conflict)). + +| Property | Type | Description | +|----------|------|-------------| +| docVersion | `number` | Current document version for dirty tracking | +| savedVersion | `number` | Document version last saved or loaded from disk | +| cacheVersion | `number` | Document version last written to crash cache | +| savedMtime | `number` | File mtime last saved or loaded from disk | +| diskMtime | `number` | Latest known file mtime on disk | +| hasDiskConflict | `boolean` | Whether the editor and disk both changed | + ## Properties ### Read-only Properties @@ -84,8 +96,12 @@ Both methods are equivalent and accept & return the same parameters. | loaded | `boolean` | Whether file has completed loading text | | loading | `boolean` | Whether file is still loading text | | session | `Proxy` | Session state with Ace-compatible helper methods | -| readOnly | `boolean` | Whether file is readonly | +| readOnly | `boolean` | Whether file is readonly. This is a plain field - assign the read-only state on the editor with [`setReadOnly()`](#setreadonlyvalue) | | markChanged | `boolean` | Whether to mark changes when session text changes | +| currentMode | `string` | Currently active syntax mode name (set by [`setMode()`](#setmodemode)) | +| currentLanguageExtension | `unknown` | CodeMirror language extension for the current mode (may be `null`) | +| headerSubtitle | `string` | Subtitle shown in the editor header for this file | +| hasVersionMetadata | `boolean` | Whether the file has version metadata for dirty tracking | ### Writable(setters) Properties @@ -98,7 +114,10 @@ Both methods are equivalent and accept & return the same parameters. | eol | `'windows' \| 'unix'` | Set end of line character | | editable | `boolean` | Set file editability | | pinned | `boolean` | Set file pinned state | -| readOnly | `boolean` | Set file readonly state | + +::: warning +`readOnly` is **not** a setter. Assigning `file.readOnly = true` only updates the field and does not reconfigure the CodeMirror editor. Use `file.setReadOnly(true)` to actually make the editor read-only. +::: ## Methods @@ -164,20 +183,54 @@ Toggles the pinned State of the file file.togglePinned(); ``` +#### [render()](#render) +Makes the file active and renders its content. Also removes the default untitled tab and any empty pane-placeholder tabs in the same pane. + +```js +file.render(); +``` + +#### [runAction()](#runaction) +Runs the file through the system file action (equivalent to opening it with the run action of the OS). + +```js +file.runAction(); +``` + +#### [setCustomTitle(titleFn)](#setcustomtitletitlefn) +Sets a custom title function used for the header subtitle of this file. Called with no arguments, it must return the title string. + +```js +file.setCustomTitle(() => `PID: ${file.pid}`); +``` + ### Editor Operations -#### [setMode(mode)](#setmodemode) +#### [setMode(mode?, options?)](#setmodemode) Sets syntax highlighting mode for the file. +- `mode` (string, optional): Mode name. When omitted (or empty), the mode is resolved from the user's mode associations (`localStorage.modeassoc`) and the filename. +- `options.recommend` (boolean, default `true`): When `false`, skips the "recommend an extension for this language" prompt. + +Updates `currentMode` and `currentLanguageExtension`. + ```js file.setMode('javascript'); +file.setMode('javascript', { recommend: false }); ``` -#### [writeToCache()](#writetocache) -Writes file content to cache. +#### [setReadOnly(value)](#setreadonlyvalue) +Sets the read-only state and reconfigures the CodeMirror editor accordingly. This is the correct way to toggle read-only - assigning the `readOnly` field directly does not touch the editor. ```js -await file.writeToCache(); +file.setReadOnly(true); +``` + +#### [readCanRun()](#readcanrun) +Async; resolves whether the run button should be shown for this file (checks open-folder `index.html`, runnable extensions, and any `canrun` handler). You normally only need [`canRun()`](#canrun). + +```js +await file.readCanRun(); ``` #### [isChanged()](#ischanged) @@ -261,8 +314,77 @@ Removes event listener. file.off('save', callback); ``` +## Dirty tracking & disk conflict + +Acode tracks whether the in-memory document differs from what is on disk, so it can warn about unsaved changes and detect conflicts where both the editor and the file changed. + +State fields: + +| Field | Type | Description | +|-------|------|-------------| +| docVersion | `number` | Current document version (incremented on every edit) | +| savedVersion | `number` | Document version last saved or loaded from disk | +| cacheVersion | `number` | Document version last written to the crash cache | +| savedMtime | `number \| null` | File mtime last saved or loaded from disk (ms) | +| diskMtime | `number \| null` | Latest known file mtime on disk (ms) | +| hasDiskConflict | `boolean` | `true` when both the editor and the disk changed | +| hasVersionMetadata | `boolean` | Whether any version metadata has been recorded | + +Normally you read `file.isUnsaved` / `file.hasUnsavedChanges()`. Use the `mark*` methods to keep the state accurate when you load, edit, save, or detect external changes. + +#### [hasUnsavedChanges(): boolean](#hasunsavedchanges) +Checks whether the file has unsaved changes, comparing the current document with the last saved/loaded document. + +```js +if (file.hasUnsavedChanges()) { + // prompt to save +} +``` + +#### [refreshUnsavedState(): boolean](#refreshunsavedstate) +Recomputes `isUnsaved` from the current state and returns it. + +#### [markLoaded({ mtime, isUnsaved, savedDoc }?)](#markloaded) +Marks the file as loaded. Resets `docVersion` (to `1` when `isUnsaved`, else `0`), sets `savedVersion`, `cacheVersion`, `savedMtime`, and `diskMtime`, and clears `hasDiskConflict`. + +#### [markEdited({ exact }?)](#markedited) +Increments `docVersion` and marks the file as unsaved. When `exact` is `true`, `isUnsaved` is recomputed instead of just set to `true`. Gives the file a new id if it was still the default untitled session. + +#### [markSaved({ mtime, savedDoc, savedVersion }?)](#marksaved) +Marks the file as saved. Updates `savedVersion`, `savedMtime`, `diskMtime`, clears `hasDiskConflict`, and refreshes `isUnsaved`. + +#### [markDiskChanged({ mtime, deleted }?)](#markdiskchanged) +Records an external change. When `deleted` is `true` the file is marked deleted and unsaved. Otherwise sets `hasDiskConflict` when both `docVersion !== savedVersion` and `diskMtime !== savedMtime`, then refreshes `isUnsaved`. + +## Cache pipeline + +Acode keeps a crash-recovery cache of every open editor file. Changes are written to `cacheFile` (debounced by default) so an app crash does not lose unsaved work. + +#### [scheduleCacheWrite(delay = 1500)](#schedulecachewritedelay--1500) +Schedules a cache write after `delay` ms (or writes immediately when `delay <= 0`). No-op when the cache is already up to date with `docVersion`. + +```js +file.scheduleCacheWrite(500); +``` + +#### [flushCacheWrite()](#flushcachewrite) +Flushes any pending scheduled cache write immediately and waits for in-flight writes. + +```js +await file.flushCacheWrite(); +``` + +#### [writeToCache()](#writetocache) +Writes file content to cache immediately (see also `isChanged()` below). + +```js +await file.writeToCache(); +``` + ## Events +### `on(event, callback)` events + The EditorFile class emits the following events: | Event | Description | @@ -281,6 +403,48 @@ The EditorFile class emits the following events: | run | File is run | | canRun | File runnable state changes | +### `on*` callback properties + +Each event also has a direct callback property. Setting it is equivalent to registering a listener for that event: + +| Property | Event | +|----------|-------| +| `onsave` | `save` | +| `onchange` | `change` | +| `onfocus` | `focus` | +| `onblur` | `blur` | +| `onclose` | `close` | +| `onrename` | `rename` | +| `onload` | `load` | +| `onloaderror` | `loadError` | +| `onloadstart` | `loadStart` | +| `onloadend` | `loadEnd` | +| `onchangemode` | `changeMode` | +| `onrun` | `run` | +| `oncanrun` | `canRun` | +| `onpinstatechange` | Called with the new pinned value whenever the pinned state changes | + +```js +file.onpinstatechange = (pinned) => { + console.log("pinned:", pinned); +}; +``` + +### Events emitted on `editorManager` + +These events fire on `editorManager` (see [EditorManager](../global-apis/editor-manager.md#events)) for file lifecycle: + +| Event | Payload | Description | +|-------|---------|-------------| +| `new-file` | `file` | A new file/tab was created | +| `file-loaded` | `file` | The file finished loading its text | +| `file-loading-preview` | `file`, `text` | A remote-file preview became available while loading | +| `file-content-changed` | `file` | Content changed by a plugin (inactive-file edit) | +| `rename-file` | `file` | File renamed or moved | +| `remove-file` | `file` | File removed/closed | + +`update` sub-actions are also emitted, for example `"file-changed"`, `"read-only"`, `"pin-tab"`, `"remove-file"`, `"switch-file"`. + ## Examples ### Creating a New File @@ -334,24 +498,6 @@ file1.addStyle('/styles/additional.css'); Custom Editor Tabs are isolated from main DOM using Shadow DOM, so don't select tab elements using `document`. ::: -Syntax highlighting inside a custom tab is opt-in and available from **versionCode `1008`**. Set `highlightStyles: true` so the tab's shadow root gets the theme stylesheet, then add the `cm-highlighted` class to the wrapper. See [Code Highlight](../utilities/code-highlight.md). - -```js -const codeHighlight = acode.require("codeHighlight"); -const html = await codeHighlight.highlightCodeBlock(source, "javascript"); - -const code = document.createElement("code"); -code.className = codeHighlight.HIGHLIGHT_CLASS; -code.innerHTML = html; - -new EditorFile("snippet.js", { - type: "custom", - content: code, - highlightStyles: true, - hideQuickTools: true, -}); -``` - ### Saving File Changes ```js diff --git a/docs/editor-components/file-index.md b/docs/editor-components/file-index.md index 8347de0..0dea643 100644 --- a/docs/editor-components/file-index.md +++ b/docs/editor-components/file-index.md @@ -11,7 +11,7 @@ Available from **versionCode `1002`**. Set `"minVersionCode": 1002` in `plugin.j | | `fileList` (legacy) | `fileIndex` (new) | | --- | --- | --- | | SAF / `file://` | No longer fully listed | Native SQLite index | -| FTP / SFTP / custom | Still works | Not supported — use `fileList` | +| FTP / SFTP / custom | Still works | Not supported - use `fileList` | | API style | Sync tree objects | Async flat records | | Large workspaces | Heavy WebView tree | Paginated native queries | | Search | App-side workers | Optional native streaming search | @@ -29,7 +29,7 @@ Feature detection: ```js const fileIndex = acode.require("fileIndex"); if (!fileIndex?.query) { - // Running on an older Acode build — use fileList fallback + // Running on an older Acode build - use fileList fallback } ``` @@ -307,11 +307,11 @@ const { entries } = await fileIndex.query({ Key differences: -1. **`fileIndex` is asynchronous** — always `await` queries and scans. -2. **Results are flat records** — no `children` / `parent` tree navigation. -3. **Pagination** — use `cursor` / `hasMore` for large result sets. -4. **SAF + `file://` only** — keep using `fileList` for FTP/SFTP if needed. -5. **Search events may be batched** — handle `search-results` as well as `search-result`. +1. **`fileIndex` is asynchronous** - always `await` queries and scans. +2. **Results are flat records** - no `children` / `parent` tree navigation. +3. **Pagination** - use `cursor` / `hasMore` for large result sets. +4. **SAF + `file://` only** - keep using `fileList` for FTP/SFTP if needed. +5. **Search events may be batched** - handle `search-results` as well as `search-result`. Hybrid pattern (native roots + remote fallback): @@ -347,7 +347,7 @@ Scan and search jobs emit events with a shared shape. Common `type` values: | Type | When | | --- | --- | | `status` | Progress message during scan/search | -| `progress` | Numeric progress (`data` is 0–100) | +| `progress` | Numeric progress (`data` is 0-100) | | `batch` | Optional entry batches during scan | | `search-result` | One file's matches (`batchResults: false`) | | `search-results` | Array of file match payloads (`batchResults: true`) | diff --git a/docs/editor-components/file-list.md b/docs/editor-components/file-list.md index 170fa60..3668fa9 100644 --- a/docs/editor-components/file-list.md +++ b/docs/editor-components/file-list.md @@ -1,10 +1,10 @@ # File List API -::: warning Deprecated — migrate to File Index +::: warning Deprecated - migrate to File Index `acode.require("fileList")` is **deprecated** from **versionCode `1002`**. - SAF (`content:`) and `file://` workspaces are no longer fully listed here. -- Those roots live in the native index — use [`fileIndex`](./file-index.md) . +- Those roots live in the native index - use [`fileIndex`](./file-index.md) . - `fileList` still contains **non-native** providers only (FTP, SFTP, custom storage). ```js diff --git a/docs/getting-started/create-plugin.md b/docs/getting-started/create-plugin.md index f972f93..d33d039 100644 --- a/docs/getting-started/create-plugin.md +++ b/docs/getting-started/create-plugin.md @@ -126,7 +126,7 @@ For local development, start a dev server using `npm run dev`. In Acode, use the It's more convenient to manage this from the sidebar. When you install a local plugin(either using url or selecting the zip), Acode will add a **reload** icon in the **Extensions** tab of the sidebar. This is useful because the server automatically builds the plugin ZIP when changes are made. Simply press the reload button to apply the latest changes instantly. -This makes plugin development a much smoother experience—previously, it was quite frustrating, but this feature was recently added to improve the workflow. +This makes plugin development a much smoother experience - previously, it was quite frustrating, but this feature was recently added to improve the workflow. ::: ## Creating Plugins with the CLI diff --git a/docs/getting-started/intro.md b/docs/getting-started/intro.md index c160722..c495c5b 100644 --- a/docs/getting-started/intro.md +++ b/docs/getting-started/intro.md @@ -13,7 +13,7 @@ title: Acode Plugins ### Language Flexibility -Acode plugins are primarily written in JavaScript, offering a familiar and widely-used language for developers. Additionally, for those who prefer TypeScript, **good news 🥳** — Acode supports `TypeScript` for plugin development, providing the benefits of static typing and improved developer experience. +Acode plugins are primarily written in JavaScript, offering a familiar and widely-used language for developers. Additionally, for those who prefer TypeScript, **good news 🥳** - Acode supports `TypeScript` for plugin development, providing the benefits of static typing and improved developer experience. ## Installing Acode Plugins diff --git a/docs/getting-started/understanding-plugin.md b/docs/getting-started/understanding-plugin.md index 6c1e65b..5aabeaf 100644 --- a/docs/getting-started/understanding-plugin.md +++ b/docs/getting-started/understanding-plugin.md @@ -27,11 +27,11 @@ If you skip `setPluginInit`, your script may load, but your plugin logic will no ## What You Get In `init` -Your init function receives: +The `init` callback registered with `setPluginInit` receives three arguments: -- `baseUrl`: internal base URL to your plugin files +- `baseUrl`: internal base URL to your plugin files (normalize it with a trailing slash, see below) - `$page`: a plugin page object for UI screens -- `cache`: object with: +- `options`: object with: - `cacheFileUrl` - `cacheFile` - `firstInit` @@ -39,37 +39,53 @@ Your init function receives: Use `firstInit` for one-time setup or migration. +`ctx` is your plugin's native-backed context: encrypted secret storage and permission checks. See [Plugin Context (`ctx`)](../plugin-essentials/plugin-context.md). + ## Recommended `main.js` Shape +The official templates structure your plugin as an `AcodePlugin` class with `init()` and `destroy()`: + ```js import plugin from "../plugin.json"; -function init(baseUrl, $page, cache) { - const commands = acode.require("commands"); - - commands.addCommand({ - name: "example.open", - description: "Open Example Panel", - exec: () => { - $page.innerHTML = "

Example Plugin

"; - $page.show(); - }, - }); -} +class AcodePlugin { + baseUrl = ""; -function unmount() { - const commands = acode.require("commands"); - commands.removeCommand("example.open"); + async init(_page, _cacheFile, _cacheFileUrl, _firstInit, _ctx) { + // plugin code + } + + async destroy() { + // plugin clean up + } } -acode.setPluginInit(plugin.id, init); -acode.setPluginUnmount(plugin.id, unmount); +if (window.acode) { + const acodePlugin = new AcodePlugin(); + + acode.setPluginInit(plugin.id, async (baseUrl, $page, { cacheFileUrl, cacheFile, firstInit, ctx }) => { + acodePlugin.baseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`; + await acodePlugin.init($page, cacheFile, cacheFileUrl, firstInit, ctx); + }); + + acode.setPluginUnmount(plugin.id, () => { + acodePlugin.destroy(); + }); +} ``` +Breaking that down: + +- `window.acode` is only present once Acode's API is ready, so registration is wrapped in a guard. +- `plugin.id` comes from your `plugin.json`, so the registration always matches the installed id. +- The `init` callback receives `(baseUrl, $page, options)`, where `options` is `{ cacheFileUrl, cacheFile, firstInit, ctx }`. Those are forwarded to your class's `init`. +- `baseUrl` is stored with a guaranteed trailing slash so you can build file paths with `Url.join` or string concatenation. +- `destroy()` is wired to `setPluginUnmount` so it runs on disable/reload/uninstall. `init` is awaited, so heavy setup can be done inside it. + ## What Happens On Disable / Enable / Uninstall - Disable: - - Acode calls `acode.unmountPlugin(id)` which triggers your unmount. + - Acode calls `acode.unmountPlugin(id)` which triggers your registered unmount (your class's `destroy()`). - Plugin runtime state is cleared (including plugin cache file). - Enable: - Acode loads the plugin again and runs init again. @@ -77,7 +93,7 @@ acode.setPluginUnmount(plugin.id, unmount); - Plugin files are removed. - Acode runs unmount cleanup for loaded resources. -Treat `init` as repeatable and `unmount` as mandatory cleanup. +Treat `init` as repeatable and `destroy` as mandatory cleanup. ## Failure Behavior You Should Know @@ -96,5 +112,5 @@ acode.clearBrokenPluginMark("com.example.plugin"); - Keep `init` fast; do heavy work lazily. - Register commands through `acode.require("commands")`. -- Always remove listeners, commands, intervals, and UI hooks in `unmount`. +- Always remove listeners, commands, intervals, and UI hooks in `destroy`. - Avoid storing important state only in memory; use cache/settings when needed. diff --git a/docs/global-apis/ace.md b/docs/global-apis/ace.md index 3c6931f..8d4a180 100644 --- a/docs/global-apis/ace.md +++ b/docs/global-apis/ace.md @@ -10,7 +10,7 @@ This page exists for plugin migration from Ace-era APIs. - Use `acode.require("commands")` for command registration/removal. - Use `acode.require("editorLanguages")` to register or remove language modes. - Use `acode.require("editorThemes")` to register or apply editor themes. -- Use `acode.require("codemirror")` (or `@codemirror/*` / `@lezer/*`) for the same CodeMirror packages the app uses — see [CodeMirror packages](../utilities/codemirror.md). +- Use `acode.require("codemirror")` (or `@codemirror/*` / `@lezer/*`) for the same CodeMirror packages the app uses - see [CodeMirror packages](../utilities/codemirror.md). - Use `editorManager.isCodeMirror` to check whether current acode uses codemirror or ace, if its true then its codemirror and if it is null or undefined then its ace. ## Legacy Compatibility diff --git a/docs/global-apis/acode.md b/docs/global-apis/acode.md index b1b6a9d..4045cbe 100644 --- a/docs/global-apis/acode.md +++ b/docs/global-apis/acode.md @@ -13,7 +13,7 @@ This method is used to register the plugin. This method takes two parameters, `p **Example:** ```js -acode.setPluginInit('com.example.plugin', (baseUrl, $page, cache) => { // [!code focus] +acode.setPluginInit(plugin.id, (baseUrl, $page, { cacheFileUrl, cacheFile, firstInit, ctx }) => { // [!code focus] const commands = acode.require("commands"); commands.addCommand({ name: 'example-plugin', @@ -29,6 +29,10 @@ acode.setPluginInit('com.example.plugin', (baseUrl, $page, cache) => { // [!code }); ``` +::: tip +The official templates wrap this in an `AcodePlugin` class with `init()` and `destroy()` methods. See [Understanding How Plugins Work](../getting-started/understanding-plugin.md) for the recommended `main.js` shape. +::: + ### `init(baseUrl: string, $page: WCPage, options: object)` When the init function is called, it will receive 3 parameters: @@ -43,6 +47,7 @@ When the init function is called, it will receive 3 parameters: * `cacheFile File: object` File object of the cached file. Using this object, you can write/read the file. * `firstInit: boolean` If this is the first time the plugin is loaded, this value will be true. Otherwise, it will be `false`. + * `ctx: PluginContext` Your plugin's native context: encrypted secret storage and permission checks. See [Plugin Context (`ctx`)](../plugin-essentials/plugin-context.md). ### `Settings Object` @@ -306,11 +311,50 @@ Clears a plugin's broken mark so it can be retried on next load. acode.clearBrokenPluginMark("com.example.plugin"); ``` +### `joinUrl(...parts: string[]): string` + +Joins URL parts into a single url (delegates to the [`Url`](../utilities/url.md) module's `join`). + +```js +const url = acode.joinUrl("file:///sdcard", "Acode", "file.txt"); +``` + +### `setLoadingMessage(message: string): void` + +Sets a small loading message on the app body (`data-small-msg` attribute). Pass an empty string to clear it. + +```js +acode.setLoadingMessage("Loading plugins..."); +``` + +### `exitAppMessage: string | null` + +Read-only. Returns a localized warning string when there are unsaved files (used by Acode when the user tries to exit), otherwise `null`. + +```js +const msg = acode.exitAppMessage; // "You have unsaved files..." or null +``` + +### `unmountPlugin(pluginId: string): void` + +Runs the unmount callback registered via [`setPluginUnmount`](#setpluginunmountpluginid-string-unmount-function), deletes the plugin's cache file, and removes the plugin's settings page. Called by Acode on disable/reload/uninstall - plugins normally do not need to call it. + +### `fsOperation(file: string): FsOperation` + +Returns a file-system operation object for the given path/uri (see [File System](../utilities/fs.md)). + +```js +const fs = acode.fsOperation("file:///sdcard/Acode/plugin.json"); +const exists = await fs.exists(); +``` + ## Related APIs - Commands API (preferred for adding/removing commands): [Commands](../utilities/commands.md) - CodeMirror editor theme API: [Editor Themes](../utilities/editor-themes.md) -- Static CodeMirror highlighter (versionCode `1008+`): [Code Highlight](../utilities/code-highlight.md) +- Static code highlighting status: [Code Highlight](../utilities/code-highlight.md) - Language server API: [LSP](../advanced-apis/lsp.md) - File handler API: [File Handlers](../advanced-apis/file-handlers.md) - Terminal API: [Terminal](../advanced-apis/terminal.md) +- Background process API: [Executor](../advanced-apis/executor.md) +- Native Android bridge: [System](../advanced-apis/system.md) diff --git a/docs/global-apis/config.md b/docs/global-apis/config.md new file mode 100644 index 0000000..d02718a --- /dev/null +++ b/docs/global-apis/config.md @@ -0,0 +1,102 @@ +# Config + +The `config` module exposes Acode's internal read-only configuration: app constants, ports, URLs, and feature flags. + +Require it with `acode.require('config')`. + +```js +const config = acode.require("config"); +``` + +## Read-only proxy + +The module is a **read-only proxy** around the internal `config` object (`src/lib/config.js`). Any attempt to set, define, delete, or change the prototype of a property is blocked and logged to the console as a security warning. Values can be read at any time; they cannot be mutated by plugins. + +```js +config.API_BASE; // https://acode.app/api +config.FONT_SIZE; // /^[0-9\.]{1,3}(px|rem|em|pt|mm|pc|in)$/ +``` + +## Properties + +### App identity & API + +| Property | Type | Description | +|----------|------|-------------| +| `BASE_URL` | `string` | Root URL of the Acode website (`https://acode.app`) | +| `API_BASE` | `string` | Base URL of the Acode plugin API (`https://acode.app/api`) | +| `PLAY_STORE_URL` | `string` | Play Store listing URL for the current app package | +| `FEEDBACK_EMAIL` | `string` | Support email (`acode@foxdebug.com`) | +| `ERUDA_CDN` | `string` | CDN URL of the Eruda console (`https://cdn.jsdelivr.net/npm/eruda`) | + +### Pro / monetization + +| Property | Type | Description | +|----------|------|-------------| +| `HAS_PRO` | `boolean` | The real free/Pro flag. `true` when the user is on a Pro (paid) build or has Pro unlocked, `false` on the free build. **This is the replacement for the undocumented `IS_FREE_VERSION` global, which does not exist.** | +| `SKU_LIST` | `string[]` | Frozen array of purchase SKUs (`crystal`, `bronze`, `silver`, `gold`, `platinum`, `titanium`) | + +### Editor + +| Property | Type | Description | +|----------|------|-------------| +| `SUPPORTED_EDITOR` | `string` | Editor engine identifier (`"cm"` for CodeMirror 6) | +| `FILE_NAME_REGEX` | `RegExp` | Regex that matches valid file names | +| `FONT_SIZE` | `RegExp` | Regex that matches valid font-size CSS values | +| `DEFAULT_FILE_NAME` | `string` | Default name for a new file (`untitled.txt`) | +| `DEFAULT_FILE_SESSION` | `string` | Session id used for the default untitled tab (`default-session`) | +| `CUSTOM_THEME` | `string` | CSS selector for the custom theme (`body[theme="custom"]`) | + +### Ports + +| Property | Type | Description | +|----------|------|-------------| +| `CONSOLE_PORT` | `number` | Port used by the app console (`8159`) | +| `SERVER_PORT` | `number` | Port used by the local preview server (`8158`) | +| `PREVIEW_PORT` | `number` | Port used by the live preview (`8158`) | + +### Behaviour constants + +| Property | Type | Description | +|----------|------|-------------| +| `VIBRATION_TIME` | `number` | Short vibration duration in ms (`30`) | +| `VIBRATION_TIME_LONG` | `number` | Long vibration duration in ms (`150`) | +| `SCROLL_SPEED_SLOW` | `string` | Slow scroll speed constant (`"SLOW"`) | +| `SCROLL_SPEED_NORMAL` | `string` | Normal scroll speed constant (`"NORMAL"`) | +| `SCROLL_SPEED_FAST` | `string` | Fast scroll speed constant (`"FAST"`) | +| `SCROLL_SPEED_FAST_X2` | `string` | 2× fast scroll speed constant (`"FAST_X2"`) | +| `SIDEBAR_SLIDE_START_THRESHOLD_PX` | `number` | Drag distance in px before the sidebar starts sliding (`20`) | +| `LOG_FILE_NAME` | `string` | Name of the log file written to `DATA_STORAGE` (`Acode.log`) | + +### Social links + +| Property | Type | Description | +|----------|------|-------------| +| `DOCS_URL` | `string` | `https://docs.acode.app` | +| `GITHUB_URL` | `string` | `https://github.com/Acode-Foundation/Acode` | +| `TELEGRAM_URL` | `string` | `https://t.me/foxdebug_acode` | +| `DISCORD_URL` | `string` | `https://discord.gg/nDqZsh7Rqz` | +| `TWITTER_URL` | `string` | `https://x.com/foxbiz_io` | +| `INSTAGRAM_URL` | `string` | `https://www.instagram.com/foxbiz.io/` | +| `FOXBIZ_URL` | `string` | `https://foxbiz.io` | + +## Example + +```js +const config = acode.require("config"); + +// Feature-gate behaviour on Pro +if (config.HAS_PRO) { + // premium-only feature +} + +// Build a link to the plugin registry API +fetch(`${config.API_BASE}/plugin/com.example.plugin`); + +// Open the Play Store listing in the browser +system.openInBrowser(config.PLAY_STORE_URL); +``` + +## Related APIs + +- [Other Global Utilities](./global-utilities.md) - storage directories, `BuildInfo`, `window.log`, etc. diff --git a/docs/global-apis/editor-manager.md b/docs/global-apis/editor-manager.md index d24b044..6befea4 100644 --- a/docs/global-apis/editor-manager.md +++ b/docs/global-apis/editor-manager.md @@ -69,6 +69,43 @@ Open-file tab list (pane-aware when multi-pane layout is active). Whether the active editor is currently scrolling. +### `TIMEOUT_VALUE: number` + +Internal debounce/operation timeout used by the editor manager (default `500` ms). Used internally when waiting for editor operations; plugins generally only need it for tuning their own timeouts to match. + +### `readOnlyCompartment` + +The CodeMirror [`Compartment`](https://codemirror.net/docs/ref/#state.Compartment) used to toggle the read-only state of editor views. Useful for advanced plugins that build their own CodeMirror configuration. + +### `getLspMetadata(file): object | null` + +Builds the LSP metadata object for a file: + +- `uri` - file uri (or an `untitled://acode/` uri for untitled files) +- `languageId` - resolved language id +- `languageName` - `file.currentMode` or the language id +- `view` - the target editor view +- `file` - the file +- `rootUri` - the matching added-folder url, or the file uri + +Returns `null` for non-editor files. + +### `getEditorHeight(editor): number` + +Returns the scrollable height of an editor view (`max(scrollHeight - clientHeight, 0)`). + +### `getEditorWidth(editor): number` + +Returns the scrollable width of an editor view. + +### `reapplyActiveFile()` + +Force-recreates the active file's editor state in the current pane (used after configuration changes). + +### `syncOpenFileList()` + +Synchronizes the visible open-file tab list with the current pane layout and file order. + ## Opening files There is no `editorManager.addNewFile` API. Create tabs with: @@ -186,6 +223,21 @@ editorManager.openNextEditorFromHistory(); editorManager.recordHistory(file); // usually automatic on switch ``` +History state is also exposed for inspection: + +- `editorManager.editorHistory` - array of `EditorFile` entries in the navigation history (max 100, oldest first). +- `editorManager.editorHistoryIndex` - index of the current position within `editorHistory`. + +```javascript +const backStack = editorManager.editorHistory.slice( + 0, + editorManager.editorHistoryIndex + 1, +); +const forwardStack = editorManager.editorHistory.slice( + editorManager.editorHistoryIndex + 1, +); +``` + ## LSP / cache helpers ```javascript @@ -206,13 +258,14 @@ await editorManager.flushCacheWrites(); | `rename-file` | File renamed | | `save-file` | File saved | | `file-loaded` | File finished loading | +| `file-loading-preview` | Remote-file preview text became available (payload: file, text) | | `file-content-changed` | File content changed | | `add-folder` | Workspace folder added | | `remove-folder` | Workspace folder removed | -| `update-folder` | Workspace folder updated | | `new-file` | New file created | -| `init-open-file-list` | Open file list initialized | +| `int-open-file-list` | Open file list initialized | | `remove-file` | File removed | +| `editor-state-changed` | The active editor's document changed (payload: the CodeMirror view) | | `update` | Generic update (often with a sub-action) | `update` listeners may receive a sub-action as the first argument, for example: @@ -220,6 +273,9 @@ await editorManager.flushCacheWrites(); - `"pin-tab"` - `"switch-file"` - `"read-only"` +- `"file-changed"` + +For each `update` emission, a detailed `update:` event is also emitted (e.g. `update:pin-tab`) whose payload is everything after the sub-action. ```javascript editorManager.on("switch-file", () => { diff --git a/docs/global-apis/global-utilities.md b/docs/global-apis/global-utilities.md index 9004fe3..2c7949a 100644 --- a/docs/global-apis/global-utilities.md +++ b/docs/global-apis/global-utilities.md @@ -2,33 +2,106 @@ These extra global APIs provide essential information about asset directories, storage locations, app features, and system specifications. -## ASSETS_DIRECTORY -`` The directory where all the assets are stored. +## Storage & asset directories -## CACHE_STORAGE -`` The directory where all the cache files are stored. +| Global | Type | Description | +|--------|------|-------------| +| `ASSETS_DIRECTORY` | `string` | The directory where all the app assets are stored | +| `DATA_STORAGE` | `string` | The directory where all the app data files are stored | +| `CACHE_STORAGE` | `string` | The directory where all the cache files are stored | +| `PLUGIN_DIR` | `string` | The directory where all the plugins are stored | +| `KEYBINDING_FILE` | `string` | The file where all the keybindings are stored | -## DATA_STORAGE -`` The directory where all the data files are stored. +```javascript +console.log(ASSETS_DIRECTORY); // /android_asset/www +``` + +## Features -## PLUGIN_DIR -`` The directory where all the plugins are stored. +| Global | Type | Description | +|--------|------|-------------| +| `DOES_SUPPORT_THEME` | `boolean` | Whether the app supports themes | +| `ANDROID_SDK_INT` | `number` | The Android SDK version | -## DOES_SUPPORT_THEME -`` Whether the app supports theme or not. +::: warning +The `IS_FREE_VERSION` global documented by older guides **does not exist** in the current Acode source and referencing it throws a `ReferenceError`. Use `acode.require("config").HAS_PRO` instead (`true` = Pro, `false` = free). See [Config](./config.md). +::: -## IS_FREE_VERSION -`` Whether the app is free version or not. +## `window.app` and `window.root` -## KEYBINDING_FILE -`` The file where all the keybindings are stored. +- `window.app` - the `` element of the app (`document.body`). Use it to append UI. +- `window.root` - the `#root` element that Acode's UI is mounted into. + +```javascript +const page = document.createElement("div"); +window.app.appendChild(page); +``` -## ANDROID_SDK_INT -`` The Android SDK version. +## `window.appInstallSource` +Read-only string describing where the app was installed from (e.g. `"play"`, `"fdroid"`, `"web"`, …). Determined at startup from the OS installer. -Here is a very simple example on how to use these APIs: ```javascript -console.log(ASSETS_DIRECTORY) // returns a string like "/path/to/assets" -console.log(IS_FREE_VERSION) // logs true if user is using free version of the app, else false -``` \ No newline at end of file +if (window.appInstallSource === "play") { + // Play Store build +} +``` + +## `window.log(level, message)` + +Writes a log entry that is buffered and flushed to `DATA_STORAGE/Acode.log` (10 MB cap, rotated from the top). `level` is one of `"error"`, `"warn"`, `"info"`, or `"debug"`; the minimum recorded level is `"info"` by default. + +`message` may be a string or an `Error` (in which case the stack trace is included). + +```javascript +window.log("error", "Something went wrong"); +window.log("warn", "Low battery"); +window.log("info", "Plugin initialized"); +``` + +## `BuildInfo` + +The Cordova `BuildInfo` plugin object, clobbered to `window.BuildInfo`. Read-only app metadata populated at startup: + +| Property | Type | Description | +|----------|------|-------------| +| `packageName` | `string` | App package id (e.g. `com.foxdebug.acode` or `com.foxdebug.acodefree`) | +| `basePackageName` | `string` | Base package name | +| `displayName` / `name` | `string` | App display name | +| `version` | `string` | Version name (e.g. `1.11.2`) | +| `versionCode` | `number` | Version code (integer) | +| `debug` | `boolean` | Whether the build is a debug build | +| `buildType` | `string` | Gradle build type | +| `flavor` | `string` | Gradle flavor | +| `installDate` | `string` | Formatted install date | + +```javascript +const isNewer = BuildInfo.versionCode >= 1008; +const isFree = /free$/.test(BuildInfo.packageName); +``` + +::: info +The Pro flag is `acode.require("config").HAS_PRO`, not a `BuildInfo` field. On the free build the package name ends in `free`. +::: + +## `system` + +The native `system` plugin (`cordova-plugin-system`), clobbered to `window.system`. Provides low-level Android utilities used by Acode and plugins: file operations, storage management, runtime permissions, app/device info, intents, shortcuts, and text comparison. + +```javascript +system.fileAction(fileUri, filename, "VIEW", "text/plain"); +``` + +See [System](../advanced-apis/system.md) for the full API. + +## `strings` + +The app's localized string table, clobbered to `window.strings` from `src/lib/lang.js`. It is a `{ key: value }` dictionary of the current language's translations. + +```javascript +window.strings.error; // localized "Error" text +``` + +::: tip +Prefer the [helpers](../utilities/helpers.md) module (`acode.require("helpers")`) for the most common cross-cutting utilities. +::: diff --git a/docs/plugin-essentials/core-file.md b/docs/plugin-essentials/core-file.md index c251c41..9e5570c 100644 --- a/docs/plugin-essentials/core-file.md +++ b/docs/plugin-essentials/core-file.md @@ -24,7 +24,7 @@ To register your plugin, utilize the `acode.setPluginInit(pluginId: string, init 2. **init function:** - The function to be executed when the plugin is loaded. -Upon execution, the `init` function will receive three parameters: +Upon execution, the `init` function will receive three arguments: - **baseUrl (string):** - The base URL of the plugin, allowing access to files within the plugin directory. @@ -38,36 +38,64 @@ Upon execution, the `init` function will receive three parameters: - URL of the cached file. - **cacheFile (File):** - File object of the cached file, enabling file read/write operations. + - **firstInit (boolean):** + - `true` when the plugin is being installed/loaded for the first time. + - **ctx (PluginContext):** + - Your plugin's native context. Provides encrypted secret storage (`getSecret`, `setSecret`, `deleteSecret`, `clearAllSecrets`) and permission checks (`grantedPermission`, `listAllPermissions`). See [Plugin Context (`ctx`)](./plugin-context.md). ### Example main.js File -Here's an illustrative example of a `main.js` file: +The official templates structure the plugin as an `AcodePlugin` class. Here is an illustrative example of a `main.js` file: ```javascript -acode.setPluginInit('com.example.plugin', (baseUrl, $page, cache) => { - const commands = acode.require("commands"); - commands.addCommand({ - name: 'example-plugin', - bindKey: { win: 'Ctrl-Alt-E', mac: 'Command-Alt-E' }, - exec: () => { - $page.innerHTML = ` -

Example Plugin

-

This is an example plugin.

- `; - $page.show(); - }, - }); -}); +import plugin from "../plugin.json"; + +class AcodePlugin { + baseUrl = ""; + + async init($page, cacheFile, cacheFileUrl, firstInit, ctx) { + const commands = acode.require("commands"); + commands.addCommand({ + name: "example-plugin", + bindKey: { win: "Ctrl-Alt-E", mac: "Command-Alt-E" }, + exec: () => { + $page.innerHTML = ` +

Example Plugin

+

This is an example plugin.

+ `; + $page.show(); + }, + }); + } + + async destroy() { + const commands = acode.require("commands"); + commands.removeCommand("example-plugin"); + } +} + +if (window.acode) { + const acodePlugin = new AcodePlugin(); + + acode.setPluginInit(plugin.id, async (baseUrl, $page, { cacheFileUrl, cacheFile, firstInit, ctx }) => { + acodePlugin.baseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`; + await acodePlugin.init($page, cacheFile, cacheFileUrl, firstInit, ctx); + }); + + acode.setPluginUnmount(plugin.id, () => { + acodePlugin.destroy(); + }); +} ``` ## Plugin Unmount Function -The `main.js` file must also define an unmount function, which is called when the plugin is unloaded or uninstalled. This function allows you to perform cleanup operations associated with your plugin. +The `main.js` file must also define cleanup logic, which is called when the plugin is unloaded or uninstalled. This cleanup allows you to remove listeners, commands, intervals, and UI hooks associated with your plugin. In the class template this lives in the `destroy()` method, registered via `acode.setPluginUnmount`. ### Example Unmount Function ```javascript -acode.setPluginUnmount('com.example.plugin', () => { +acode.setPluginUnmount(plugin.id, () => { const commands = acode.require("commands"); commands.removeCommand('example-plugin'); }); @@ -80,5 +108,5 @@ For command registration APIs, see [Commands](../utilities/commands.md). ::: :::tip -You will not need to write this `unmount` or `initialize` functions for your plugin because templates comes with it , just you will need to write your plugin code inside the `AcodePlugin class` +You will not need to write these `init`/`destroy` registration functions for your plugin because the templates ship with them. You only need to write your plugin code inside the `AcodePlugin` class. ::: diff --git a/docs/plugin-essentials/manifest.md b/docs/plugin-essentials/manifest.md index 1061282..dddca12 100644 --- a/docs/plugin-essentials/manifest.md +++ b/docs/plugin-essentials/manifest.md @@ -69,6 +69,11 @@ The `plugin.json` file is a crucial component of every Acode plugin, serving as ## 15. **repository:** - Github/Gitlab url of your plugin source(only for free plugins) +## 16. **permissions:** + - An array of permission strings granted to your plugin's context. + - Permissions are bound to your plugin's [context token](./plugin-context.md) when it loads and can be checked at runtime with `ctx.grantedPermission(permission)` or `ctx.listAllPermissions()`. + - Only the permissions listed here are granted; there is no runtime permission prompt. + # Updating Plugins: If you wish to publish an update for your plugin, follow these guidelines: @@ -98,6 +103,7 @@ If you wish to publish an update for your plugin, follow these guidelines: "price": 0, "license": "MIT", "keywords": ["foo","bar"], + "permissions": ["read", "write"], "changelogs": "changelogs.md", "author": { "name": "Example Author", diff --git a/docs/plugin-essentials/plugin-context.md b/docs/plugin-essentials/plugin-context.md new file mode 100644 index 0000000..93aa489 --- /dev/null +++ b/docs/plugin-essentials/plugin-context.md @@ -0,0 +1,130 @@ +# Plugin Context (`ctx`) + +The plugin context (`ctx`) is the third argument of the options object passed to your plugin's `init` function. It is a native-backed handle for your plugin that provides **encrypted secret storage** and **permission checks**. + +Your `init` function receives it as `options.ctx`: + +```js +function init(baseUrl, $page, options) { + const ctx = options.ctx; +} +``` + +## Overview + +`ctx` is a `PluginContext` instance (`src/lib/pluginContext.js`). It is created by Acode for **your plugin id only** and is backed by a cryptographically signed token issued by the native `Tee` plugin. Because of this: + +- Secrets are scoped to your plugin id - another plugin cannot read them. +- The token is bound to the permissions declared in your `plugin.json` at install/load time. +- The object is `Object.freeze`d, so its properties cannot be replaced or extended. + +### `created_at`, `uuid`, `toString()` + +- `created_at` - timestamp (ms) when the context was created. +- `uuid` - the opaque token string for this context. +- `ctx.toString()` - returns the `uuid` string. The object coerces to the uuid for string operations (numeric coercion returns `NaN`). + +```js +String(ctx) === ctx.uuid; // true +``` + +## Secrets + +Secrets are key/value strings stored in an **EncryptedPreferenceManager** on the native side (scoped to your plugin id). They survive app restarts. Use them for API tokens, oauth state, or other sensitive data - never store secrets in `localStorage`. + +### `getSecret(key, defaultValue = ""): Promise` + +Resolves the stored value for `key`, or `defaultValue` when the key has not been set. + +```js +const token = await ctx.getSecret("github_token", ""); +if (!token) { + await ctx.setSecret("github_token", "ghp_..."); +} +``` + +### `setSecret(key, value): Promise` + +Stores `value` for `key`. + +```js +await ctx.setSecret("access_token", "abc123"); +``` + +### `deleteSecret(key): Promise` + +Removes a single key. + +```js +await ctx.deleteSecret("access_token"); +``` + +### `clearAllSecrets(): Promise` + +Removes every secret stored for your plugin. + +```js +await ctx.clearAllSecrets(); +``` + +## Permissions + +Permissions are declared in your `plugin.json` as an array: + +```json +{ + "id": "com.example.plugin", + "main": "dist/main.js", + "permissions": ["read", "write"] +} +``` + +The list is bound to your context's token when the plugin loads. The native side grants exactly the permissions listed; there is no runtime "request" dialog - a permission either is or is not present. + +### `grantedPermission(permission): Promise` + +Resolves `true` when your plugin was granted `permission`. + +```js +if (await ctx.grantedPermission("write")) { + // do something privileged +} +``` + +### `listAllPermissions(): Promise` + +Resolves the full list of permissions granted to your plugin. + +```js +const permissions = await ctx.listAllPermissions(); +``` + +## Full example + +```js +function init(baseUrl, $page, options) { + const ctx = options.ctx; + + (async () => { + console.log("permissions:", await ctx.listAllPermissions()); + + if (await ctx.grantedPermission("api-access")) { + const token = await ctx.getSecret("api_token"); + if (!token) { + await ctx.setSecret("api_token", prompt("Enter API token")); + } + } + })(); +} +``` + +## Notes + +- `ctx.invalidate()` exists but is used internally by Acode; plugins do not need to call it. +- If the trusted native session is not available (for example the token request fails), `ctx` may be `null` - guard against it if your plugin depends on it. +- Secrets are encrypted at rest and scoped per plugin id. + +## Related + +- [Manifest (`plugin.json`)](./manifest.md) - declaring `permissions` +- [Core File](./core-file.md) - where `ctx` is passed to `init` diff --git a/docs/utilities/code-highlight.md b/docs/utilities/code-highlight.md index 9c24edf..c97a686 100644 --- a/docs/utilities/code-highlight.md +++ b/docs/utilities/code-highlight.md @@ -1,119 +1,25 @@ -# Code Highlight +# Code Highlight -Acode exposes the same **static CodeMirror / Lezer highlighter** it uses for markdown previews, plugin pages, and LSP reference snippets. Plugins can highlight code without bundling a second highlighter, and the colors follow the user's editor theme. +::: warning +The static CodeMirror / Lezer highlighter described by earlier revisions of this page is currently **internal to Acode** (`src/utils/codeHighlight.js`). It is **not** exposed to plugins: neither `acode.require("codeHighlight")` nor `acode.require("codemirror").highlight` exists in the current plugin API, and the `EditorFile` `highlightStyles` option is **not implemented**. -::: info -Available from **versionCode `1008`** (the next Acode release). Set `"minVersionCode": 1008` in `plugin.json` when your plugin depends on it. +Calling these throws `undefined is not a function` / `TypeError`. Do not use them until a future Acode release exposes the module. ::: -## Import +## What is available instead -```js -const codeHighlight = acode.require("codeHighlight"); -``` - -The same object is also available as `acode.require("codemirror").highlight`. - -## Highlight HTML - -Both methods return **escaped HTML** with Lezer `tok-*` class names (`tok-keyword`, `tok-string`, …). Put the result inside an element with the `cm-highlighted` class (or `codeHighlight.HIGHLIGHT_CLASS`). - -### `highlightCodeBlock(code, language?)` - -Highlight a multi-line snippet. `language` is a mode name or markdown fence id (`"javascript"`, `"python"`, `"js"`, `"ts"`, …). Unknown languages fall back to escaped plain text. - -```js -const codeHighlight = acode.require("codeHighlight"); - -const html = await codeHighlight.highlightCodeBlock( - 'const answer = 42;\nconsole.log(answer);', - "javascript", -); - -const pre = document.createElement("pre"); -const code = document.createElement("code"); -code.className = codeHighlight.HIGHLIGHT_CLASS; -code.innerHTML = html; -pre.appendChild(code); -``` - -`highlight(code, language?)` is an alias of `highlightCodeBlock`. - -### `highlightLine(text, uri, symbolName?)` - -Highlight a single line. Language is inferred from `uri`. When `symbolName` is set, matching text is wrapped in ``. - -```js -const html = await codeHighlight.highlightLine( - "export function greet() {}", - "file:///sdcard/project/src/hello.js", - "greet", -); -``` - -## Shadow DOM and custom editor tabs - -Token colors live in a stylesheet, not in the returned HTML. Styles injected on `document` **do not pierce Shadow DOM**. - -Custom editor tabs do **not** get this stylesheet by default. Opt in when the tab will render highlighted HTML: +Acode uses CodeMirror 6. Plugins can highlight or render code using the shared CodeMirror packages exposed via `acode.require("codemirror")`: ```js -new EditorFile("snippet.js", { - type: "custom", - content: pre, - highlightStyles: true, -}); +const cm = acode.require("codemirror"); +// cm.language.HighlightStyle, cm.state, cm.view, cm.lezer, ... ``` -For any other shadow root (a dialog, a custom element, a tab that did not set `highlightStyles`), adopt the shared sheet yourself: - -```js -const host = document.createElement("div"); -const shadow = host.attachShadow({ mode: "open" }); - -codeHighlight.applyStyles(shadow); -// or: codeHighlight.applyStyles(host); // resolves to host.shadowRoot -// or: codeHighlight.applyStyles(file.content); // custom tab host -``` - -`applyStyles` prefers `adoptedStyleSheets`. Theme changes then update every adopted root in place — you do not need to call it again. - -```js -const css = codeHighlight.getStyles(); -const sheet = codeHighlight.getStyleSheet(); -``` - -Use `getStyles()` only if you need the raw CSS string. Prefer `applyStyles` so theme updates stay in sync. - -## Cache - -Results are cached per theme + language + source. Call `codeHighlight.clearCache()` after you register or unregister a language if stale HTML would be a problem. - -## Custom tab example - -```js -const EditorFile = acode.require("EditorFile"); -const codeHighlight = acode.require("codeHighlight"); - -async function openSnippetTab(source, language) { - const html = await codeHighlight.highlightCodeBlock(source, language); - const pre = document.createElement("pre"); - const code = document.createElement("code"); - code.className = codeHighlight.HIGHLIGHT_CLASS; - code.innerHTML = html; - pre.appendChild(code); - - new EditorFile(`${language} snippet`, { - type: "custom", - tabIcon: "file file_type_js", - content: pre, - highlightStyles: true, - hideQuickTools: true, - }); -} -``` +- Language registration: [Editor Languages](./ace-modes.md) +- Editor theme registration: [Editor Themes](./editor-themes.md) +- Shared CodeMirror packages: [CodeMirror packages](./codemirror.md) -`highlightStyles: true` adopts the highlight stylesheet into the tab's shadow root, so the snippet uses the current editor theme. +For rendering syntax-highlighted HTML inside your own UI, bundle your own highlighter (e.g. `shiki`, `highlight.js`, or `@lezer/highlight` via the `@lezer/*` modules exposed under `acode.require("codemirror").lezer`). ## Related APIs diff --git a/docs/utilities/codemirror.md b/docs/utilities/codemirror.md index 9c4a2bb..ec9ae79 100644 --- a/docs/utilities/codemirror.md +++ b/docs/utilities/codemirror.md @@ -50,11 +50,11 @@ Prefer these requires over bundling your own copy of CodeMirror. Duplicate packa ## Related APIs -- Active editor: `editorManager.editor` — see [EditorManager](../global-apis/editor-manager.md) -- Language registration: `acode.require("editorLanguages")` — see [Editor Languages](./ace-modes.md) -- Theme registration: `acode.require("editorThemes")` — see [Editor Themes](./editor-themes.md) -- Language servers: `acode.require("lsp")` — see [LSP](../advanced-apis/lsp.md) -- Static highlighter for snippets and plugin tabs: `acode.require("codeHighlight")` — see [Code Highlight](./code-highlight.md) +- Active editor: `editorManager.editor` - see [EditorManager](../global-apis/editor-manager.md) +- Language registration: `acode.require("editorLanguages")` - see [Editor Languages](./ace-modes.md) +- Theme registration: `acode.require("editorThemes")` - see [Editor Themes](./editor-themes.md) +- Language servers: `acode.require("lsp")` - see [LSP](../advanced-apis/lsp.md) +- Static highlighting status: see [Code Highlight](./code-highlight.md) ## Minimal extension example diff --git a/docs/utilities/helpers.md b/docs/utilities/helpers.md new file mode 100644 index 0000000..f9738d0 --- /dev/null +++ b/docs/utilities/helpers.md @@ -0,0 +1,202 @@ +# Helpers + +The `helpers` module is a collection of small utility functions used across Acode and exposed to plugins. + +Require it with `acode.require('helpers')`. + +```js +const helpers = acode.require("helpers"); +``` + +## Strings & parsing + +### `parseJSON(string): any | null` + +Parses a JSON string. Returns `null` when the input is empty or cannot be parsed (never throws). + +```js +helpers.parseJSON('{"a": 1}'); // { a: 1 } +helpers.parseJSON("not json"); // null +``` + +### `fixFilename(name: string): string` + +Removes line breaks (`\r\n`, `\r`, `\n`) and tabs from a name and trims it. + +```js +helpers.fixFilename("my\nfile.txt"); // "myfile.txt" +``` + +### `uuid(): string` + +Returns a unique id string (timestamp + random, base-36). + +### `formatDownloadCount(count: number): string` + +Formats a download count into a short human-readable string using `K`/`M`/`B`/`T` units. + +```js +helpers.formatDownloadCount(15400); // "15.4K" +helpers.formatDownloadCount(2_500_000); // "2.5M" +``` + +## Errors + +### `errorMessage(err, ...args): string` + +Builds a human-readable error message from an `Error`, a string, or a fallback. Extra `args` are appended with `
` separators, and URL-like arguments are rewritten to their virtual path via [`getVirtualPath`](#getvirtualpathpath-string-string). + +### `error(err, ...args): Promise` + +Shows an alert dialog with the error message. Returns a promise that resolves when the dialog is closed. If the error has `code === 0` a toast is shown instead. + +```js +try { + await something(); +} catch (err) { + await helpers.error(err); +} +``` + +## Types + +### `isDir(type: string): boolean` + +Returns `true` for `'dir'`, `'directory'`, or `'folder'`. + +### `isFile(type: string): boolean` + +Returns `true` for `'file'` or `'link'`. + +### `isBinary(file: string): boolean` + +Returns `true` if the file name/uri looks like a binary file. + +## URLs & paths + +### `getVirtualPath(path: string): string` + +Replaces the matching part of a url with the alias name of the storage it belongs to (from `localStorage.storageList`). Content uris are resolved to their primary (virtual) address first, if available. + +```js +helpers.getVirtualPath("content://com.android.externalstorage.documents/..."); +``` + +### `toInternalUri(uri: string): Promise` + +Resolves a `file://` (or other) uri to an internal `cdvfile://` url using `resolveLocalFileSystemURL`. + +```js +const internalUrl = await helpers.toInternalUri(file.uri); +``` + +### `updateUriOfAllActiveFiles(oldUrl, newUrl)` + +Updates the `uri` of every open file whose uri starts with `oldUrl`, replacing it with `newUrl` (keeping the filename). Pass `null` as `newUrl` to clear uris. Afterwards calls `editorManager.onupdate("file-delete")` and emits the `update` event with `"file-delete"` as its sub-action. + +### `createFileStructure(uri, pathString, isFile = true): Promise<{ uri, parentUri, created, type }>` + +Creates nested folders (and optionally a final file) under `uri`, walking `pathString` split on `/`. Handles special-case SAF/ExternalStorage/Termux/Acode-terminal document uris. + +Returns an object describing the first created entry: +- `uri` - url of the first created entry (or the target uri if nothing was created) +- `parentUri` - parent uri of the first created entry +- `created` - `true` when at least one entry was created +- `type` - `'file'` or `'folder'` + +Throws if an existing entry's type does not match the expected type. + +```js +const res = await helpers.createFileStructure( + "file:///storage/emulated/0/Acode", + "project/src/index.js", + true, +); +``` + +## Files & sorting + +### `getIconForFile(filename: string): string` + +Returns the icon class string for a filename (e.g. `"file file_type_default file_type_js"`). + +### `sortDir(list, fileBrowser, mode = "both"): Array` + +Sorts a list of file entries into directories-first order. `mode` can be `'both'`, `'file'`, or `'folder'`. Honors `sortByName` and `showHiddenFiles` settings from `fileBrowser`. Sets `item.icon` and `item.disabled` (when mode is `'folder'`) as a side effect. + +## Promises & timing + +### `promisify(func, ...args): Promise` + +Wraps a callback-style function that calls `(resolve, reject)` as its trailing arguments. + +```js +const value = await helpers.promisify(system.getFilesDir); +``` + +### `checkAPIStatus(): Promise` + +Fetches `API_BASE/status`. Resolves `true` when the Acode API is reachable, `false` on any error. + +### `debounce(func, wait): Function` + +Returns a debounced version of `func` that only runs after `wait` ms without further calls. + +```js +window.addEventListener("resize", helpers.debounce(onResize, 200)); +``` + +## DOM & HTML + +### `parseHTML(html): HTMLElement | HTMLElement[]` + +Parses an HTML string with `DOMParser`. Returns the single element when there is exactly one child, otherwise an array of children. + +```js +const el = helpers.parseHTML("
Hello
"); +``` + +## Deprecation helpers + +### `defineDeprecatedProperty(obj, name, getter, setter)` + +Defines a property on `obj` that warns to the console whenever it is read or written. + +### `decodeText(arrayBuffer, encoding = "utf-8"): string` + +::: warning Deprecated +Use the `encodings` module instead. +::: + +Decodes an `ArrayBuffer` to a string. When `encoding` is `"json"`, the result is parsed as JSON. + +## Ads (free builds only) + +These are used by Acode internally to manage ads on the free build. They are no-ops / return `false` on Pro. + +| Method | Description | +|--------|-------------| +| `canShowAds()` | `true` when the build is not Pro and ads are available | +| `showInterstitialIfReady()` | Shows an interstitial ad if loaded; resolves `true` when shown | +| `showAd()` | Displays a banner ad on the current page (if eligible) | + +## Purchasing + +| Method | Description | +|--------|-------------| +| `isIapAvailable()` | `true` when the In-App Purchase plugin is present and available | +| `shouldAllowExternalPurchase()` | `true` when IAP is unavailable and the app was not installed from the Play Store | + +## Mtime helpers + +Used by the editor's disk-conflict tracking. + +| Method | Description | +|--------|-------------| +| `normalizeMtime(value): number \| null` | Converts a `Date` or timestamp to a numeric ms value (or `null`) | +| `getStatMtime(stat): number \| null` | Extracts an mtime from a `stat` object (`modifiedDate`, `lastModified`, or `mtime`) | + +## Related APIs + +- [Config](../global-apis/config.md) - `HAS_PRO` and other app constants used by the helpers +- [File System (fs)](./fs.md) - low-level file operations diff --git a/docs/utilities/open-folder.md b/docs/utilities/open-folder.md index 728dc2f..dd9922c 100644 --- a/docs/utilities/open-folder.md +++ b/docs/utilities/open-folder.md @@ -97,7 +97,6 @@ const folder = openFolder.find('/path/to/fileOrFolder'); The `openFolder` utility emits various events to help manage folder operations: - `add-folder` - `remove-folder` -- `update-folder` These events can be listened to for performing custom actions upon folder operations. diff --git a/user-guide/command-palette.md b/user-guide/command-palette.md index a711c10..65a864c 100644 --- a/user-guide/command-palette.md +++ b/user-guide/command-palette.md @@ -13,7 +13,7 @@ You can open the Command Palette using the standard shortcut: ### Mobile Devices (QuickTools) -On mobile devices where a physical keyboard might not be present, Acode provides **QuickTools**—a toolbar above the keyboard that contains essential keys like `Ctrl`. +On mobile devices where a physical keyboard might not be present, Acode provides **QuickTools** - a toolbar above the keyboard that contains essential keys like `Ctrl`. ![QuickTools](/quicktools.png) From 6a82ccb57062a1e133ebb933b7686f359cf9ce5c Mon Sep 17 00:00:00 2001 From: Rohit Kushwaha Date: Tue, 25 Aug 2026 15:27:01 +0530 Subject: [PATCH 2/6] feat: removed slop --- docs/advanced-apis/lsp.md | 4 +- docs/editor-components/editor-file.md | 198 ++++--------------------- docs/editor-components/file-index.md | 16 +- docs/editor-components/file-list.md | 4 +- docs/global-apis/ace.md | 2 +- docs/global-apis/acode.md | 48 +----- docs/global-apis/editor-manager.md | 60 +------- docs/global-apis/global-utilities.md | 113 +++----------- docs/utilities/code-highlight.md | 118 +++++++++++++-- docs/utilities/codemirror.md | 10 +- docs/utilities/helpers.md | 202 -------------------------- docs/utilities/open-folder.md | 1 + 12 files changed, 175 insertions(+), 601 deletions(-) delete mode 100644 docs/utilities/helpers.md diff --git a/docs/advanced-apis/lsp.md b/docs/advanced-apis/lsp.md index 7ea7158..9f720aa 100644 --- a/docs/advanced-apis/lsp.md +++ b/docs/advanced-apis/lsp.md @@ -449,7 +449,7 @@ Returns a `TransportHandle`: { kind: "ready" } ``` -**Error** (worker → main) if initialization fails - rejects `ready` immediately and tears down the worker: +**Error** (worker → main) if initialization fails — rejects `ready` immediately and tears down the worker: ```js { kind: "error", message: "Failed to initialize worker" } @@ -586,7 +586,7 @@ lsp.servers.unregister(SERVER_ID); lsp.runtimes.unregister(RUNTIME_ID); ``` -Use `transport: { kind: "external" }` for worker servers - the runtime returns the real transport handle. Register your own server id; do not replace built-in ids like `html`, `css`, `json`, or `typescript`. +Use `transport: { kind: "external" }` for worker servers — the runtime returns the real transport handle. Register your own server id; do not replace built-in ids like `html`, `css`, `json`, or `typescript`. ### Runtime URI Resolution diff --git a/docs/editor-components/editor-file.md b/docs/editor-components/editor-file.md index 8b47eae..d69eb9c 100644 --- a/docs/editor-components/editor-file.md +++ b/docs/editor-components/editor-file.md @@ -51,26 +51,14 @@ Both methods are equivalent and accept & return the same parameters. | tabIcon | `string` | Icon class for the file tab | `'file file_type_default'` | | content | string \| [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement) | Custom content element or HTML string. Strings are sanitized using DOMPurify | - | | stylesheets | `string\|string[]` | Custom stylesheets for tab. Can be URL, or CSS string | - | +| highlightStyles | `boolean` | Adopt the static CodeMirror highlight stylesheet into this custom tab's shadow root. Use only when the tab will render `codeHighlight` HTML. Available from **versionCode `1008`** | `false` | | hideQuickTools | `boolean` | Whether to hide quicktools for this tab | `false` | | pinned | `boolean` | Pin the tab to prevent accidental closing | `false` | -| readOnly | `boolean` | Open the file as read-only (sets `editable` to `false`) | `false` | +| readOnly | `boolean` | Open the file as read-only | `false` | | paneId | `string` | Target editor pane id (multi-pane layout) | - | | pane | `object` | Target editor pane instance (multi-pane layout) | - | | isPanePlaceholder | `boolean` | Temporary empty tab for an empty pane | `false` | -#### Version-metadata options - -These options seed the dirty-tracking / disk-conflict system (see [Dirty tracking](#dirty-tracking--disk-conflict)). - -| Property | Type | Description | -|----------|------|-------------| -| docVersion | `number` | Current document version for dirty tracking | -| savedVersion | `number` | Document version last saved or loaded from disk | -| cacheVersion | `number` | Document version last written to crash cache | -| savedMtime | `number` | File mtime last saved or loaded from disk | -| diskMtime | `number` | Latest known file mtime on disk | -| hasDiskConflict | `boolean` | Whether the editor and disk both changed | - ## Properties ### Read-only Properties @@ -96,12 +84,8 @@ These options seed the dirty-tracking / disk-conflict system (see [Dirty trackin | loaded | `boolean` | Whether file has completed loading text | | loading | `boolean` | Whether file is still loading text | | session | `Proxy` | Session state with Ace-compatible helper methods | -| readOnly | `boolean` | Whether file is readonly. This is a plain field - assign the read-only state on the editor with [`setReadOnly()`](#setreadonlyvalue) | +| readOnly | `boolean` | Whether file is readonly | | markChanged | `boolean` | Whether to mark changes when session text changes | -| currentMode | `string` | Currently active syntax mode name (set by [`setMode()`](#setmodemode)) | -| currentLanguageExtension | `unknown` | CodeMirror language extension for the current mode (may be `null`) | -| headerSubtitle | `string` | Subtitle shown in the editor header for this file | -| hasVersionMetadata | `boolean` | Whether the file has version metadata for dirty tracking | ### Writable(setters) Properties @@ -114,10 +98,7 @@ These options seed the dirty-tracking / disk-conflict system (see [Dirty trackin | eol | `'windows' \| 'unix'` | Set end of line character | | editable | `boolean` | Set file editability | | pinned | `boolean` | Set file pinned state | - -::: warning -`readOnly` is **not** a setter. Assigning `file.readOnly = true` only updates the field and does not reconfigure the CodeMirror editor. Use `file.setReadOnly(true)` to actually make the editor read-only. -::: +| readOnly | `boolean` | Set file readonly state | ## Methods @@ -183,54 +164,20 @@ Toggles the pinned State of the file file.togglePinned(); ``` -#### [render()](#render) -Makes the file active and renders its content. Also removes the default untitled tab and any empty pane-placeholder tabs in the same pane. - -```js -file.render(); -``` - -#### [runAction()](#runaction) -Runs the file through the system file action (equivalent to opening it with the run action of the OS). - -```js -file.runAction(); -``` - -#### [setCustomTitle(titleFn)](#setcustomtitletitlefn) -Sets a custom title function used for the header subtitle of this file. Called with no arguments, it must return the title string. - -```js -file.setCustomTitle(() => `PID: ${file.pid}`); -``` - ### Editor Operations -#### [setMode(mode?, options?)](#setmodemode) +#### [setMode(mode)](#setmodemode) Sets syntax highlighting mode for the file. -- `mode` (string, optional): Mode name. When omitted (or empty), the mode is resolved from the user's mode associations (`localStorage.modeassoc`) and the filename. -- `options.recommend` (boolean, default `true`): When `false`, skips the "recommend an extension for this language" prompt. - -Updates `currentMode` and `currentLanguageExtension`. - ```js file.setMode('javascript'); -file.setMode('javascript', { recommend: false }); -``` - -#### [setReadOnly(value)](#setreadonlyvalue) -Sets the read-only state and reconfigures the CodeMirror editor accordingly. This is the correct way to toggle read-only - assigning the `readOnly` field directly does not touch the editor. - -```js -file.setReadOnly(true); ``` -#### [readCanRun()](#readcanrun) -Async; resolves whether the run button should be shown for this file (checks open-folder `index.html`, runnable extensions, and any `canrun` handler). You normally only need [`canRun()`](#canrun). +#### [writeToCache()](#writetocache) +Writes file content to cache. ```js -await file.readCanRun(); +await file.writeToCache(); ``` #### [isChanged()](#ischanged) @@ -314,77 +261,8 @@ Removes event listener. file.off('save', callback); ``` -## Dirty tracking & disk conflict - -Acode tracks whether the in-memory document differs from what is on disk, so it can warn about unsaved changes and detect conflicts where both the editor and the file changed. - -State fields: - -| Field | Type | Description | -|-------|------|-------------| -| docVersion | `number` | Current document version (incremented on every edit) | -| savedVersion | `number` | Document version last saved or loaded from disk | -| cacheVersion | `number` | Document version last written to the crash cache | -| savedMtime | `number \| null` | File mtime last saved or loaded from disk (ms) | -| diskMtime | `number \| null` | Latest known file mtime on disk (ms) | -| hasDiskConflict | `boolean` | `true` when both the editor and the disk changed | -| hasVersionMetadata | `boolean` | Whether any version metadata has been recorded | - -Normally you read `file.isUnsaved` / `file.hasUnsavedChanges()`. Use the `mark*` methods to keep the state accurate when you load, edit, save, or detect external changes. - -#### [hasUnsavedChanges(): boolean](#hasunsavedchanges) -Checks whether the file has unsaved changes, comparing the current document with the last saved/loaded document. - -```js -if (file.hasUnsavedChanges()) { - // prompt to save -} -``` - -#### [refreshUnsavedState(): boolean](#refreshunsavedstate) -Recomputes `isUnsaved` from the current state and returns it. - -#### [markLoaded({ mtime, isUnsaved, savedDoc }?)](#markloaded) -Marks the file as loaded. Resets `docVersion` (to `1` when `isUnsaved`, else `0`), sets `savedVersion`, `cacheVersion`, `savedMtime`, and `diskMtime`, and clears `hasDiskConflict`. - -#### [markEdited({ exact }?)](#markedited) -Increments `docVersion` and marks the file as unsaved. When `exact` is `true`, `isUnsaved` is recomputed instead of just set to `true`. Gives the file a new id if it was still the default untitled session. - -#### [markSaved({ mtime, savedDoc, savedVersion }?)](#marksaved) -Marks the file as saved. Updates `savedVersion`, `savedMtime`, `diskMtime`, clears `hasDiskConflict`, and refreshes `isUnsaved`. - -#### [markDiskChanged({ mtime, deleted }?)](#markdiskchanged) -Records an external change. When `deleted` is `true` the file is marked deleted and unsaved. Otherwise sets `hasDiskConflict` when both `docVersion !== savedVersion` and `diskMtime !== savedMtime`, then refreshes `isUnsaved`. - -## Cache pipeline - -Acode keeps a crash-recovery cache of every open editor file. Changes are written to `cacheFile` (debounced by default) so an app crash does not lose unsaved work. - -#### [scheduleCacheWrite(delay = 1500)](#schedulecachewritedelay--1500) -Schedules a cache write after `delay` ms (or writes immediately when `delay <= 0`). No-op when the cache is already up to date with `docVersion`. - -```js -file.scheduleCacheWrite(500); -``` - -#### [flushCacheWrite()](#flushcachewrite) -Flushes any pending scheduled cache write immediately and waits for in-flight writes. - -```js -await file.flushCacheWrite(); -``` - -#### [writeToCache()](#writetocache) -Writes file content to cache immediately (see also `isChanged()` below). - -```js -await file.writeToCache(); -``` - ## Events -### `on(event, callback)` events - The EditorFile class emits the following events: | Event | Description | @@ -403,48 +281,6 @@ The EditorFile class emits the following events: | run | File is run | | canRun | File runnable state changes | -### `on*` callback properties - -Each event also has a direct callback property. Setting it is equivalent to registering a listener for that event: - -| Property | Event | -|----------|-------| -| `onsave` | `save` | -| `onchange` | `change` | -| `onfocus` | `focus` | -| `onblur` | `blur` | -| `onclose` | `close` | -| `onrename` | `rename` | -| `onload` | `load` | -| `onloaderror` | `loadError` | -| `onloadstart` | `loadStart` | -| `onloadend` | `loadEnd` | -| `onchangemode` | `changeMode` | -| `onrun` | `run` | -| `oncanrun` | `canRun` | -| `onpinstatechange` | Called with the new pinned value whenever the pinned state changes | - -```js -file.onpinstatechange = (pinned) => { - console.log("pinned:", pinned); -}; -``` - -### Events emitted on `editorManager` - -These events fire on `editorManager` (see [EditorManager](../global-apis/editor-manager.md#events)) for file lifecycle: - -| Event | Payload | Description | -|-------|---------|-------------| -| `new-file` | `file` | A new file/tab was created | -| `file-loaded` | `file` | The file finished loading its text | -| `file-loading-preview` | `file`, `text` | A remote-file preview became available while loading | -| `file-content-changed` | `file` | Content changed by a plugin (inactive-file edit) | -| `rename-file` | `file` | File renamed or moved | -| `remove-file` | `file` | File removed/closed | - -`update` sub-actions are also emitted, for example `"file-changed"`, `"read-only"`, `"pin-tab"`, `"remove-file"`, `"switch-file"`. - ## Examples ### Creating a New File @@ -498,6 +334,24 @@ file1.addStyle('/styles/additional.css'); Custom Editor Tabs are isolated from main DOM using Shadow DOM, so don't select tab elements using `document`. ::: +Syntax highlighting inside a custom tab is opt-in and available from **versionCode `1008`**. Set `highlightStyles: true` so the tab's shadow root gets the theme stylesheet, then add the `cm-highlighted` class to the wrapper. See [Code Highlight](../utilities/code-highlight.md). + +```js +const codeHighlight = acode.require("codeHighlight"); +const html = await codeHighlight.highlightCodeBlock(source, "javascript"); + +const code = document.createElement("code"); +code.className = codeHighlight.HIGHLIGHT_CLASS; +code.innerHTML = html; + +new EditorFile("snippet.js", { + type: "custom", + content: code, + highlightStyles: true, + hideQuickTools: true, +}); +``` + ### Saving File Changes ```js diff --git a/docs/editor-components/file-index.md b/docs/editor-components/file-index.md index 0dea643..8347de0 100644 --- a/docs/editor-components/file-index.md +++ b/docs/editor-components/file-index.md @@ -11,7 +11,7 @@ Available from **versionCode `1002`**. Set `"minVersionCode": 1002` in `plugin.j | | `fileList` (legacy) | `fileIndex` (new) | | --- | --- | --- | | SAF / `file://` | No longer fully listed | Native SQLite index | -| FTP / SFTP / custom | Still works | Not supported - use `fileList` | +| FTP / SFTP / custom | Still works | Not supported — use `fileList` | | API style | Sync tree objects | Async flat records | | Large workspaces | Heavy WebView tree | Paginated native queries | | Search | App-side workers | Optional native streaming search | @@ -29,7 +29,7 @@ Feature detection: ```js const fileIndex = acode.require("fileIndex"); if (!fileIndex?.query) { - // Running on an older Acode build - use fileList fallback + // Running on an older Acode build — use fileList fallback } ``` @@ -307,11 +307,11 @@ const { entries } = await fileIndex.query({ Key differences: -1. **`fileIndex` is asynchronous** - always `await` queries and scans. -2. **Results are flat records** - no `children` / `parent` tree navigation. -3. **Pagination** - use `cursor` / `hasMore` for large result sets. -4. **SAF + `file://` only** - keep using `fileList` for FTP/SFTP if needed. -5. **Search events may be batched** - handle `search-results` as well as `search-result`. +1. **`fileIndex` is asynchronous** — always `await` queries and scans. +2. **Results are flat records** — no `children` / `parent` tree navigation. +3. **Pagination** — use `cursor` / `hasMore` for large result sets. +4. **SAF + `file://` only** — keep using `fileList` for FTP/SFTP if needed. +5. **Search events may be batched** — handle `search-results` as well as `search-result`. Hybrid pattern (native roots + remote fallback): @@ -347,7 +347,7 @@ Scan and search jobs emit events with a shared shape. Common `type` values: | Type | When | | --- | --- | | `status` | Progress message during scan/search | -| `progress` | Numeric progress (`data` is 0-100) | +| `progress` | Numeric progress (`data` is 0–100) | | `batch` | Optional entry batches during scan | | `search-result` | One file's matches (`batchResults: false`) | | `search-results` | Array of file match payloads (`batchResults: true`) | diff --git a/docs/editor-components/file-list.md b/docs/editor-components/file-list.md index 3668fa9..170fa60 100644 --- a/docs/editor-components/file-list.md +++ b/docs/editor-components/file-list.md @@ -1,10 +1,10 @@ # File List API -::: warning Deprecated - migrate to File Index +::: warning Deprecated — migrate to File Index `acode.require("fileList")` is **deprecated** from **versionCode `1002`**. - SAF (`content:`) and `file://` workspaces are no longer fully listed here. -- Those roots live in the native index - use [`fileIndex`](./file-index.md) . +- Those roots live in the native index — use [`fileIndex`](./file-index.md) . - `fileList` still contains **non-native** providers only (FTP, SFTP, custom storage). ```js diff --git a/docs/global-apis/ace.md b/docs/global-apis/ace.md index 8d4a180..3c6931f 100644 --- a/docs/global-apis/ace.md +++ b/docs/global-apis/ace.md @@ -10,7 +10,7 @@ This page exists for plugin migration from Ace-era APIs. - Use `acode.require("commands")` for command registration/removal. - Use `acode.require("editorLanguages")` to register or remove language modes. - Use `acode.require("editorThemes")` to register or apply editor themes. -- Use `acode.require("codemirror")` (or `@codemirror/*` / `@lezer/*`) for the same CodeMirror packages the app uses - see [CodeMirror packages](../utilities/codemirror.md). +- Use `acode.require("codemirror")` (or `@codemirror/*` / `@lezer/*`) for the same CodeMirror packages the app uses — see [CodeMirror packages](../utilities/codemirror.md). - Use `editorManager.isCodeMirror` to check whether current acode uses codemirror or ace, if its true then its codemirror and if it is null or undefined then its ace. ## Legacy Compatibility diff --git a/docs/global-apis/acode.md b/docs/global-apis/acode.md index 4045cbe..b1b6a9d 100644 --- a/docs/global-apis/acode.md +++ b/docs/global-apis/acode.md @@ -13,7 +13,7 @@ This method is used to register the plugin. This method takes two parameters, `p **Example:** ```js -acode.setPluginInit(plugin.id, (baseUrl, $page, { cacheFileUrl, cacheFile, firstInit, ctx }) => { // [!code focus] +acode.setPluginInit('com.example.plugin', (baseUrl, $page, cache) => { // [!code focus] const commands = acode.require("commands"); commands.addCommand({ name: 'example-plugin', @@ -29,10 +29,6 @@ acode.setPluginInit(plugin.id, (baseUrl, $page, { cacheFileUrl, cacheFile, first }); ``` -::: tip -The official templates wrap this in an `AcodePlugin` class with `init()` and `destroy()` methods. See [Understanding How Plugins Work](../getting-started/understanding-plugin.md) for the recommended `main.js` shape. -::: - ### `init(baseUrl: string, $page: WCPage, options: object)` When the init function is called, it will receive 3 parameters: @@ -47,7 +43,6 @@ When the init function is called, it will receive 3 parameters: * `cacheFile File: object` File object of the cached file. Using this object, you can write/read the file. * `firstInit: boolean` If this is the first time the plugin is loaded, this value will be true. Otherwise, it will be `false`. - * `ctx: PluginContext` Your plugin's native context: encrypted secret storage and permission checks. See [Plugin Context (`ctx`)](../plugin-essentials/plugin-context.md). ### `Settings Object` @@ -311,50 +306,11 @@ Clears a plugin's broken mark so it can be retried on next load. acode.clearBrokenPluginMark("com.example.plugin"); ``` -### `joinUrl(...parts: string[]): string` - -Joins URL parts into a single url (delegates to the [`Url`](../utilities/url.md) module's `join`). - -```js -const url = acode.joinUrl("file:///sdcard", "Acode", "file.txt"); -``` - -### `setLoadingMessage(message: string): void` - -Sets a small loading message on the app body (`data-small-msg` attribute). Pass an empty string to clear it. - -```js -acode.setLoadingMessage("Loading plugins..."); -``` - -### `exitAppMessage: string | null` - -Read-only. Returns a localized warning string when there are unsaved files (used by Acode when the user tries to exit), otherwise `null`. - -```js -const msg = acode.exitAppMessage; // "You have unsaved files..." or null -``` - -### `unmountPlugin(pluginId: string): void` - -Runs the unmount callback registered via [`setPluginUnmount`](#setpluginunmountpluginid-string-unmount-function), deletes the plugin's cache file, and removes the plugin's settings page. Called by Acode on disable/reload/uninstall - plugins normally do not need to call it. - -### `fsOperation(file: string): FsOperation` - -Returns a file-system operation object for the given path/uri (see [File System](../utilities/fs.md)). - -```js -const fs = acode.fsOperation("file:///sdcard/Acode/plugin.json"); -const exists = await fs.exists(); -``` - ## Related APIs - Commands API (preferred for adding/removing commands): [Commands](../utilities/commands.md) - CodeMirror editor theme API: [Editor Themes](../utilities/editor-themes.md) -- Static code highlighting status: [Code Highlight](../utilities/code-highlight.md) +- Static CodeMirror highlighter (versionCode `1008+`): [Code Highlight](../utilities/code-highlight.md) - Language server API: [LSP](../advanced-apis/lsp.md) - File handler API: [File Handlers](../advanced-apis/file-handlers.md) - Terminal API: [Terminal](../advanced-apis/terminal.md) -- Background process API: [Executor](../advanced-apis/executor.md) -- Native Android bridge: [System](../advanced-apis/system.md) diff --git a/docs/global-apis/editor-manager.md b/docs/global-apis/editor-manager.md index 6befea4..d24b044 100644 --- a/docs/global-apis/editor-manager.md +++ b/docs/global-apis/editor-manager.md @@ -69,43 +69,6 @@ Open-file tab list (pane-aware when multi-pane layout is active). Whether the active editor is currently scrolling. -### `TIMEOUT_VALUE: number` - -Internal debounce/operation timeout used by the editor manager (default `500` ms). Used internally when waiting for editor operations; plugins generally only need it for tuning their own timeouts to match. - -### `readOnlyCompartment` - -The CodeMirror [`Compartment`](https://codemirror.net/docs/ref/#state.Compartment) used to toggle the read-only state of editor views. Useful for advanced plugins that build their own CodeMirror configuration. - -### `getLspMetadata(file): object | null` - -Builds the LSP metadata object for a file: - -- `uri` - file uri (or an `untitled://acode/` uri for untitled files) -- `languageId` - resolved language id -- `languageName` - `file.currentMode` or the language id -- `view` - the target editor view -- `file` - the file -- `rootUri` - the matching added-folder url, or the file uri - -Returns `null` for non-editor files. - -### `getEditorHeight(editor): number` - -Returns the scrollable height of an editor view (`max(scrollHeight - clientHeight, 0)`). - -### `getEditorWidth(editor): number` - -Returns the scrollable width of an editor view. - -### `reapplyActiveFile()` - -Force-recreates the active file's editor state in the current pane (used after configuration changes). - -### `syncOpenFileList()` - -Synchronizes the visible open-file tab list with the current pane layout and file order. - ## Opening files There is no `editorManager.addNewFile` API. Create tabs with: @@ -223,21 +186,6 @@ editorManager.openNextEditorFromHistory(); editorManager.recordHistory(file); // usually automatic on switch ``` -History state is also exposed for inspection: - -- `editorManager.editorHistory` - array of `EditorFile` entries in the navigation history (max 100, oldest first). -- `editorManager.editorHistoryIndex` - index of the current position within `editorHistory`. - -```javascript -const backStack = editorManager.editorHistory.slice( - 0, - editorManager.editorHistoryIndex + 1, -); -const forwardStack = editorManager.editorHistory.slice( - editorManager.editorHistoryIndex + 1, -); -``` - ## LSP / cache helpers ```javascript @@ -258,14 +206,13 @@ await editorManager.flushCacheWrites(); | `rename-file` | File renamed | | `save-file` | File saved | | `file-loaded` | File finished loading | -| `file-loading-preview` | Remote-file preview text became available (payload: file, text) | | `file-content-changed` | File content changed | | `add-folder` | Workspace folder added | | `remove-folder` | Workspace folder removed | +| `update-folder` | Workspace folder updated | | `new-file` | New file created | -| `int-open-file-list` | Open file list initialized | +| `init-open-file-list` | Open file list initialized | | `remove-file` | File removed | -| `editor-state-changed` | The active editor's document changed (payload: the CodeMirror view) | | `update` | Generic update (often with a sub-action) | `update` listeners may receive a sub-action as the first argument, for example: @@ -273,9 +220,6 @@ await editorManager.flushCacheWrites(); - `"pin-tab"` - `"switch-file"` - `"read-only"` -- `"file-changed"` - -For each `update` emission, a detailed `update:` event is also emitted (e.g. `update:pin-tab`) whose payload is everything after the sub-action. ```javascript editorManager.on("switch-file", () => { diff --git a/docs/global-apis/global-utilities.md b/docs/global-apis/global-utilities.md index 2c7949a..9004fe3 100644 --- a/docs/global-apis/global-utilities.md +++ b/docs/global-apis/global-utilities.md @@ -2,106 +2,33 @@ These extra global APIs provide essential information about asset directories, storage locations, app features, and system specifications. -## Storage & asset directories +## ASSETS_DIRECTORY +`` The directory where all the assets are stored. -| Global | Type | Description | -|--------|------|-------------| -| `ASSETS_DIRECTORY` | `string` | The directory where all the app assets are stored | -| `DATA_STORAGE` | `string` | The directory where all the app data files are stored | -| `CACHE_STORAGE` | `string` | The directory where all the cache files are stored | -| `PLUGIN_DIR` | `string` | The directory where all the plugins are stored | -| `KEYBINDING_FILE` | `string` | The file where all the keybindings are stored | +## CACHE_STORAGE +`` The directory where all the cache files are stored. -```javascript -console.log(ASSETS_DIRECTORY); // /android_asset/www -``` - -## Features +## DATA_STORAGE +`` The directory where all the data files are stored. -| Global | Type | Description | -|--------|------|-------------| -| `DOES_SUPPORT_THEME` | `boolean` | Whether the app supports themes | -| `ANDROID_SDK_INT` | `number` | The Android SDK version | +## PLUGIN_DIR +`` The directory where all the plugins are stored. -::: warning -The `IS_FREE_VERSION` global documented by older guides **does not exist** in the current Acode source and referencing it throws a `ReferenceError`. Use `acode.require("config").HAS_PRO` instead (`true` = Pro, `false` = free). See [Config](./config.md). -::: +## DOES_SUPPORT_THEME +`` Whether the app supports theme or not. -## `window.app` and `window.root` +## IS_FREE_VERSION +`` Whether the app is free version or not. -- `window.app` - the `` element of the app (`document.body`). Use it to append UI. -- `window.root` - the `#root` element that Acode's UI is mounted into. - -```javascript -const page = document.createElement("div"); -window.app.appendChild(page); -``` +## KEYBINDING_FILE +`` The file where all the keybindings are stored. -## `window.appInstallSource` +## ANDROID_SDK_INT +`` The Android SDK version. -Read-only string describing where the app was installed from (e.g. `"play"`, `"fdroid"`, `"web"`, …). Determined at startup from the OS installer. +Here is a very simple example on how to use these APIs: ```javascript -if (window.appInstallSource === "play") { - // Play Store build -} -``` - -## `window.log(level, message)` - -Writes a log entry that is buffered and flushed to `DATA_STORAGE/Acode.log` (10 MB cap, rotated from the top). `level` is one of `"error"`, `"warn"`, `"info"`, or `"debug"`; the minimum recorded level is `"info"` by default. - -`message` may be a string or an `Error` (in which case the stack trace is included). - -```javascript -window.log("error", "Something went wrong"); -window.log("warn", "Low battery"); -window.log("info", "Plugin initialized"); -``` - -## `BuildInfo` - -The Cordova `BuildInfo` plugin object, clobbered to `window.BuildInfo`. Read-only app metadata populated at startup: - -| Property | Type | Description | -|----------|------|-------------| -| `packageName` | `string` | App package id (e.g. `com.foxdebug.acode` or `com.foxdebug.acodefree`) | -| `basePackageName` | `string` | Base package name | -| `displayName` / `name` | `string` | App display name | -| `version` | `string` | Version name (e.g. `1.11.2`) | -| `versionCode` | `number` | Version code (integer) | -| `debug` | `boolean` | Whether the build is a debug build | -| `buildType` | `string` | Gradle build type | -| `flavor` | `string` | Gradle flavor | -| `installDate` | `string` | Formatted install date | - -```javascript -const isNewer = BuildInfo.versionCode >= 1008; -const isFree = /free$/.test(BuildInfo.packageName); -``` - -::: info -The Pro flag is `acode.require("config").HAS_PRO`, not a `BuildInfo` field. On the free build the package name ends in `free`. -::: - -## `system` - -The native `system` plugin (`cordova-plugin-system`), clobbered to `window.system`. Provides low-level Android utilities used by Acode and plugins: file operations, storage management, runtime permissions, app/device info, intents, shortcuts, and text comparison. - -```javascript -system.fileAction(fileUri, filename, "VIEW", "text/plain"); -``` - -See [System](../advanced-apis/system.md) for the full API. - -## `strings` - -The app's localized string table, clobbered to `window.strings` from `src/lib/lang.js`. It is a `{ key: value }` dictionary of the current language's translations. - -```javascript -window.strings.error; // localized "Error" text -``` - -::: tip -Prefer the [helpers](../utilities/helpers.md) module (`acode.require("helpers")`) for the most common cross-cutting utilities. -::: +console.log(ASSETS_DIRECTORY) // returns a string like "/path/to/assets" +console.log(IS_FREE_VERSION) // logs true if user is using free version of the app, else false +``` \ No newline at end of file diff --git a/docs/utilities/code-highlight.md b/docs/utilities/code-highlight.md index c97a686..9c24edf 100644 --- a/docs/utilities/code-highlight.md +++ b/docs/utilities/code-highlight.md @@ -1,25 +1,119 @@ -# Code Highlight +# Code Highlight -::: warning -The static CodeMirror / Lezer highlighter described by earlier revisions of this page is currently **internal to Acode** (`src/utils/codeHighlight.js`). It is **not** exposed to plugins: neither `acode.require("codeHighlight")` nor `acode.require("codemirror").highlight` exists in the current plugin API, and the `EditorFile` `highlightStyles` option is **not implemented**. +Acode exposes the same **static CodeMirror / Lezer highlighter** it uses for markdown previews, plugin pages, and LSP reference snippets. Plugins can highlight code without bundling a second highlighter, and the colors follow the user's editor theme. -Calling these throws `undefined is not a function` / `TypeError`. Do not use them until a future Acode release exposes the module. +::: info +Available from **versionCode `1008`** (the next Acode release). Set `"minVersionCode": 1008` in `plugin.json` when your plugin depends on it. ::: -## What is available instead +## Import -Acode uses CodeMirror 6. Plugins can highlight or render code using the shared CodeMirror packages exposed via `acode.require("codemirror")`: +```js +const codeHighlight = acode.require("codeHighlight"); +``` + +The same object is also available as `acode.require("codemirror").highlight`. + +## Highlight HTML + +Both methods return **escaped HTML** with Lezer `tok-*` class names (`tok-keyword`, `tok-string`, …). Put the result inside an element with the `cm-highlighted` class (or `codeHighlight.HIGHLIGHT_CLASS`). + +### `highlightCodeBlock(code, language?)` + +Highlight a multi-line snippet. `language` is a mode name or markdown fence id (`"javascript"`, `"python"`, `"js"`, `"ts"`, …). Unknown languages fall back to escaped plain text. ```js -const cm = acode.require("codemirror"); -// cm.language.HighlightStyle, cm.state, cm.view, cm.lezer, ... +const codeHighlight = acode.require("codeHighlight"); + +const html = await codeHighlight.highlightCodeBlock( + 'const answer = 42;\nconsole.log(answer);', + "javascript", +); + +const pre = document.createElement("pre"); +const code = document.createElement("code"); +code.className = codeHighlight.HIGHLIGHT_CLASS; +code.innerHTML = html; +pre.appendChild(code); ``` -- Language registration: [Editor Languages](./ace-modes.md) -- Editor theme registration: [Editor Themes](./editor-themes.md) -- Shared CodeMirror packages: [CodeMirror packages](./codemirror.md) +`highlight(code, language?)` is an alias of `highlightCodeBlock`. + +### `highlightLine(text, uri, symbolName?)` + +Highlight a single line. Language is inferred from `uri`. When `symbolName` is set, matching text is wrapped in ``. + +```js +const html = await codeHighlight.highlightLine( + "export function greet() {}", + "file:///sdcard/project/src/hello.js", + "greet", +); +``` + +## Shadow DOM and custom editor tabs + +Token colors live in a stylesheet, not in the returned HTML. Styles injected on `document` **do not pierce Shadow DOM**. + +Custom editor tabs do **not** get this stylesheet by default. Opt in when the tab will render highlighted HTML: + +```js +new EditorFile("snippet.js", { + type: "custom", + content: pre, + highlightStyles: true, +}); +``` + +For any other shadow root (a dialog, a custom element, a tab that did not set `highlightStyles`), adopt the shared sheet yourself: + +```js +const host = document.createElement("div"); +const shadow = host.attachShadow({ mode: "open" }); + +codeHighlight.applyStyles(shadow); +// or: codeHighlight.applyStyles(host); // resolves to host.shadowRoot +// or: codeHighlight.applyStyles(file.content); // custom tab host +``` + +`applyStyles` prefers `adoptedStyleSheets`. Theme changes then update every adopted root in place — you do not need to call it again. + +```js +const css = codeHighlight.getStyles(); +const sheet = codeHighlight.getStyleSheet(); +``` + +Use `getStyles()` only if you need the raw CSS string. Prefer `applyStyles` so theme updates stay in sync. + +## Cache + +Results are cached per theme + language + source. Call `codeHighlight.clearCache()` after you register or unregister a language if stale HTML would be a problem. + +## Custom tab example + +```js +const EditorFile = acode.require("EditorFile"); +const codeHighlight = acode.require("codeHighlight"); + +async function openSnippetTab(source, language) { + const html = await codeHighlight.highlightCodeBlock(source, language); + const pre = document.createElement("pre"); + const code = document.createElement("code"); + code.className = codeHighlight.HIGHLIGHT_CLASS; + code.innerHTML = html; + pre.appendChild(code); + + new EditorFile(`${language} snippet`, { + type: "custom", + tabIcon: "file file_type_js", + content: pre, + highlightStyles: true, + hideQuickTools: true, + }); +} +``` -For rendering syntax-highlighted HTML inside your own UI, bundle your own highlighter (e.g. `shiki`, `highlight.js`, or `@lezer/highlight` via the `@lezer/*` modules exposed under `acode.require("codemirror").lezer`). +`highlightStyles: true` adopts the highlight stylesheet into the tab's shadow root, so the snippet uses the current editor theme. ## Related APIs diff --git a/docs/utilities/codemirror.md b/docs/utilities/codemirror.md index ec9ae79..9c4a2bb 100644 --- a/docs/utilities/codemirror.md +++ b/docs/utilities/codemirror.md @@ -50,11 +50,11 @@ Prefer these requires over bundling your own copy of CodeMirror. Duplicate packa ## Related APIs -- Active editor: `editorManager.editor` - see [EditorManager](../global-apis/editor-manager.md) -- Language registration: `acode.require("editorLanguages")` - see [Editor Languages](./ace-modes.md) -- Theme registration: `acode.require("editorThemes")` - see [Editor Themes](./editor-themes.md) -- Language servers: `acode.require("lsp")` - see [LSP](../advanced-apis/lsp.md) -- Static highlighting status: see [Code Highlight](./code-highlight.md) +- Active editor: `editorManager.editor` — see [EditorManager](../global-apis/editor-manager.md) +- Language registration: `acode.require("editorLanguages")` — see [Editor Languages](./ace-modes.md) +- Theme registration: `acode.require("editorThemes")` — see [Editor Themes](./editor-themes.md) +- Language servers: `acode.require("lsp")` — see [LSP](../advanced-apis/lsp.md) +- Static highlighter for snippets and plugin tabs: `acode.require("codeHighlight")` — see [Code Highlight](./code-highlight.md) ## Minimal extension example diff --git a/docs/utilities/helpers.md b/docs/utilities/helpers.md deleted file mode 100644 index f9738d0..0000000 --- a/docs/utilities/helpers.md +++ /dev/null @@ -1,202 +0,0 @@ -# Helpers - -The `helpers` module is a collection of small utility functions used across Acode and exposed to plugins. - -Require it with `acode.require('helpers')`. - -```js -const helpers = acode.require("helpers"); -``` - -## Strings & parsing - -### `parseJSON(string): any | null` - -Parses a JSON string. Returns `null` when the input is empty or cannot be parsed (never throws). - -```js -helpers.parseJSON('{"a": 1}'); // { a: 1 } -helpers.parseJSON("not json"); // null -``` - -### `fixFilename(name: string): string` - -Removes line breaks (`\r\n`, `\r`, `\n`) and tabs from a name and trims it. - -```js -helpers.fixFilename("my\nfile.txt"); // "myfile.txt" -``` - -### `uuid(): string` - -Returns a unique id string (timestamp + random, base-36). - -### `formatDownloadCount(count: number): string` - -Formats a download count into a short human-readable string using `K`/`M`/`B`/`T` units. - -```js -helpers.formatDownloadCount(15400); // "15.4K" -helpers.formatDownloadCount(2_500_000); // "2.5M" -``` - -## Errors - -### `errorMessage(err, ...args): string` - -Builds a human-readable error message from an `Error`, a string, or a fallback. Extra `args` are appended with `
` separators, and URL-like arguments are rewritten to their virtual path via [`getVirtualPath`](#getvirtualpathpath-string-string). - -### `error(err, ...args): Promise` - -Shows an alert dialog with the error message. Returns a promise that resolves when the dialog is closed. If the error has `code === 0` a toast is shown instead. - -```js -try { - await something(); -} catch (err) { - await helpers.error(err); -} -``` - -## Types - -### `isDir(type: string): boolean` - -Returns `true` for `'dir'`, `'directory'`, or `'folder'`. - -### `isFile(type: string): boolean` - -Returns `true` for `'file'` or `'link'`. - -### `isBinary(file: string): boolean` - -Returns `true` if the file name/uri looks like a binary file. - -## URLs & paths - -### `getVirtualPath(path: string): string` - -Replaces the matching part of a url with the alias name of the storage it belongs to (from `localStorage.storageList`). Content uris are resolved to their primary (virtual) address first, if available. - -```js -helpers.getVirtualPath("content://com.android.externalstorage.documents/..."); -``` - -### `toInternalUri(uri: string): Promise` - -Resolves a `file://` (or other) uri to an internal `cdvfile://` url using `resolveLocalFileSystemURL`. - -```js -const internalUrl = await helpers.toInternalUri(file.uri); -``` - -### `updateUriOfAllActiveFiles(oldUrl, newUrl)` - -Updates the `uri` of every open file whose uri starts with `oldUrl`, replacing it with `newUrl` (keeping the filename). Pass `null` as `newUrl` to clear uris. Afterwards calls `editorManager.onupdate("file-delete")` and emits the `update` event with `"file-delete"` as its sub-action. - -### `createFileStructure(uri, pathString, isFile = true): Promise<{ uri, parentUri, created, type }>` - -Creates nested folders (and optionally a final file) under `uri`, walking `pathString` split on `/`. Handles special-case SAF/ExternalStorage/Termux/Acode-terminal document uris. - -Returns an object describing the first created entry: -- `uri` - url of the first created entry (or the target uri if nothing was created) -- `parentUri` - parent uri of the first created entry -- `created` - `true` when at least one entry was created -- `type` - `'file'` or `'folder'` - -Throws if an existing entry's type does not match the expected type. - -```js -const res = await helpers.createFileStructure( - "file:///storage/emulated/0/Acode", - "project/src/index.js", - true, -); -``` - -## Files & sorting - -### `getIconForFile(filename: string): string` - -Returns the icon class string for a filename (e.g. `"file file_type_default file_type_js"`). - -### `sortDir(list, fileBrowser, mode = "both"): Array` - -Sorts a list of file entries into directories-first order. `mode` can be `'both'`, `'file'`, or `'folder'`. Honors `sortByName` and `showHiddenFiles` settings from `fileBrowser`. Sets `item.icon` and `item.disabled` (when mode is `'folder'`) as a side effect. - -## Promises & timing - -### `promisify(func, ...args): Promise` - -Wraps a callback-style function that calls `(resolve, reject)` as its trailing arguments. - -```js -const value = await helpers.promisify(system.getFilesDir); -``` - -### `checkAPIStatus(): Promise` - -Fetches `API_BASE/status`. Resolves `true` when the Acode API is reachable, `false` on any error. - -### `debounce(func, wait): Function` - -Returns a debounced version of `func` that only runs after `wait` ms without further calls. - -```js -window.addEventListener("resize", helpers.debounce(onResize, 200)); -``` - -## DOM & HTML - -### `parseHTML(html): HTMLElement | HTMLElement[]` - -Parses an HTML string with `DOMParser`. Returns the single element when there is exactly one child, otherwise an array of children. - -```js -const el = helpers.parseHTML("
Hello
"); -``` - -## Deprecation helpers - -### `defineDeprecatedProperty(obj, name, getter, setter)` - -Defines a property on `obj` that warns to the console whenever it is read or written. - -### `decodeText(arrayBuffer, encoding = "utf-8"): string` - -::: warning Deprecated -Use the `encodings` module instead. -::: - -Decodes an `ArrayBuffer` to a string. When `encoding` is `"json"`, the result is parsed as JSON. - -## Ads (free builds only) - -These are used by Acode internally to manage ads on the free build. They are no-ops / return `false` on Pro. - -| Method | Description | -|--------|-------------| -| `canShowAds()` | `true` when the build is not Pro and ads are available | -| `showInterstitialIfReady()` | Shows an interstitial ad if loaded; resolves `true` when shown | -| `showAd()` | Displays a banner ad on the current page (if eligible) | - -## Purchasing - -| Method | Description | -|--------|-------------| -| `isIapAvailable()` | `true` when the In-App Purchase plugin is present and available | -| `shouldAllowExternalPurchase()` | `true` when IAP is unavailable and the app was not installed from the Play Store | - -## Mtime helpers - -Used by the editor's disk-conflict tracking. - -| Method | Description | -|--------|-------------| -| `normalizeMtime(value): number \| null` | Converts a `Date` or timestamp to a numeric ms value (or `null`) | -| `getStatMtime(stat): number \| null` | Extracts an mtime from a `stat` object (`modifiedDate`, `lastModified`, or `mtime`) | - -## Related APIs - -- [Config](../global-apis/config.md) - `HAS_PRO` and other app constants used by the helpers -- [File System (fs)](./fs.md) - low-level file operations diff --git a/docs/utilities/open-folder.md b/docs/utilities/open-folder.md index dd9922c..728dc2f 100644 --- a/docs/utilities/open-folder.md +++ b/docs/utilities/open-folder.md @@ -97,6 +97,7 @@ const folder = openFolder.find('/path/to/fileOrFolder'); The `openFolder` utility emits various events to help manage folder operations: - `add-folder` - `remove-folder` +- `update-folder` These events can be listened to for performing custom actions upon folder operations. From 04ed1715915fb6e4aff24098aae59c627febacbe Mon Sep 17 00:00:00 2001 From: Rohit Kushwaha Date: Tue, 25 Aug 2026 15:30:40 +0530 Subject: [PATCH 3/6] fix: removed entry --- .vitepress/config.mts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.vitepress/config.mts b/.vitepress/config.mts index 5e25755..009fdc9 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -196,10 +196,6 @@ export default defineConfig({ text: "File System(fs)", link: "/docs/utilities/fs", }, - { - text: "Helpers", - link: "/docs/utilities/helpers", - }, { text: "URL", link: "/docs/utilities/url", From 272926c0f5c19972d11e370dffaedde24dce3462 Mon Sep 17 00:00:00 2001 From: Rohit Kushwaha Date: Tue, 25 Aug 2026 15:33:34 +0530 Subject: [PATCH 4/6] fix: removed unused permission entry as its not currently in use --- docs/plugin-essentials/manifest.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/plugin-essentials/manifest.md b/docs/plugin-essentials/manifest.md index dddca12..3c1450c 100644 --- a/docs/plugin-essentials/manifest.md +++ b/docs/plugin-essentials/manifest.md @@ -103,7 +103,6 @@ If you wish to publish an update for your plugin, follow these guidelines: "price": 0, "license": "MIT", "keywords": ["foo","bar"], - "permissions": ["read", "write"], "changelogs": "changelogs.md", "author": { "name": "Example Author", From 3c0101279f70684980f60db689e86cb71c4c9b16 Mon Sep 17 00:00:00 2001 From: Rohit Kushvaha Date: Tue, 25 Aug 2026 15:37:45 +0530 Subject: [PATCH 5/6] Update system.md --- docs/advanced-apis/system.md | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/docs/advanced-apis/system.md b/docs/advanced-apis/system.md index f63d0af..923542c 100644 --- a/docs/advanced-apis/system.md +++ b/docs/advanced-apis/system.md @@ -235,18 +235,3 @@ Changes the soft-keyboard input type. ### `setNativeContextMenuDisabled(disabled, success, error)` Enables or disables the native context menu on the WebView. - -## Rewards - -### `getRewardStatus(success, error)` - -Resolves the current reward status (used by the ad-reward system). - -### `redeemReward(offerId, success, error)` - -Redeems a reward offer. - -## Related APIs - -- Install source & other globals: [Other Global Utilities](../global-apis/global-utilities.md) -- `helpers.promisify` for callback-style methods: [Helpers](../utilities/helpers.md) From 284d615a3d5dfd62a6818a99b88ba2ef31135852 Mon Sep 17 00:00:00 2001 From: Rohit Kushvaha Date: Tue, 25 Aug 2026 15:39:35 +0530 Subject: [PATCH 6/6] Update manifest.md --- docs/plugin-essentials/manifest.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/plugin-essentials/manifest.md b/docs/plugin-essentials/manifest.md index 3c1450c..1061282 100644 --- a/docs/plugin-essentials/manifest.md +++ b/docs/plugin-essentials/manifest.md @@ -69,11 +69,6 @@ The `plugin.json` file is a crucial component of every Acode plugin, serving as ## 15. **repository:** - Github/Gitlab url of your plugin source(only for free plugins) -## 16. **permissions:** - - An array of permission strings granted to your plugin's context. - - Permissions are bound to your plugin's [context token](./plugin-context.md) when it loads and can be checked at runtime with `ctx.grantedPermission(permission)` or `ctx.listAllPermissions()`. - - Only the permissions listed here are granted; there is no runtime permission prompt. - # Updating Plugins: If you wish to publish an update for your plugin, follow these guidelines: