# Code Editor

Monaco-based code editor with syntax highlighting, multi-tab editing, command palette, JSON schema validation, and full programmatic control.

## Live Demo

<iframe id="ce-demo" src="/online/webapp/code-editor" width="100%" height="450" frameborder="0" style="border:1px solid #ccc; border-radius:4px;"></iframe>

<script>window._wsConnect('ce-demo', 'ceSocket');</script>

## Embed

```html
<iframe src="https://sgapps.io/online/webapp/code-editor"
    width="100%" height="600" frameborder="0" allow="clipboard-write"></iframe>
```

---

## Events Reference

---

### `active-file:codeEditor:value` -- Get or Set Editor Content

The most commonly used event. When called with a string, it sets the editor text. When called with only a callback, it returns the current text.

| Arg | Type | Description |
|-----|------|-------------|
| `value` | string (optional) | New content to set |
| `callback` | function | `(value: string)` when getting, `()` when setting |

```js
// Set content
socket.fire("webapp::instance::request",
    "active-file:codeEditor:value",
    "function hello() {\n    return 'world';\n}",
    function () { console.log("Content set"); }
);

// Get content
socket.fire("webapp::instance::request",
    "active-file:codeEditor:value",
    function (value) { console.log("Content:", value); }
);
```

<button onclick="window._ws('ceSocket')&&window.ceSocket.fire('webapp::instance::request','active-file:codeEditor:value','// Hello from the docs page!\nfunction greet(name) {\n    return \'Hello, \' + name + \'!\';\n}\n\nconsole.log(greet(\'World\'));')">Try: Set JS Code</button>
<button onclick="window._ws('ceSocket')&&window.ceSocket.fire('webapp::instance::request','active-file:codeEditor:value',function(v){alert('Length: '+v.length+' chars\n\nFirst 200 chars:\n'+v.substring(0,200))})">Try: Get Code</button>

---

### `active-file:codeEditor:syntax` -- Get or Set Language Mode

Changes the syntax highlighting language. Uses Monaco's language identifiers: `javascript`, `typescript`, `html`, `css`, `json`, `python`, `markdown`, `xml`, `sql`, `yaml`, `go`, `rust`, `cpp`, etc.

| Arg | Type | Description |
|-----|------|-------------|
| `syntax` | string (optional) | Language ID to set |
| `callback` | function | `(err, currentLanguage: string)` |

```js
// Set to Python
socket.fire("webapp::instance::request", "active-file:codeEditor:syntax", "python");

// Get current language
socket.fire("webapp::instance::request", "active-file:codeEditor:syntax",
    function (err, lang) { console.log("Language:", lang); }
);
```

<button onclick="window._ws('ceSocket')&&window.ceSocket.fire('webapp::instance::request','active-file:codeEditor:syntax','javascript')">Try: JavaScript</button>
<button onclick="window._ws('ceSocket')&&window.ceSocket.fire('webapp::instance::request','active-file:codeEditor:syntax','python')">Try: Python</button>
<button onclick="window._ws('ceSocket')&&window.ceSocket.fire('webapp::instance::request','active-file:codeEditor:syntax','html')">Try: HTML</button>

---

### `active-file:codeEditor:options` -- Set Monaco Editor Options

