diff --git a/app/MindWork AI Studio/Components/CodeEditor.razor b/app/MindWork AI Studio/Components/CodeEditor.razor
index 9dc1692d..8863142d 100644
--- a/app/MindWork AI Studio/Components/CodeEditor.razor
+++ b/app/MindWork AI Studio/Components/CodeEditor.razor
@@ -1 +1,4 @@
-
+
diff --git a/app/MindWork AI Studio/Components/CodeEditor.razor.cs b/app/MindWork AI Studio/Components/CodeEditor.razor.cs
index ac7e019a..f6fceff0 100644
--- a/app/MindWork AI Studio/Components/CodeEditor.razor.cs
+++ b/app/MindWork AI Studio/Components/CodeEditor.razor.cs
@@ -23,8 +23,9 @@ public partial class CodeEditor : ComponentBase, IAsyncDisposable
public string Class { get; set; } = string.Empty;
private readonly string editorId = $"code-editor-{Guid.NewGuid():N}";
- private const string CODE_EDITOR_MODULE = "./system/CodeEditor/code-editor.js?v=20260707-3";
+ private const string CODE_EDITOR_MODULE = "./system/CodeEditor/code-editor.js?v=20260708-1";
private ElementReference editorElement;
+ private ElementReference lineNumbersElement;
private IJSObjectReference? module;
private string CodeEditorThemeStyle => this.GetCodeEditorThemeStyle();
@@ -34,7 +35,7 @@ public partial class CodeEditor : ComponentBase, IAsyncDisposable
return;
this.module = await this.JsRuntime.InvokeAsync("import", CODE_EDITOR_MODULE);
- await this.module.InvokeVoidAsync("init", this.editorId, this.editorElement, this.Value, this.Language.ToString());
+ await this.module.InvokeVoidAsync("init", this.editorId, this.editorElement, this.lineNumbersElement, this.Value, this.Language.ToString());
}
public async ValueTask GetCodeAsync()
diff --git a/app/MindWork AI Studio/wwwroot/app.css b/app/MindWork AI Studio/wwwroot/app.css
index aa53a34b..3a6a646a 100644
--- a/app/MindWork AI Studio/wwwroot/app.css
+++ b/app/MindWork AI Studio/wwwroot/app.css
@@ -204,21 +204,45 @@
}
.code-editor {
+ display: grid;
+ grid-template-columns: minmax(2.8rem, auto) minmax(0, 1fr);
min-height: 32rem;
height: 62vh;
width: 100%;
- overflow: auto;
- padding: 0.8rem;
+ overflow: hidden;
border: 1px solid var(--mw-code-editor-border, rgba(0,0,0,0.11764705882352941));
border-radius: 4px;
background: var(--mw-code-editor-background, rgba(255,255,255,1));
color: var(--mw-code-editor-foreground, rgba(66,66,66,1));
- caret-color: var(--mw-code-editor-foreground, rgba(66,66,66,1));
font-family: "JetBrains Mono", monospace;
font-size: 0.65rem;
line-height: 1.45;
- white-space: pre;
tab-size: 4;
+}
+
+.code-editor-line-numbers {
+ overflow: hidden;
+ padding: 0.8rem 0.65rem 0.8rem 0.5rem;
+ border-right: 1px solid var(--mw-code-editor-border, rgba(0,0,0,0.11764705882352941));
+ color: var(--mw-code-editor-foreground, rgba(66,66,66,1));
+ opacity: 0.55;
+ text-align: right;
+ white-space: pre;
+ user-select: none;
+ font: inherit;
+ font-variant-numeric: tabular-nums;
+}
+
+.code-editor-input {
+ min-width: 0;
+ height: 100%;
+ overflow: auto;
+ padding: 0.8rem;
+ color: var(--mw-code-editor-foreground, rgba(66,66,66,1));
+ caret-color: var(--mw-code-editor-foreground, rgba(66,66,66,1));
+ font: inherit;
+ line-height: inherit;
+ tab-size: inherit;
outline: none;
}
diff --git a/app/MindWork AI Studio/wwwroot/system/CodeEditor/code-editor.js b/app/MindWork AI Studio/wwwroot/system/CodeEditor/code-editor.js
index fdbcd8be..cb5397c6 100644
--- a/app/MindWork AI Studio/wwwroot/system/CodeEditor/code-editor.js
+++ b/app/MindWork AI Studio/wwwroot/system/CodeEditor/code-editor.js
@@ -27,21 +27,25 @@ const LUA_BUILT_INS = new Set([
* The highlighter callback receives the editor DOM node. It must write highlighted
* HTML back into that node, so every token emitted by our highlighter is HTML-escaped.
*/
-export function init(id, element, code, language) {
+export function init(id, element, lineNumbersElement, code, language) {
const codeJar = CodeJar(element, getHighlighter(language), {
tab: ' ',
spellcheck: false
});
+ const scrollHandler = () => syncLineNumbersScroll(element, lineNumbersElement);
codeJar.updateCode(code ?? '');
- editors.set(id, codeJar);
+ updateLineNumbers(lineNumbersElement, codeJar.toString());
+ codeJar.onUpdate(updatedCode => updateLineNumbers(lineNumbersElement, updatedCode));
+ element.addEventListener('scroll', scrollHandler);
+ editors.set(id, { codeJar, element, scrollHandler });
}
/**
* Returns the current plain text from a CodeJar instance.
*/
export function getCode(id) {
- return editors.get(id)?.toString() ?? '';
+ return editors.get(id)?.codeJar.toString() ?? '';
}
/**
@@ -49,17 +53,43 @@ export function getCode(id) {
* state stays consistent with CodeJar's internal model.
*/
export function setCode(id, code) {
- editors.get(id)?.updateCode(code ?? '');
+ const editor = editors.get(id);
+ if (!editor)
+ return;
+
+ editor.codeJar.updateCode(code ?? '');
}
/**
* Disposes one editor instance and removes it from the JS-side registry.
*/
export function destroy(id) {
- editors.get(id)?.destroy();
+ const editor = editors.get(id);
+ if (!editor)
+ return;
+
+ editor.element.removeEventListener('scroll', editor.scrollHandler);
+ editor.codeJar.destroy();
editors.delete(id);
}
+function updateLineNumbers(lineNumbersElement, code) {
+ const lineCount = (code.match(/\n/g)?.length ?? 0) + 1;
+ let lineNumbers = '';
+ for (let lineNumber = 1; lineNumber <= lineCount; lineNumber++) {
+ if (lineNumber > 1)
+ lineNumbers += '\n';
+
+ lineNumbers += lineNumber;
+ }
+
+ lineNumbersElement.textContent = lineNumbers;
+}
+
+function syncLineNumbersScroll(editorElement, lineNumbersElement) {
+ lineNumbersElement.scrollTop = editorElement.scrollTop;
+}
+
function highlightLua(editor) {
editor.innerHTML = highlightLuaCode(editor.textContent ?? '');
}