diff --git a/.vitepress/config.mts b/.vitepress/config.mts index 20ca73a..09933ea 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -5,6 +5,7 @@ export default defineConfig({ lastUpdated: true, cleanUrls: true, base: "/", + srcExclude: ["Acode/**"], head: [["link", { rel: "icon", href: "/acode.png" }]], locales: { @@ -102,7 +103,7 @@ export default defineConfig({ collapsed: true, items: [ { - text: "Ace", + text: "CodeMirror & Legacy Ace", link: "/docs/global-apis/ace", }, { @@ -196,9 +197,17 @@ export default defineConfig({ link: "/docs/utilities/projects", }, { - text: "ACE Modes", + text: "Commands", + link: "/docs/utilities/commands", + }, + { + text: "Editor Languages", link: "/docs/utilities/ace-modes", }, + { + text: "Editor Themes", + link: "/docs/utilities/editor-themes", + }, { text: "Encoding", link: "/docs/utilities/encoding", @@ -303,6 +312,14 @@ export default defineConfig({ text: "Terminal", link: "/docs/advanced-apis/terminal", }, + { + text: "LSP", + link: "/docs/advanced-apis/lsp", + }, + { + text: "File Handlers", + link: "/docs/advanced-apis/file-handlers", + }, { text: "Action Stack", link: "/docs/advanced-apis/action-stack", diff --git a/docs/advanced-apis/file-handlers.md b/docs/advanced-apis/file-handlers.md new file mode 100644 index 0000000..1c247f1 --- /dev/null +++ b/docs/advanced-apis/file-handlers.md @@ -0,0 +1,35 @@ +# File Handlers API + +Use this API to register custom open handlers for file extensions. + +## `acode.registerFileHandler(id, options)` + +Registers a handler. + +```js +acode.registerFileHandler("com.example.svg-viewer", { + extensions: ["svgx", ".svgalt"], + handleFile: async (fileInfo) => { + console.log(fileInfo.name, fileInfo.uri); + }, +}); +``` + +`options` fields: + +- `extensions` (required): extension array. Dots are allowed and normalized. +- `handleFile` (required): async function receiving file info. + +## `acode.unregisterFileHandler(id)` + +Removes a handler. + +```js +acode.unregisterFileHandler("com.example.svg-viewer"); +``` + +## Notes + +- Handler ids must be unique. +- Extensions are matched case-insensitively. +- `"*"` can be used to match any extension. diff --git a/docs/advanced-apis/lsp.md b/docs/advanced-apis/lsp.md new file mode 100644 index 0000000..962c627 --- /dev/null +++ b/docs/advanced-apis/lsp.md @@ -0,0 +1,146 @@ +# LSP API + +Use this API to register/manage language servers used by Acode's CodeMirror LSP client. + +## Import + +```js +const lsp = acode.require("lsp"); +``` + +## Important Transport Reality + +CodeMirror's LSP client in Acode communicates via **WebSocket** transport. + +- `transport.kind: "websocket"` is the recommended and practical mode. +- `transport.kind: "stdio"` is **not a direct stdio pipe** to the editor. +- In this codebase, `stdio` mode still expects a **WebSocket bridge URL**. + +For local stdio language servers, use an **AXS bridge** (`launcher.bridge`) and keep transport as websocket. + +::: warning +`stdio` with only `transport.command` is not enough in Acode. +You still need a WebSocket bridge endpoint (AXS or equivalent) for the client connection. +::: + +## Recommended Setup (Local Server via AXS Bridge) + +```js +lsp.registerServer( + { + id: "typescript-custom", + label: "TypeScript (Custom)", + languages: ["javascript", "javascriptreact", "typescript", "typescriptreact", "tsx", "jsx"], + transport: { + kind: "websocket", + // url can be omitted when using launcher.bridge auto-port mode + }, + launcher: { + bridge: { + kind: "axs", + command: "typescript-language-server", + args: ["--stdio"], + }, + checkCommand: "which typescript-language-server", + install: { + command: + "apk add --no-cache nodejs npm && npm install -g typescript-language-server typescript", + }, + }, + enabled: true, + initializationOptions: { + provideFormatter: true, + }, + }, + { replace: true }, +); +``` + +## Remote/External WebSocket Server + +```js +lsp.registerServer({ + id: "remote-lsp", + label: "Remote LSP", + languages: ["json"], + transport: { + kind: "websocket", + url: "ws://127.0.0.1:2087/", + options: { + binary: true, + timeout: 5000, + }, + }, + enabled: true, +}); +``` + +## API Reference + +### `registerServer(definition, options?)` + +- `options.replace?: boolean` (default `false`) + - `false`: existing server with same id is kept + - `true`: existing server is replaced + +Definition fields commonly used: + +- `id` (required): normalized to lowercase +- `label` (optional) +- `languages` (required): non-empty array, normalized to lowercase +- `enabled` (optional, default `true`) +- `transport` (required) + - `kind`: `"websocket"` or `"stdio"` (see transport note above) + - `url?: string` + - `command?: string` (required by registry validation when using `stdio`) + - `args?: string[]` + - `options?: { binary?, timeout?, reconnect?, maxReconnectAttempts? }` +- `launcher` (optional) + - `startCommand?: string | string[]` + - `command?: string` + - `args?: string[]` + - `checkCommand?: string` + - `install?: { command?: string }` + - `bridge?: { kind: "axs", command: string, args?: string[], port?: number, session?: string }` +- `initializationOptions?: object` +- `clientConfig?: object` +- `startupTimeout?: number` +- `capabilityOverrides?: object` +- `rootUri?: (uri, context) => string | null` +- `resolveLanguageId?: (context) => string | null` +- `useWorkspaceFolders?: boolean` + +### Other Registry Methods + +- `unregisterServer(id)` +- `updateServer(id, updater)` +- `getServer(id)` +- `listServers()` +- `getServersForLanguage(languageId, options?)` + +```js +const jsServers = lsp.getServersForLanguage("javascript"); + +lsp.updateServer("typescript-custom", (current) => ({ + ...current, + enabled: false, +})); +``` + +`getServersForLanguage` options: + +- `includeDisabled?: boolean` (default `false`) + +## Client Manager + +- `clientManager.setOptions(options)` +- `clientManager.getActiveClients()` + +```js +lsp.clientManager.setOptions({ + diagnosticsUiExtension: [], +}); + +const activeClients = lsp.clientManager.getActiveClients(); +console.log(activeClients); +``` diff --git a/docs/editor-components/editor-file.md b/docs/editor-components/editor-file.md index c1d5b83..f949508 100644 --- a/docs/editor-components/editor-file.md +++ b/docs/editor-components/editor-file.md @@ -46,7 +46,7 @@ Both methods are equivalent and accept & return the same parameters. | cursorPos | `object` | Cursor position | - | | scrollLeft | `number` | Scroll left position | - | | scrollTop | `number` | Scroll top position | - | -| folds | [`Array`](https://ajaxorg.github.io/ace-api-docs/classes/src_edit_session_fold.Fold.html) | Code folds | - | +| folds | `Array<{ fromLine: number, fromCol: number, toLine: number, toCol: number }>` | Code folds | - | | type | `string` | Type of content (e.g., 'editor') | `'editor'` | | 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 | - | @@ -76,7 +76,7 @@ Both methods are equivalent and accept & return the same parameters. | SAFMode | `'single' \| 'tree' \| null` | Storage access framework mode | | loaded | `boolean` | Whether file has completed loading text | | loading | `boolean` | Whether file is still loading text | -| session | `AceAjax.IEditSession` | EditSession of the file | +| session | `Proxy` | Session state with Ace-compatible helper methods | | readOnly | `boolean` | Whether file is readonly | | markChanged | `boolean` | Whether to mark changes when session text changes | diff --git a/docs/getting-started/understanding-plugin.md b/docs/getting-started/understanding-plugin.md index 7b1c631..6c1e65b 100644 --- a/docs/getting-started/understanding-plugin.md +++ b/docs/getting-started/understanding-plugin.md @@ -1,17 +1,100 @@ -# Understanding Plugins +# Understanding How Plugins Work -:::warning -**Note:** Work is in progress 🚧 -::: +This page is the practical mental model for writing Acode plugins: what Acode does, what your plugin must do, and what happens during load/unload. -We are currently working on this section to provide you with detailed and comprehensive information about how Acode plugins work. Please check back soon for updates! +## The Plugin Contract -## Contribute to the Documentation +From Acode's perspective, your plugin is: -We welcome contributions from the community! If you would like to help improve this documentation, please visit our [GitHub repository](https://github.com/bajrangCoder/acode-plugin-docs) and follow the contribution guidelines. +1. A folder in `PLUGIN_DIR` +2. A `plugin.json` +3. An entry script (usually `main.js`) -:::tip -You can suggest changes, add new content, or improve existing sections. Every bit of help is appreciated! 🤗 -::: +From your perspective, your script should register: -For more information, see official [Guide](https://acode.app/plugin-docs). +- `acode.setPluginInit(pluginId, initFn)` +- `acode.setPluginUnmount(pluginId, unmountFn)` (strongly recommended) + +If you skip `setPluginInit`, your script may load, but your plugin logic will not run through Acode's lifecycle. + +## Lifecycle In One View + +1. Acode discovers plugin folders. +2. It decides which plugins to load (enabled, not broken, not already loaded). +3. It loads your entry script. +4. It calls your registered `init` with runtime context. +5. Later, on disable/uninstall/reload, it calls your registered `unmount`. + +## What You Get In `init` + +Your init function receives: + +- `baseUrl`: internal base URL to your plugin files +- `$page`: a plugin page object for UI screens +- `cache`: object with: + - `cacheFileUrl` + - `cacheFile` + - `firstInit` + - `ctx` + +Use `firstInit` for one-time setup or migration. + +## Recommended `main.js` Shape + +```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(); + }, + }); +} + +function unmount() { + const commands = acode.require("commands"); + commands.removeCommand("example.open"); +} + +acode.setPluginInit(plugin.id, init); +acode.setPluginUnmount(plugin.id, unmount); +``` + +## What Happens On Disable / Enable / Uninstall + +- Disable: + - Acode calls `acode.unmountPlugin(id)` which triggers your unmount. + - Plugin runtime state is cleared (including plugin cache file). +- Enable: + - Acode loads the plugin again and runs init again. +- Uninstall: + - Plugin files are removed. + - Acode runs unmount cleanup for loaded resources. + +Treat `init` as repeatable and `unmount` as mandatory cleanup. + +## Failure Behavior You Should Know + +If your plugin throws during load/init: + +- it is marked as broken for the session flow, +- Acode skips loading it again until user/action retries it. + +For programmatic recovery: + +```js +acode.clearBrokenPluginMark("com.example.plugin"); +``` + +## Author Guidelines + +- Keep `init` fast; do heavy work lazily. +- Register commands through `acode.require("commands")`. +- Always remove listeners, commands, intervals, and UI hooks in `unmount`. +- 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 2a441b3..b08ff3e 100644 --- a/docs/global-apis/ace.md +++ b/docs/global-apis/ace.md @@ -1,26 +1,29 @@ -# Using the Ace Global API in Acode +# CodeMirror and Legacy Ace Compatibility -**Introduction** +Acode now uses **CodeMirror 6** as the editor engine. -Acode is built on the Ace editor to provide a robust code editing experience. The Ace Global API offers a way to interact with the underlying Ace editor instance within the Acode environment. +This page exists for plugin migration from Ace-era APIs. -**Key Points** +## What To Use Now -* **Not the Raw Ace Instance:** The Ace Global API is not a direct reference to the current editor instance. For direct manipulation, use the `editorManager`. -* **Official Ace API Reference:** For a comprehensive understanding of Ace's capabilities, refer to the official documentation: [https://ajaxorg.github.io/ace-api-docs/modules/ace.html](https://ajaxorg.github.io/ace-api-docs/modules/ace.html). +- Use `editorManager.editor` as the active editor view (`EditorView`). +- 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. -**Example: Including Language Tools** +## Legacy Compatibility -To enable language tools for a specific language, you can use the following code: +Acode still provides some Ace-like compatibility for older plugins: -```javascript -// main.js -ace.require("ace/ext/language_tools"); -``` +- `editorManager.editor.session` exposes a session object with Ace-style helper methods. +- `acode.require("aceModes")` still maps to mode registration helpers. -**Additional Considerations** +These compatibility layers are for transition only. Prefer CodeMirror-first APIs for new plugins. -* **API Differences:** While the Ace Global API provides access to many Ace features, there might be limitations or differences compared to directly interacting with the raw Ace instance. -* **Acode-Specific Functionalities:** Explore the Acode documentation to discover additional features and customizations related to the Ace editor that might be available through the Ace Global API or other Acode-specific mechanisms. +## Migration Quick Map -By understanding the nuances of the Ace Global API and leveraging the official Ace documentation, you can effectively customize and extend Acode's editing capabilities. +| Old pattern | New pattern | +|---|---| +| `acode.require("aceModes")` | `acode.require("editorLanguages")` | +| `editor.session.setMode(...)` | `editorManager.activeFile?.setMode(...)` | +| Ace global API usage (`ace.*`) | `editorManager.editor` + `editorLanguages` + `editorThemes` | diff --git a/docs/global-apis/acode.md b/docs/global-apis/acode.md index 2e35830..3184b96 100644 --- a/docs/global-apis/acode.md +++ b/docs/global-apis/acode.md @@ -14,7 +14,7 @@ This method is used to register the plugin. This method takes two parameters, `p ```js acode.setPluginInit('com.example.plugin', (baseUrl, $page, cache) => { // [!code focus] - const { commands } = editorManager.editor; + const commands = acode.require("commands"); commands.addCommand({ name: 'example-plugin', bindKey: { win: 'Ctrl-Alt-E', mac: 'Command-Alt-E' }, @@ -95,7 +95,7 @@ This method is used to set the unmount function. This function will be called wh ```js acode.setPluginUnmount("com.example.plugin", () => { // [!code focus] - const { commands } = editorManager.editor; + const commands = acode.require("commands"); commands.removeCommand("example-plugin"); }); ``` @@ -138,18 +138,21 @@ This method executes a command defined in file `src/lib/commands.js`. This metho acode.exec("console"); // Opens the console ``` -### `registerFormatter(pluginId: string, extensions: string[], format: Function)` +### `registerFormatter(pluginId: string, extensions: string[], format: Function, displayName?: string)` -This method is used to register a formatter. This method takes three parameters, `pluginId`, `extensions` and format function. The `pluginId` is the ID of your plugin. The `extensions` is an array of file extensions. The format function is the function that will be called when the file is formatted. +This method is used to register a formatter. It takes `pluginId`, `extensions`, formatter function, and optional display name. **Example:** ```js acode.registerFormatter("com.example.plugin", ["js"], () => { // [!code focus] // formats the active file if supported - const text = editorManager.editor.session.getValue(); + const view = editorManager.editor; + const text = view.state.doc.toString(); // format the text - editorManager.editor.session.setValue(text); + view.dispatch({ + changes: { from: 0, to: view.state.doc.length, insert: text } + }); }); ``` @@ -157,6 +160,34 @@ acode.registerFormatter("com.example.plugin", ["js"], () => { // [!code focus] This method is used to unregister a formatter. This method takes one parameter, `pluginId`. The pluginId is the ID of your plugin. +### `format(selectIfNull = true): Promise` + +Formats the active editor file using the selected formatter for the current mode. + +- `selectIfNull` (optional): when `true`, Acode opens formatter selection if none is configured. + +Returns `true` when formatting succeeds, otherwise `false`. + +```js +await acode.format(); +``` + +### `formatters: Array<{ id: string, name: string, exts: string[] }>` + +List of registered formatters. + +```js +console.log(acode.formatters); +``` + +### `getFormatterFor(extensions: string[]): Array<[string | null, string]>` + +Returns formatter options for the given extensions. + +```js +const options = acode.getFormatterFor(["js", "ts"]); +``` + ### `addIcon(iconName: string, iconSrc: string, options?: { monochrome?: boolean })` This method is used to add an icon. This method takes two parameters, `iconName` , `iconSrc` and a optional. The `iconName` is the name of the icon. The `iconSrc` is the URL of the icon. If `options.monochrome` true, uses CSS masks to render the icon. This allows it to inherit the theme's currentColor(in case of svg). @@ -230,35 +261,55 @@ This api is added in `v1.10.6` , versionCode: `954` ::: -### `newEditorFile(filename: string, options?: FileOptions): EditorFile` +### `newEditorFile(filename: string, options?: FileOptions): void` ::: warning Requires version code `958` or above ::: -Creates a new EditorFile instance. This is an alternative to using the [EditorFile](../editor-components/editor-file.md) constructor directly. +Creates a new EditorFile instance and adds it to the editor. **Parameters:** * `filename: string` - Name of the file * `options?: FileOptions` - File creation options (see [EditorFile API](../editor-components/editor-file.md#fileoptions) for details) -**Returns:** - -* `EditorFile` - A new EditorFile instance - **Example:** ```js -const file = acode.newEditorFile('example.js', { - text: 'console.log("Hello World");', - editable: true +acode.newEditorFile("example.js", { + text: 'console.log("Hello World");', + editable: true, }); ``` ::: tip -This method is equivalent to `new EditorFile(filename, options)`. Both methods accept and return the same parameters. +This method is equivalent to calling `new EditorFile(filename, options)`. ::: ::: info This API was added in `v1.11.0` (versionCode: `956`) and marked stable in `v1.11.2` (versionCode: `958`) ::: + +### `waitForPlugin(pluginId: string): Promise` + +Resolves when the target plugin has loaded. + +```js +await acode.waitForPlugin("com.example.other-plugin"); +``` + +### `clearBrokenPluginMark(pluginId: string): void` + +Clears a plugin's broken mark so it can be retried on next load. + +```js +acode.clearBrokenPluginMark("com.example.plugin"); +``` + +## Related APIs + +- Commands API (preferred for adding/removing commands): [Commands](../utilities/commands.md) +- CodeMirror editor theme API: [Editor Themes](../utilities/editor-themes.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) diff --git a/docs/global-apis/editor-manager.md b/docs/global-apis/editor-manager.md index 25e6b67..9a6c5e7 100644 --- a/docs/global-apis/editor-manager.md +++ b/docs/global-apis/editor-manager.md @@ -6,18 +6,54 @@ The `editorManager` allows to interact with the Editor Instance and listen to va ## Methods and Properties -### `editor` -This is an instance of the Ace editor. Check out editor methods [here](https://ajaxorg.github.io/ace-api-docs/classes/ace.Editor-1.html) +### `editor` +This is the active **CodeMirror `EditorView`** instance. -- `addCommand(args: string | object)` This method allows you add a command to command palette of Acode app. You can use it like: +Read text: ```javascript - editorManager.editor.commands.addCommand("command name or command object") - ``` +const text = editorManager.editor.state.doc.toString(); +``` + +Update text: +```javascript +const view = editorManager.editor; +view.dispatch({ + changes: { from: 0, to: view.state.doc.length, insert: "new content" }, +}); +``` + +Add a command: +```javascript +// Compatibility API +editorManager.editor.commands.addCommand({ + name: "my-command", + description: "My command", + exec: () => console.log("Run"), +}); +``` + +Remove a command: +```javascript +// Compatibility API +editorManager.editor.commands.removeCommand("my-command"); +``` + +::: tip +Prefer `acode.require("commands")` for command registration/removal in new plugins. +See: [Commands API](../utilities/commands.md) +::: + +Compatibility helpers are also available for legacy plugins: + +- `editor.session` (Ace-style session proxy for active file) +- `editor.getValue()` +- `editor.gotoLine(...)` +- `editor.insert(text)` +- `editor.getCursorPosition()` +- `editor.moveCursorToPosition({ row, column })` +- `editor.selection.getRange()` +- `editor.getCopyText()` -- `removeCommand(args: string | object)` This method allows you remove a command to command palette of Acode app. You can use it like: -``` javascript -editorManager.editor.commands.removeCommand("command name or command object") - ``` ### `addNewFile(filename?:string, options?: object)` This function adds a new file to the workspace. diff --git a/docs/plugin-essentials/core-file.md b/docs/plugin-essentials/core-file.md index 9e76705..c251c41 100644 --- a/docs/plugin-essentials/core-file.md +++ b/docs/plugin-essentials/core-file.md @@ -2,6 +2,8 @@ The `main.js`(can be of any name but that must be specified in `plugin.json`) file is the heart of your Acode plugin, serving as the entry point and execution hub when the plugin is loaded. Here we'll explore the essential concept of `main.js`, focusing on initialization, registration, and cleanup. +For loader behavior and runtime lifecycle details, see [Understanding Plugin Lifecycle](../getting-started/understanding-plugin.md). + ## Plugin Initialization ### Entry Point for Your Plugin @@ -43,7 +45,7 @@ Here's an illustrative example of a `main.js` file: ```javascript acode.setPluginInit('com.example.plugin', (baseUrl, $page, cache) => { - const { commands } = editorManager.editor; + const commands = acode.require("commands"); commands.addCommand({ name: 'example-plugin', bindKey: { win: 'Ctrl-Alt-E', mac: 'Command-Alt-E' }, @@ -66,13 +68,17 @@ The `main.js` file must also define an unmount function, which is called when th ```javascript acode.setPluginUnmount('com.example.plugin', () => { - const { commands } = editorManager.editor; + const commands = acode.require("commands"); commands.removeCommand('example-plugin'); }); ``` In this example, the unmount function removes the 'example-plugin' command, ensuring that the plugin's impact on Acode is cleanly reverted upon unloading. +::: tip +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` ::: diff --git a/docs/utilities/ace-modes.md b/docs/utilities/ace-modes.md index 03537c1..0718909 100644 --- a/docs/utilities/ace-modes.md +++ b/docs/utilities/ace-modes.md @@ -1,88 +1,62 @@ -# Ace Modes +# Editor Languages (CodeMirror) -The Ace Modes is a utility method in Acode allows you to manage the language modes supported by the Ace editor. This includes adding new modes, removing existing ones, and configuring how the editor handles different file types. +Acode now uses CodeMirror. +For language registration, use `editorLanguages` as the primary API. -## Importing Ace Modes - -To use the Ace Modes utilities, you need to import them using the `acode.require` method: +## Import ```javascript -const aceModes = acode.require('aceModes'); +const editorLanguages = acode.require("editorLanguages"); ``` ## Methods -### `addMode(name: string, extensions: string | string[], caption: string)` - -The `addMode` method adds a new mode to the Ace editor. This is useful when you want to support a custom language or file type. +### `register(name, extensions, caption?, loader?)` -- **name**: The name of the mode. -- **extensions**: The file extensions associated with this mode. This can be a string or an array of strings. -- **caption**: The display name of the mode. +Registers a language mode. -#### Example +- `name`: Internal mode name. +- `extensions`: String or string array (without `.`), for example `"mymode"` or `["mymode", "mym"]`. +- `caption` (optional): Label shown in UI. +- `loader` (optional): Function returning a CodeMirror extension (or a Promise resolving to one). ```javascript -const { addMode } = acode.require('aceModes'); - -addMode('myMode', ['mymode', 'mym'], 'My Custom Mode'); +const editorLanguages = acode.require("editorLanguages"); + +editorLanguages.register( + "myMode", + ["mymode", "mym"], + "My Custom Mode", + async () => { + // Return a CodeMirror language extension here + return []; + } +); ``` -:::info - The `extensions` parameter can be a single string or an array of strings, making it flexible to accommodate multiple file types. -::: - -### `removeMode(name: string)` - -The `removeMode` method removes a mode from the Ace editor. This is useful if you need to clean up or no longer need support for a particular mode. +### `unregister(name)` -- **name**: The name of the mode to be removed. - -#### Example +Removes a previously registered language mode. ```javascript -const { removeMode } = acode.require('aceModes'); - -removeMode('myMode'); +const editorLanguages = acode.require("editorLanguages"); +editorLanguages.unregister("myMode"); ``` -:::tip -Removing a mode that is no longer needed can help keep editor environment clean and efficient. -::: - -## Example - -Here’s a more detailed example that demonstrates how to add and remove a custom mode, and how to utilize these modes within the Ace editor. -### Adding a Custom Mode +## Apply A Mode To Active File -Let's say you have a custom language with the extension `.mymode` and you want it to be recognized by the Ace editor. - -```javascript:line-numbers -const aceModes = acode.require('aceModes'); - -const onClick = () => { - // Action to perform when the menu item is clicked - console.log('Custom Mode Menu Item Clicked!'); -}; - -// Adding the custom mode -aceModes.addMode('myMode', ['mymode'], 'My Custom Mode'); - -// Assuming you have the necessary mode definitions loaded for 'myMode' -// Example of using the custom mode in the editor -const editor = editorManager.editor; -editor.session.setMode('ace/mode/myMode'); +```javascript +editorManager.activeFile?.setMode("myMode"); ``` -### Removing a Custom Mode +## Legacy Alias (`aceModes`) -If you decide to remove the custom mode from the Ace editor: +`acode.require("aceModes")` is still available for backward compatibility: -```javascript:line-numbers -const aceModes = acode.require('aceModes'); - -// Removing the custom mode -aceModes.removeMode('myMode'); - -// The mode will no longer be available for use +```javascript +const aceModes = acode.require("aceModes"); +aceModes.addMode("myMode", ["mymode"], "My Custom Mode"); +aceModes.removeMode("myMode"); ``` + +Prefer `editorLanguages` for new plugins. diff --git a/docs/utilities/commands.md b/docs/utilities/commands.md new file mode 100644 index 0000000..830e7c2 --- /dev/null +++ b/docs/utilities/commands.md @@ -0,0 +1,71 @@ +# Commands API + +Use the Commands API to register commands for command palette/keybindings and execute them programmatically. + +## Preferred Access + +```js +const commands = acode.require("commands"); +``` + +::: tip +Prefer this API over `editorManager.editor.commands.*` (which is kept for compatibility). +::: + +## `addCommand(descriptor)` + +Registers a command. + +```js +commands.addCommand({ + name: "example.sayHello", + description: "Say hello", + bindKey: { win: "Ctrl-Alt-H", mac: "Command-Alt-H" }, + exec: (view, args) => { + acode.alert("Hello", `Args: ${JSON.stringify(args)}`); + return true; + }, +}); +``` + +Descriptor fields: + +- `name` (required): unique command id. +- `description` (optional): command palette label. +- `bindKey` (optional): key combo string or `{ win, linux, mac }`. +- `exec` (required): function `(view, args) => boolean | void`. + +## `removeCommand(name)` + +Unregisters a command. + +```js +commands.removeCommand("example.sayHello"); +``` + +## `registry` + +Low-level registry API: + +- `registry.add(descriptor)` +- `registry.remove(name)` +- `registry.execute(name, view?, args?)` +- `registry.list()` + +```js +commands.registry.execute("example.sayHello", editorManager.editor, { + source: "plugin", +}); + +const all = commands.registry.list(); +console.log(all.map((cmd) => cmd.name)); +``` + +## Convenience Methods On `acode` + +These call the same registry internally: + +- `acode.addCommand(descriptor)` +- `acode.removeCommand(name)` +- `acode.execCommand(name, view?, args?)` +- `acode.listCommands()` diff --git a/docs/utilities/editor-themes.md b/docs/utilities/editor-themes.md new file mode 100644 index 0000000..e390849 --- /dev/null +++ b/docs/utilities/editor-themes.md @@ -0,0 +1,204 @@ +# Editor Themes API + +The Editor Themes API lets plugins register CodeMirror themes in Acode. + +## Import + +```js +const editorThemes = acode.require("editorThemes"); +``` + +## `register(spec)` + +Registers a theme. + +```js +editorThemes.register({ + id: "example-night", + caption: "Example Night", + dark: true, + extensions: editorThemes.createTheme({ + dark: true, + styles: { + "&": { + backgroundColor: "#0f1115", + color: "#d6deeb", + }, + }, + }), +}); +``` + +### Full Plugin-Style Example + +```js +const editorThemes = acode.require("editorThemes"); + +function buildChaiTheme() { + const { cm, createTheme, createHighlightStyle } = editorThemes; + const t = cm.tags; + + const highlight = createHighlightStyle([ + { tag: t.keyword, color: "#ffb86c" }, + { tag: [t.string, t.special(t.string)], color: "#a5ff90" }, + { tag: [t.number, t.bool], color: "#ffd866" }, + { tag: t.comment, color: "#7f8c98" }, + { tag: [t.function(t.variableName), t.propertyName], color: "#8be9fd" }, + { tag: [t.typeName, t.className], color: "#bd93f9" }, + { tag: t.invalid, color: "#ff6b6b" }, + ]); + + return createTheme({ + dark: true, + styles: { + "&": { color: "#e6edf3", backgroundColor: "#101418" }, + ".cm-content": { caretColor: "#f8f8f2" }, + ".cm-cursor, .cm-dropCursor": { borderLeftColor: "#f8f8f2" }, + ".cm-selectionBackground, .cm-content ::selection": { + backgroundColor: "#2a3340", + }, + ".cm-gutters": { + backgroundColor: "#101418", + color: "#637083", + border: "none", + }, + ".cm-activeLine": { backgroundColor: "#18202a" }, + ".cm-activeLineGutter": { backgroundColor: "#18202a" }, + }, + highlightStyle: highlight, + }); +} + +acode.setPluginInit("com.example.theme", () => { + editorThemes.register({ + id: "chai_theme", + caption: "Chai Theme", + dark: true, + getExtension: () => buildChaiTheme(), + config: { + name: "chai_theme", + dark: true, + background: "#101418", + foreground: "#e6edf3", + keyword: "#ffb86c", + string: "#a5ff90", + number: "#ffd866", + comment: "#7f8c98", + function: "#8be9fd", + variable: "#e6edf3", + type: "#bd93f9", + class: "#bd93f9", + constant: "#ffd866", + operator: "#ffb86c", + invalid: "#ff6b6b", + }, + }); +}); + +acode.setPluginUnmount("com.example.theme", () => { + editorThemes.unregister("chai_theme"); +}); +``` + +### Sample (Class + `plugin.json` style) + +```js +import plugin from "../plugin.json"; + +class ChaiThemePlugin { + constructor() { + this.themeId = "chai_theme"; + this.editorThemes = acode.require("editorThemes"); + } + + buildExtensions() { + const { cm, createTheme, createHighlightStyle } = this.editorThemes; + const t = cm.tags; + + const highlight = createHighlightStyle([ + { tag: t.keyword, color: "#ffb86c" }, + { tag: [t.string, t.special(t.string)], color: "#a5ff90" }, + { tag: t.comment, color: "#7f8c98" }, + ]); + + return createTheme({ + dark: true, + styles: { + "&": { color: "#e6edf3", backgroundColor: "#101418" }, + ".cm-gutters": { backgroundColor: "#101418", border: "none" }, + }, + highlightStyle: highlight, + }); + } + + init() { + this.editorThemes.register({ + id: this.themeId, + caption: "Chai Theme", + dark: true, + getExtension: () => this.buildExtensions(), + }); + } + + destroy() { + this.editorThemes.unregister(this.themeId); + } +} + +const acodePlugin = new ChaiThemePlugin(); + +acode.setPluginInit(plugin.id, () => { + acodePlugin.init(); +}); + +acode.setPluginUnmount(plugin.id, () => { + acodePlugin.destroy(); +}); +``` + +`spec` fields: + +- `id` (required): unique theme id (or `name` alias). +- `caption` (optional): display label (or `label` alias). +- `dark` (optional): whether the theme is dark (or `isDark` alias). +- `getExtension` / `extensions` / `extension` / `theme` (required): CodeMirror extension(s) or function returning them. +- `config` (optional): theme metadata object. + +## `apply(id)` + +Applies a registered theme to the active editor. + +```js +editorThemes.apply("example-night"); +``` + +## Theme Management Methods + +- `unregister(id)` +- `list()` +- `get(id)` +- `getConfig(id)` + +```js +const themes = editorThemes.list(); +console.log(themes.map((t) => t.id)); +``` + +## Helpers + +### `createTheme({ styles, dark, highlightStyle, extensions })` + +Builds a theme extension array. + +### `createHighlightStyle(spec)` + +Builds a `HighlightStyle` from tag rules. + +### `cm` + +CodeMirror helpers exposed by Acode: + +- `EditorView` +- `HighlightStyle` +- `syntaxHighlighting` +- `tags` diff --git a/user-guide/settings/editor-settings.md b/user-guide/settings/editor-settings.md index a6fb39c..c771382 100644 --- a/user-guide/settings/editor-settings.md +++ b/user-guide/settings/editor-settings.md @@ -1,45 +1,41 @@ # Editor Settings -Customize the code editor to match your coding style. - -## Scroll Settings - -Opens a sub-page with these options: - -| Setting | Description | -| :--------------------- | :---------------------------------- | -| **Diagonal Scrolling** | Enable diagonal scrolling behavior. | -| **Reverse Scrolling** | Reverse the scroll direction. | -| **Scroll Speed** | Adjust the scrolling speed. | -| **Scrollbar Size** | Set the size of the scrollbar. | - -## Appearance - -| Setting | Description | -| :------------------------- | :--------------------------------------------------------- | -| **Editor Font** | Choose a font for the editor. | -| **Font Size** | Adjust the editor font size. | -| **Line Height** | Set line spacing between lines. | -| **Cursor Controller Size** | Size of the teardrop cursor controller. | -| **Color Preview** | Shows preview of colors in editor as a chip or square. | -| **Show Line Numbers** | Display line numbers in the gutter. | -| **Relative Line Numbers** | Show line numbers relative to the current cursor position. | -| **Show Spaces** | Display whitespace characters (spaces, tabs). | -| **Show Print Margin** | Display a vertical line at a specified column. | -| **Print Margin** | Set the column position for the print margin. | -| **Fade Fold Widgets** | Fade fold widgets when not needed. | - -## Editing Behavior - -| Setting | Description | -| :--------------------------- | :--------------------------------------------------------------- | -| **Autosave** | Automatically save files after changes. | -| **Format on Save** | Automatically format code when the file is saved. | -| **Live Autocompletion** | Enable real-time code suggestions as you type. | -| **Elastic Tabstops** | Enable elastic tabstop behavior for alignment. | -| **Soft Tab** | Use spaces instead of tab characters. | -| **Tab Size** | Number of spaces per tab. | -| **Text Wrap / Word Wrap** | Enable or disable word wrapping. | -| **Hard Wrap** | Hard wrap lines at a specified column. | -| **Line Based RTL Switching** | Enable right-to-left text handling per line. | -| **Use Textarea for IME** | Use textarea for Input Method Editor (better CJK input support). | +Customize the CodeMirror editor used by Acode. + +## Editor Options + +| Setting | Description | Allowed Values / Notes | Default | +| :------ | :---------- | :--------------------- | :------ | +| **Autosave** | Auto-save after delay. | `0` (off) or `>= 1000` ms | `0` | +| **Font Size** | Editor text size. | CSS size string (e.g. `12px`) | `12px` | +| **Editor Font** | Font family used in editor. | Any installed editor font | `Roboto Mono` | +| **Line Height** | Line spacing in editor. | `1.0` to `2.0` | `2` | +| **Soft Tab** | Insert spaces instead of tab character. | On/Off | `On` | +| **Tab Size** | Visual/indent tab width. | `1` to `8` | `2` | +| **Show Line Numbers** | Show line number gutter. | On/Off | `On` | +| **Relative Line Numbers** | Show relative numbers from cursor line. | On/Off | `Off` | +| **Lint Gutter** | Show diagnostics markers in gutter (LSP-related). | On/Off | `On` | +| **Format on Save** | Runs selected formatter on save. | On/Off | `Off` | +| **Live Autocompletion** | Suggest completions while typing. | On/Off | `On` | +| **Text Wrap** | Wrap long lines in editor viewport. | On/Off | `On` | +| **Show Spaces** | Show spaces/tabs/trailing whitespace markers. | On/Off | `Off` | +| **Fade Fold Widgets** | Folds gutter icons fade until hover. | On/Off | `Off` | +| **Rainbow Brackets** | Color bracket pairs with nested colors. | On/Off | `On` | +| **Indent Guides** | Show indentation guide lines. | On/Off | `On` | +| **Color Preview** | Inline color swatches for color values. | On/Off | `On` | +| **Cursor Controller Size** | Touch cursor handle size. | `None`, `Small`, `Medium`, `Large` | `Medium` | +| **Line Based RTL Switching** | Per-line right-to-left direction support. | On/Off | `Off` | +| **Hard Wrap** | Legacy option in settings UI. | Currently limited/no-op in CodeMirror flow | `Off` | + +## Scroll Settings (Sub-page) + +The **Scroll Settings** item opens a sub-page. + +| Setting | Description | Values | Default | +| :------ | :---------- | :----- | :------ | +| **Scrollbar Size** | Width/height of editor scrollbars. | `5`, `10`, `15`, `20` px | `20` | + +## Notes + +- Some old Ace-era options (print margin, elastic tabstops, textarea-for-IME) are no longer active in current CodeMirror editor settings flow. +- **Lint Gutter** visibility is tied to diagnostics UI from active language servers (LSP).