Configures the Monaco editor instance. Any [Monaco IEditorOptions](https://microsoft.github.io/monaco-editor/docs.html#interfaces/editor.IEditorOptions.html) can be passed.

| Arg | Type | Description |
|-----|------|-------------|
| `options` | object | Monaco editor options |
| `callback` | function | `(err)` |

```js
// Make read-only with large font
socket.fire("webapp::instance::request", "active-file:codeEditor:options", {
    readOnly: true,
    fontSize: 18,
    wordWrap: "on",
    minimap: { enabled: false }
});

// Restore editable
socket.fire("webapp::instance::request", "active-file:codeEditor:options", {
    readOnly: false,
    fontSize: 14
});
```

<button onclick="window._ws('ceSocket')&&window.ceSocket.fire('webapp::instance::request','active-file:codeEditor:options',{readOnly:true,fontSize:18})">Try: Read-Only + Large Font</button>
<button onclick="window._ws('ceSocket')&&window.ceSocket.fire('webapp::instance::request','active-file:codeEditor:options',{readOnly:false,fontSize:14})">Try: Restore Editable</button>

---

### `active-file:codeEditor:selection` -- Get Selected Text

Returns the current text selection with line/column positions.

```js
socket.fire("webapp::instance::request", "active-file:codeEditor:selection",
    function (err, sel) {
        console.log("Selected:", sel);
        // { startLine: 3, startCol: 5, endLine: 3, endCol: 20, text: "selected text" }
    }
);
```

<button onclick="window._ws('ceSocket')&&window.ceSocket.fire('webapp::instance::request','active-file:codeEditor:selection',function(e,s){alert(s.text?('Selected: \"'+s.text+'\" at line '+s.startLine):'No selection')})">Try: Get Selection</button>

---

### `active-file:codeEditor:insertText` -- Insert Text

Inserts text at a specific position or at the current cursor position. If a `position` object is provided, text is inserted at that line/column. Otherwise, it replaces the current selection.

| Arg | Type | Description |
|-----|------|-------------|
| `text` | string | Text to insert |
| `position` | object (optional) | `{ line: number, col: number }` |
| `callback` | function | `(err)` |

```js
// Insert at line 1, column 1
socket.fire("webapp::instance::request", "active-file:codeEditor:insertText",
    "// Auto-generated header\n",
    { line: 1, col: 1 }
);

// Insert at current cursor position
socket.fire("webapp::instance::request", "active-file:codeEditor:insertText",
    "console.log('inserted');"
);
```

<button onclick="window._ws('ceSocket')&&window.ceSocket.fire('webapp::instance::request','active-file:codeEditor:insertText','// Inserted from docs!\n',{line:1,col:1})">Try: Insert at Top</button>

---

### `active-file:codeEditor:revealLine` -- Scroll to Line

Scrolls the editor to center a specific line number in the viewport. Useful when navigating to errors or search results.

| Arg | Type | Description |
|-----|------|-------------|
| `lineNumber` | number | Line to reveal |
| `callback` | function | `(err)` |

```js
socket.fire("webapp::instance::request", "active-file:codeEditor:revealLine", 42);
```

---

### `active-file:codeEditor:json:schema` -- JSON Schema Validation

Sets a JSON Schema for real-time validation of the active editor content. Monaco will show errors/warnings inline when the content doesn't match the schema.

| Arg | Type | Description |
|-----|------|-------------|
| `schema` | object | JSON Schema object |
| `callback` | function | `(err)` |

```js
socket.fire("webapp::instance::request", "active-file:codeEditor:json:schema", {
    type: "object",
    properties: {
        name: { type: "string" },
        version: { type: "string", pattern: "^\\d+\\.\\d+\\.\\d+$" }
    },
    required: ["name", "version"]
});
```

---

### `active-file:save` -- Save Active File

Triggers the save action on the currently active file tab. The file is written back to its original path in the file system.

```js
socket.fire("webapp::instance::request", "active-file:save", function (err) {
    console.log(err || "File saved");
});
```

---

### `active-file:markers` -- Set Error/Warning Markers

Programmatically set diagnostic markers (errors, warnings, info) on the editor. This is useful for showing linting results, build errors, or custom validation from your application.

| Arg | Type | Description |
|-----|------|-------------|
| `markers` | Array | Array of marker objects |
| `callback` | function | `(err)` |

Each marker: `{ startLine, startCol, endLine, endCol, message, severity }`

Severity levels: `1` = Hint, `2` = Info, `4` = Warning, `8` = Error

```js
socket.fire("webapp::instance::request", "active-file:markers", [
    { startLine: 3, startCol: 1, endLine: 3, endCol: 30,
      message: "Unused variable 'x'", severity: 4 },
    { startLine: 7, startCol: 5, endLine: 7, endCol: 15,
      message: "Syntax error: unexpected token", severity: 8 }
]);
```

<button onclick="window._ws('ceSocket')&&window.ceSocket.fire('webapp::instance::request','active-file:markers',[{startLine:1,startCol:1,endLine:1,endCol:50,message:'This is a warning from the docs page',severity:4},{startLine:3,startCol:1,endLine:3,endCol:30,message:'This is an error marker',severity:8}])">Try: Set Markers</button>
<button onclick="window._ws('ceSocket')&&window.ceSocket.fire('webapp::instance::request','active-file:markers',[])">Try: Clear Markers</button>

---

### `files:open` -- Open a File in a New Tab

Opens a file in a new editor tab. The `type` field determines the renderer: `"file"` for text editor, `"markdown"` for rendered markdown, `"file-image"` for image viewer.

| Arg | Type | Description |
|-----|------|-------------|
| `fileConfig` | object | `{ path, type?, title? }` |
| `callback` | function | `(err)` |

```js
socket.fire("webapp::instance::request", "files:open", {
    path: "web:///api/applications.json",
    type: "file",
    title: "applications.json"
});
```

---

### `files:list` -- List Open Tabs

Returns an array of all currently open tabs with their index, name, path, type, and title.

```js
socket.fire("webapp::instance::request", "files:list", function (err, tabs) {
    console.log(tabs);
    // [{ index: 0, name: "index.js", path: "/home/user/index.js", type: "file", title: "index.js" }]
});
```

<button onclick="window._ws('ceSocket')&&window.ceSocket.fire('webapp::instance::request','files:list',function(e,t){alert('Open tabs: '+t.length+'\n'+t.map(function(x){return x.index+': '+x.title}).join('\n'))})">Try: List Tabs</button>

---

### `files:active:set` -- Activate a Tab

Switches to a specific tab by its index (as returned by `files:list`).

| Arg | Type | Description |
|-----|------|-------------|
| `tabIndex` | number | Zero-based tab index |
| `callback` | function | `(err)` |

```js
socket.fire("webapp::instance::request", "files:active:set", 0);
```

---

### `files:close:all` -- Close All Tabs

Closes all open editor tabs.

```js
socket.fire("webapp::instance::request", "files:close:all");
```

---

### `gui:sidebar:toggle` -- Show/Hide Sidebar

Toggle the file tree sidebar visibility.

| Arg | Type | Description |
|-----|------|-------------|
| `visible` | boolean | `true` to show, `false` to hide |

```js
socket.fire("webapp::instance::request", "gui:sidebar:toggle", false); // hide
socket.fire("webapp::instance::request", "gui:sidebar:toggle", true);  // show
```

<button onclick="window._ws('ceSocket')&&window.ceSocket.fire('webapp::instance::request','gui:sidebar:toggle',false)">Try: Hide Sidebar</button>
<button onclick="window._ws('ceSocket')&&window.ceSocket.fire('webapp::instance::request','gui:sidebar:toggle',true)">Try: Show Sidebar</button>

---

## Complete Example

```html
<!DOCTYPE html>
<html>
<head><title>Embedded Code Editor</title></head>
<body>
    <div style="display:flex;gap:8px;padding:10px;flex-wrap:wrap;">
        <button id="set-code">Load Sample</button>
        <button id="get-code">Read Code</button>
        <button id="set-python">Python Mode</button>
        <button id="readonly">Read-Only</button>
        <button id="add-markers">Show Errors</button>
    </div>

    <iframe id="editor" src="https://sgapps.io/online/webapp/code-editor"
        width="100%" height="500" frameborder="0" allow="clipboard-write"></iframe>

    <pre id="output" style="background:#f6f8fa;padding:10px;max-height:200px;overflow:auto;"></pre>

    <script src="https://sgapps.io/components/window-socket/index.js"></script>
    <script>
        var socket = new WindowSocket(document.getElementById('editor').contentWindow);
        socket.start();

        socket.on("webapp::connection::ping", function () {
            socket.fire("webapp::instance::embed-mode", true);
        });

        document.getElementById('set-code').onclick = function () {
            socket.fire("webapp::instance::request", "active-file:codeEditor:value",
                "def fibonacci(n):\n    a, b = 0, 1\n    for _ in range(n):\n        a, b = b, a + b\n    return a\n\nprint(fibonacci(10))");
            socket.fire("webapp::instance::request", "active-file:codeEditor:syntax", "python");
        };

        document.getElementById('get-code').onclick = function () {
            socket.fire("webapp::instance::request", "active-file:codeEditor:value",
                function (v) { document.getElementById('output').textContent = v; });
        };

        document.getElementById('set-python').onclick = function () {
            socket.fire("webapp::instance::request", "active-file:codeEditor:syntax", "python");
        };

        document.getElementById('readonly').onclick = function () {
            socket.fire("webapp::instance::request", "active-file:codeEditor:options", { readOnly: true });
        };

        document.getElementById('add-markers').onclick = function () {
            socket.fire("webapp::instance::request", "active-file:markers", [
                { startLine: 2, startCol: 1, endLine: 2, endCol: 20, message: "Consider using a list comprehension", severity: 4 }
            ]);
        };
    </script>
</body>
</html>
```
