From 16a22fbcdfe5fc580b211efaceeb6eb8737d3ba6 Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Tue, 20 Oct 2026 15:11:15 +0530 Subject: [PATCH 01/14] build: link @acode/canvas-view from a local checkout Resolve canvas-view's imports (and their types) from Acode's own node_modules, so CodeMirror's state and view stay single instances. --- bun.lock | 3 +++ package.json | 1 + rspack.config.js | 17 ++++++++++++++++- tsconfig.json | 6 ++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/bun.lock b/bun.lock index 5f2c0d5655..11ccb7ae77 100644 --- a/bun.lock +++ b/bun.lock @@ -5,6 +5,7 @@ "": { "name": "com.foxdebug.acode", "dependencies": { + "@acode/canvas-view": "link:@acode/canvas-view", "@codemirror/autocomplete": "^6.20.3", "@codemirror/commands": "^6.11.0", "@codemirror/lang-angular": "^0.1.4", @@ -159,6 +160,8 @@ "tar": "^7.5.22", }, "packages": { + "@acode/canvas-view": ["@acode/canvas-view@link:@acode/canvas-view", {}], + "@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="], "@babel/code-frame": ["@babel/code-frame@8.0.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^8.0.0", "js-tokens": "^10.0.0" } }, "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw=="], diff --git a/package.json b/package.json index aebe3e1725..5aa69608d0 100644 --- a/package.json +++ b/package.json @@ -125,6 +125,7 @@ "ws": "^8.21.3" }, "dependencies": { + "@acode/canvas-view": "link:@acode/canvas-view", "@codemirror/autocomplete": "^6.20.3", "@codemirror/commands": "^6.11.0", "@codemirror/lang-angular": "^0.1.4", diff --git a/rspack.config.js b/rspack.config.js index c811c6b778..dcfca0aa1d 100644 --- a/rspack.config.js +++ b/rspack.config.js @@ -12,7 +12,19 @@ module.exports = (env, options) => { ? ''.concat(devProto, '://', devHost, ':', devPort) : ''; + // @acode/canvas-view is linked from a local checkout (`bun link`), so its + // files resolve to their real path outside node_modules. It ships pre-built + // code, and its imports must resolve from Acode's node_modules: a second + // copy of @codemirror/state or view (the checkout's own) breaks facets. + const canvasViewDist = /[\\/]canvas-view[\\/]dist[\\/]/; + const rules = [ + { + test: canvasViewDist, + resolve: { + modules: [path.resolve(__dirname, 'node_modules')], + }, + }, { test: /typescript[\\/]lib[\\/]lib\..*\.d\.ts$/, type: 'asset/source', @@ -46,7 +58,10 @@ module.exports = (env, options) => { // dependency, so it resolves to its real path outside node_modules. It // still ships pre-built code and must not go through the source loaders. { - include: /[\\/](?:node_modules|codemirror-lsp-client)[\\/]/, + include: [ + /[\\/](?:node_modules|codemirror-lsp-client)[\\/]/, + canvasViewDist, + ], use: [ { loader: 'builtin:swc-loader', diff --git a/tsconfig.json b/tsconfig.json index 1a07510751..ce52141963 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,13 @@ "declaration": false, "allowJs": true, "checkJs": false, + // Linked @acode/canvas-view must see Acode's CodeMirror types, not its checkout's. "paths": { + "@codemirror/language": ["./node_modules/@codemirror/language"], + "@codemirror/state": ["./node_modules/@codemirror/state"], + "@codemirror/view": ["./node_modules/@codemirror/view"], + "@lezer/common": ["./node_modules/@lezer/common"], + "@lezer/highlight": ["./node_modules/@lezer/highlight"], "*": ["./src/*"] }, "jsx": "preserve", From a131d86572272bd7d8601c8d172f7cfde166f04f Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Tue, 20 Oct 2026 15:11:15 +0530 Subject: [PATCH 02/14] feat(settings): add editor renderer setting Renderer: DOM (default) or Canvas (experimental), applied on reload. --- src/lang/ar-ye.json | 6 +++++- src/lang/az-az.json | 6 +++++- src/lang/be-by.json | 6 +++++- src/lang/bn-bd.json | 6 +++++- src/lang/cs-cz.json | 6 +++++- src/lang/de-de.json | 6 +++++- src/lang/en-us.json | 6 +++++- src/lang/es-sv.json | 6 +++++- src/lang/fr-fr.json | 6 +++++- src/lang/he-il.json | 6 +++++- src/lang/hi-in.json | 6 +++++- src/lang/hu-hu.json | 6 +++++- src/lang/id-id.json | 6 +++++- src/lang/index.d.ts | 4 ++++ src/lang/ir-fa.json | 6 +++++- src/lang/it-it.json | 6 +++++- src/lang/ja-jp.json | 6 +++++- src/lang/ko-kr.json | 6 +++++- src/lang/ln-ln.json | 6 +++++- src/lang/ml-in.json | 6 +++++- src/lang/mm-unicode.json | 6 +++++- src/lang/mm-zawgyi.json | 6 +++++- src/lang/pl-pl.json | 6 +++++- src/lang/pt-br.json | 6 +++++- src/lang/pu-in.json | 6 +++++- src/lang/ru-ru.json | 6 +++++- src/lang/tl-ph.json | 6 +++++- src/lang/tr-tr.json | 6 +++++- src/lang/uk-ua.json | 6 +++++- src/lang/uz-uz.json | 6 +++++- src/lang/vi-vn.json | 6 +++++- src/lang/zh-cn.json | 6 +++++- src/lang/zh-hant.json | 6 +++++- src/lang/zh-tw.json | 6 +++++- src/lib/settings.js | 1 + src/settings/editorSettings.js | 27 ++++++++++++++++++++++++++- 36 files changed, 196 insertions(+), 34 deletions(-) diff --git a/src/lang/ar-ye.json b/src/lang/ar-ye.json index 67b21b3d7c..f6350c794b 100644 --- a/src/lang/ar-ye.json +++ b/src/lang/ar-ye.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/az-az.json b/src/lang/az-az.json index 6285bb588a..e88755c291 100644 --- a/src/lang/az-az.json +++ b/src/lang/az-az.json @@ -887,5 +887,9 @@ "plugin-review:error-max-length": "Rəy ən çox {max} simvoldan ibarət olmalıdır.", "plugin-review:error-downvote-comment": "Nəyin yaxşılaşdırıla biləcəyini tərtibatçıya bildir.", "plugin-review:error-empty": "Şərh əlavə et və ya «Bəyən» seçimini et.", - "plugin-review:unrated": "Qiymətləndirilməyib" + "plugin-review:unrated": "Qiymətləndirilməyib", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/be-by.json b/src/lang/be-by.json index 98601603c2..c9d1855cce 100644 --- a/src/lang/be-by.json +++ b/src/lang/be-by.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/bn-bd.json b/src/lang/bn-bd.json index c439ccecdc..54a7cfec95 100644 --- a/src/lang/bn-bd.json +++ b/src/lang/bn-bd.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/cs-cz.json b/src/lang/cs-cz.json index f33b46844d..dcf4fa256d 100644 --- a/src/lang/cs-cz.json +++ b/src/lang/cs-cz.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/de-de.json b/src/lang/de-de.json index c19f4e6f56..536591735d 100644 --- a/src/lang/de-de.json +++ b/src/lang/de-de.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/en-us.json b/src/lang/en-us.json index 2869c6c2c2..df03ef2b35 100644 --- a/src/lang/en-us.json +++ b/src/lang/en-us.json @@ -887,5 +887,9 @@ "plugin-review:error-max-length": "Review must be {max} characters or less.", "plugin-review:error-downvote-comment": "Tell the developer what could be improved.", "plugin-review:error-empty": "Add a comment or choose thumbs up.", - "plugin-review:unrated": "Unrated" + "plugin-review:unrated": "Unrated", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/es-sv.json b/src/lang/es-sv.json index a65e2b8622..3231bdf8f0 100644 --- a/src/lang/es-sv.json +++ b/src/lang/es-sv.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Elige si las líneas ajustadas comienzan desde el borde izquierdo, mantienen la indentación de la línea original o se indentan más. Cada nivel extra usa el tamaño del tabulador.", "icon pack": "Paquete de Íconos", "settings-info-icon-pack": "Elige el paquete de iconos usado para archivos y carpetas en el explorador y listas de archivos en la aplicación.", - "unavailable": "no disponible" + "unavailable": "no disponible", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/fr-fr.json b/src/lang/fr-fr.json index 5fc53c6f1d..65936f84e5 100644 --- a/src/lang/fr-fr.json +++ b/src/lang/fr-fr.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/he-il.json b/src/lang/he-il.json index 358b04abc6..9d4112947d 100644 --- a/src/lang/he-il.json +++ b/src/lang/he-il.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/hi-in.json b/src/lang/hi-in.json index 6aa97f71a1..50c46a9a37 100644 --- a/src/lang/hi-in.json +++ b/src/lang/hi-in.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/hu-hu.json b/src/lang/hu-hu.json index f2865688a2..2dd71eab6e 100644 --- a/src/lang/hu-hu.json +++ b/src/lang/hu-hu.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Válassza ki, hogy a tördelt szövegek a képernyő bal szélétől kezdődjenek-e, az eredeti sor behúzását kövessék-e, vagy még jobban legyenek-e behúzva. Minden további behúzási szint a tabulátor méretét veszi alapul.", "icon pack": "Ikoncsomag", "settings-info-icon-pack": "Válassza ki a fájlokhoz és a mappákhoz használandó ikonokat a fájlkezelőben és az alkalmazás egészében megjelenő fájllistákban.", - "unavailable": "nem érhető el" + "unavailable": "nem érhető el", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/id-id.json b/src/lang/id-id.json index e21a315901..5f0478e909 100644 --- a/src/lang/id-id.json +++ b/src/lang/id-id.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Pilih apakah teks terbungkus dimulai dari tepi kiri, mengikuti indentasi baris asli, atau di-indentasi lebih jauh. Setiap level tambahan menggunakan ukuran tab Anda.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/index.d.ts b/src/lang/index.d.ts index 8434189934..2305bfac42 100644 --- a/src/lang/index.d.ts +++ b/src/lang/index.d.ts @@ -891,6 +891,10 @@ declare type LangStrings = { "plugin-review:error-downvote-comment": string; "plugin-review:error-empty": string; "plugin-review:unrated": string; + "editor renderer": string; + "editor renderer dom": string; + "editor renderer canvas": string; + "settings-info-editor-renderer": string; }; declare var strings: LangStrings; diff --git a/src/lang/ir-fa.json b/src/lang/ir-fa.json index 07f6c056bc..433498444f 100644 --- a/src/lang/ir-fa.json +++ b/src/lang/ir-fa.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/it-it.json b/src/lang/it-it.json index efe7c90e32..a3e145108f 100644 --- a/src/lang/it-it.json +++ b/src/lang/it-it.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/ja-jp.json b/src/lang/ja-jp.json index 27fd7b6033..0af76a7656 100644 --- a/src/lang/ja-jp.json +++ b/src/lang/ja-jp.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/ko-kr.json b/src/lang/ko-kr.json index f331beb331..a2f45a160f 100644 --- a/src/lang/ko-kr.json +++ b/src/lang/ko-kr.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/ln-ln.json b/src/lang/ln-ln.json index 63d4e9ce7e..17a11020c5 100644 --- a/src/lang/ln-ln.json +++ b/src/lang/ln-ln.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/ml-in.json b/src/lang/ml-in.json index 04db791635..950d81407a 100644 --- a/src/lang/ml-in.json +++ b/src/lang/ml-in.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/mm-unicode.json b/src/lang/mm-unicode.json index 6411d5b9b1..b33147c437 100644 --- a/src/lang/mm-unicode.json +++ b/src/lang/mm-unicode.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/mm-zawgyi.json b/src/lang/mm-zawgyi.json index dd8f456bf2..08e5c3220c 100644 --- a/src/lang/mm-zawgyi.json +++ b/src/lang/mm-zawgyi.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/pl-pl.json b/src/lang/pl-pl.json index d5b418a9e0..17701a5981 100644 --- a/src/lang/pl-pl.json +++ b/src/lang/pl-pl.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/pt-br.json b/src/lang/pt-br.json index aa6de4b0e2..c8b254054e 100644 --- a/src/lang/pt-br.json +++ b/src/lang/pt-br.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/pu-in.json b/src/lang/pu-in.json index 05b389b8c7..0edcc860fe 100644 --- a/src/lang/pu-in.json +++ b/src/lang/pu-in.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/ru-ru.json b/src/lang/ru-ru.json index 79e83dddba..15b2eac0bf 100644 --- a/src/lang/ru-ru.json +++ b/src/lang/ru-ru.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/tl-ph.json b/src/lang/tl-ph.json index b5a5424fca..2cdf3e1624 100644 --- a/src/lang/tl-ph.json +++ b/src/lang/tl-ph.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/tr-tr.json b/src/lang/tr-tr.json index 0d723b7ffd..3265917f4c 100644 --- a/src/lang/tr-tr.json +++ b/src/lang/tr-tr.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/uk-ua.json b/src/lang/uk-ua.json index ab5da72106..eb89ac7897 100644 --- a/src/lang/uk-ua.json +++ b/src/lang/uk-ua.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/uz-uz.json b/src/lang/uz-uz.json index 986916b7e8..fab0acf10e 100644 --- a/src/lang/uz-uz.json +++ b/src/lang/uz-uz.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/vi-vn.json b/src/lang/vi-vn.json index 64441eb461..08f8757177 100644 --- a/src/lang/vi-vn.json +++ b/src/lang/vi-vn.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/zh-cn.json b/src/lang/zh-cn.json index d6c1737b6d..b94eefb281 100644 --- a/src/lang/zh-cn.json +++ b/src/lang/zh-cn.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "选择折行后的文本是从行首靠左对齐、保持原行的缩进,还是进一步向内缩进。每增加一级缩进将遵循您设定的制表符宽度。", "icon pack": "图标包", "settings-info-icon-pack": "选择资源管理器及整个应用文件列表中文件和文件夹所使用的图标。", - "unavailable": "不可用" + "unavailable": "不可用", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/zh-hant.json b/src/lang/zh-hant.json index 6ad71367e6..defd18ef09 100644 --- a/src/lang/zh-hant.json +++ b/src/lang/zh-hant.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "選擇換行後的文字是靠左對齊、保持原行的縮排,還是進一步向內縮排。每增加一級縮排將遵循您設定的製表符寬度。", "icon pack": "圖標包", "settings-info-icon-pack": "選擇檔案總管及整個應用程式檔案列表中檔案和文件夾所使用的圖標。", - "unavailable": "不可用" + "unavailable": "不可用", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lang/zh-tw.json b/src/lang/zh-tw.json index c814718a27..3d62702441 100644 --- a/src/lang/zh-tw.json +++ b/src/lang/zh-tw.json @@ -887,5 +887,9 @@ "settings-info-editor-wrapping-indent": "Choose whether wrapped text starts at the left edge, matches the original line's indentation, or is indented further. Each extra level uses your tab size.", "icon pack": "Icon pack", "settings-info-icon-pack": "Choose the icons used for files and folders in the explorer and file lists across the app.", - "unavailable": "unavailable" + "unavailable": "unavailable", + "editor renderer": "Renderer", + "editor renderer dom": "DOM (default)", + "editor renderer canvas": "Canvas (experimental)", + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." } diff --git a/src/lib/settings.js b/src/lib/settings.js index de8d3b18bf..0423bb0712 100644 --- a/src/lib/settings.js +++ b/src/lib/settings.js @@ -133,6 +133,7 @@ class Settings { fontSize: "12px", cursorWidth: 2, editorTheme: "one_dark", + editorRenderer: "dom", textWrap: false, wrappingIndent: "same", softTab: true, diff --git a/src/settings/editorSettings.js b/src/settings/editorSettings.js index ec4c34b00a..e3b163b761 100644 --- a/src/settings/editorSettings.js +++ b/src/settings/editorSettings.js @@ -1,4 +1,5 @@ import settingsPage from "components/settingsPage"; +import confirm from "dialogs/confirm"; import config from "lib/config"; import fonts from "lib/fonts"; import appSettings from "lib/settings"; @@ -7,6 +8,10 @@ import scrollSettings from "./scrollSettings"; export default function editorSettings() { const title = strings["editor settings"]; const values = appSettings.value; + const rendererOptions = [ + ["dom", strings["editor renderer dom"]], + ["canvas", strings["editor renderer canvas"]], + ]; const wrappingIndentOptions = [ ["none", strings.none], ["same", strings["wrap-indent-same"]], @@ -29,6 +34,17 @@ export default function editorSettings() { category: categories.scrolling, chevron: true, }, + { + key: "editorRenderer", + text: strings["editor renderer"], + value: values.editorRenderer ?? "dom", + valueText: (value) => + rendererOptions.find(([key]) => key === value)?.[1] ?? + rendererOptions[0][1], + select: rendererOptions, + info: strings["settings-info-editor-renderer"], + category: categories.textLayout, + }, { key: "editorFont", text: strings["editor font"], @@ -269,12 +285,21 @@ export default function editorSettings() { * @param {string} key * @param {string} value */ - function callback(key, value) { + async function callback(key, value) { switch (key) { case "scroll-settings": appSettings.uiSettings[key].show(); return; + case "editorRenderer": + // Editors are created with their renderer: reload to switch. + if (value === appSettings.value.editorRenderer) return; + await appSettings.update({ [key]: value }); + if (await confirm(strings.info, strings["reload to apply"])) { + location.reload(); + } + return; + case "editorFont": fonts.setFont(value); From 28db8d97f34252d98e461a507b0fc166663016d3 Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Tue, 20 Oct 2026 15:11:15 +0530 Subject: [PATCH 03/14] feat(editor): add experimental canvas renderer With editorRenderer set to canvas, editors are @acode/canvas-view CanvasViews. Settings that the DOM editor applies through extensions (line numbers, folding, guides, rainbow brackets, whitespace, wrapping, scroll past end, RTL) become canvas options, kept in sync on changes. Themes map to a CanvasThemeSpec (config colors + the theme's HighlightStyle). The selection menu is driven by the view's touch selection hooks, reusing the existing menu UI. --- src/cm/canvasOptions.ts | 300 +++++++++++++++++++++++++++++++ src/cm/createEditorView.ts | 63 +++++++ src/cm/touchSelectionMenu.js | 98 +++++++++- src/lib/editorManager.js | 157 ++++++++++++++-- tests/unit/canvasOptions.test.ts | 139 ++++++++++++++ 5 files changed, 732 insertions(+), 25 deletions(-) create mode 100644 src/cm/canvasOptions.ts create mode 100644 src/cm/createEditorView.ts create mode 100644 tests/unit/canvasOptions.test.ts diff --git a/src/cm/canvasOptions.ts b/src/cm/canvasOptions.ts new file mode 100644 index 0000000000..0f904cba01 --- /dev/null +++ b/src/cm/canvasOptions.ts @@ -0,0 +1,300 @@ +import { + type CanvasThemeSpec, + CanvasView, + type CanvasViewConfig, + type FontSpec, + type TokenStyleSpec, +} from "@acode/canvas-view"; +import { HighlightStyle } from "@codemirror/language"; +import { oneDark } from "@codemirror/theme-one-dark"; +import { tags as t } from "@lezer/highlight"; +import { getRainbowBracketColors } from "cm/rainbowBrackets"; +import { getThemeById, getThemeConfig, getThemeExtensions } from "cm/themes"; + +/** + * Maps Acode's editor settings and themes to @acode/canvas-view options. + * + * The canvas renderer draws line numbers, fold markers, the active line, + * indent guides, rainbow brackets, whitespace, wrapping and scroll room + * itself, so editorManager leaves those CodeMirror extensions out of the + * state and passes the options built here instead. + */ + +export type CanvasDisplayOptions = Parameters[0]; + +type Settings = Record; + +interface ThemeConfig { + dark?: boolean; + background?: string; + foreground?: string; + selection?: string; + cursor?: string; + activeLine?: string; + lineNumber?: string; + lineNumberActive?: string; + matchingBracket?: string; + [color: string]: unknown; +} + +const SCROLL_PAST_END: Record = { + none: 0, + small: 0.25, + medium: 0.5, + full: 1, +}; + +const SCROLL_SPEED: Record = { + SLOW: "slow", + NORMAL: "normal", + FAST: "fast", + FAST_X2: "fastX2", +}; + +/** + * Find the HighlightStyle a theme passes to `syntaxHighlighting()`, by + * walking its extension tree (arrays, precedence wrappers, compartments, + * `facet.of(value)` providers). Returns null for themes that compute their + * highlighter (`themeType`) or don't have one. + */ +export function findHighlightStyle( + extension: unknown, + seen = new Set(), +): HighlightStyle | null { + if (!extension || typeof extension !== "object" || seen.has(extension)) { + return null; + } + seen.add(extension); + if (extension instanceof HighlightStyle) return extension; + if (Array.isArray(extension)) { + for (const item of extension) { + const found = findHighlightStyle(item, seen); + if (found) return found; + } + return null; + } + const node = extension as { + value?: unknown; + inner?: unknown; + extension?: unknown; + }; + return ( + findHighlightStyle(node.value, seen) || + findHighlightStyle(node.inner, seen) || + findHighlightStyle(node.extension, seen) + ); +} + +/** Token colors from a theme config, for themes without a HighlightStyle. */ +function highlightFromConfig(config: ThemeConfig): TokenStyleSpec[] { + const color = (key: string) => + typeof config[key] === "string" ? (config[key] as string) : undefined; + const specs: TokenStyleSpec[] = [ + { tag: t.variableName, color: color("variable") }, + { tag: [t.keyword, t.modifier], color: color("keyword") }, + { tag: [t.string, t.special(t.string)], color: color("string") }, + { tag: [t.number, t.bool, t.null], color: color("number") }, + { tag: t.comment, color: color("comment"), fontStyle: "italic" }, + { + tag: [t.function(t.variableName), t.function(t.propertyName)], + color: color("function"), + }, + { tag: t.typeName, color: color("type") }, + { tag: t.className, color: color("class") ?? color("type") }, + { + tag: [t.constant(t.variableName), t.standard(t.variableName)], + color: color("constant"), + }, + { tag: t.operator, color: color("operator") }, + { tag: t.invalid, color: color("invalid") }, + ]; + return specs.filter((spec) => spec.color); +} + +/** + * The canvas theme for an Acode editor theme: colors from its config, syntax + * colors from the HighlightStyle in its extensions. The theme's + * EditorView.theme stays in the state, where it styles the DOM UI (tooltips, + * panels, autocomplete). + */ +export function getCanvasTheme(themeId: string): CanvasThemeSpec { + // Resolve ids like getThemeExtensions does ("one-dark" -> "one_dark"). + const theme = + getThemeById(themeId) || getThemeById(String(themeId).replace(/-/g, "_")); + const config = (theme?.config || + getThemeConfig(themeId) || + {}) as ThemeConfig; + const highlight = + findHighlightStyle(getThemeExtensions(themeId, [oneDark])) || + highlightFromConfig(config); + return { + dark: config.dark !== false, + background: config.background || "#282c34", + foreground: config.foreground || "#abb2bf", + selection: config.selection, + cursor: config.cursor, + activeLine: config.activeLine, + // Like the DOM themes, which give the active gutter line no background. + activeLineGutter: config.background, + gutterForeground: config.lineNumber, + gutterActiveForeground: config.lineNumberActive, + // Acode draws its own (draggable) scrollbars over the editor. + scrollbar: "transparent", + highlight, + classes: config.matchingBracket + ? { "cm-matchingBracket": { background: config.matchingBracket } } + : undefined, + }; +} + +/** A CSS font size ("12px", "1rem", "10pt"...) in CSS pixels. */ +export function fontSizeToPx(value: unknown): number { + const match = /^([\d.]+)\s*([a-z]*)$/i.exec(String(value ?? "").trim()); + if (!match) return 12; + const size = Number.parseFloat(match[1]); + if (!Number.isFinite(size) || size <= 0) return 12; + const unit = match[2].toLowerCase(); + const rootSize = () => + Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || + 16; + switch (unit) { + case "pt": + return (size * 4) / 3; + case "pc": + return size * 16; + case "in": + return size * 96; + case "mm": + return (size * 96) / 25.4; + case "em": + case "rem": + return size * rootSize(); + default: + return size; + } +} + +export function getCanvasFont(settings: Settings, family: string): FontSpec { + const lineHeight = Number(settings.lineHeight); + return { + family, + size: fontSizeToPx(settings.fontSize || "12px"), + lineHeight: + Number.isFinite(lineHeight) && lineHeight > 0 ? lineHeight : 1.6, + }; +} + +/** Display options from Acode's editor settings (the live ones). */ +export function getCanvasDisplayOptions( + settings: Settings, + { + themeId, + onLineNumberClick, + }: { + themeId: string; + onLineNumberClick?: CanvasViewConfig["onLineNumberClick"]; + }, +): CanvasDisplayOptions { + const { linenumbers = true, relativeLineNumbers = false } = settings; + const cursorWidth = Number(settings.cursorWidth); + const showSpaces = !!settings.showSpaces; + const rainbow = settings.rainbowBrackets ?? true; + return { + lineNumbers: linenumbers + ? relativeLineNumbers + ? "relative" + : true + : false, + onLineNumberClick, + foldMarkers: settings.fadeFoldWidgets ? "fade" : "always", + highlightActiveLine: settings.highlightActiveLine !== false, + cursorWidth: + Number.isFinite(cursorWidth) && cursorWidth > 0 + ? Math.min(cursorWidth, 10) + : 2, + lineWrapping: !!settings.textWrap, + wrapIndent: settings.wrappingIndent ?? "same", + textDirection: settings.rtlText ? "auto" : "ltr", + horizontalScrollPastEnd: Number(settings.leftMargin ?? 50) || 0, + scrollPastEnd: SCROLL_PAST_END[settings.scrollPastEnd || "medium"] ?? 1, + scrollSpeed: SCROLL_SPEED[settings.scrollSpeed] ?? "normal", + // The DOM editor's guides don't highlight the active block either. + indentGuides: settings.indentGuides ? { active: false } : false, + rainbowBrackets: rainbow + ? { colors: getRainbowBracketColors(getThemeConfig(themeId)) } + : false, + showWhitespace: showSpaces + ? { spaces: true, trailing: true, lineBreaks: true } + : false, + }; +} + +interface CanvasSyncState { + display?: string; + font?: string; + theme?: string; +} + +const synced = new WeakMap(); +// Acode replaces `setTheme` on editor instances with its own (taking a theme +// id), so the view's method is called through the prototype. +const setViewTheme = CanvasView.prototype.setTheme; + +/** + * Bring a canvas view in line with the settings. Each part is applied only + * when it changed: reconfiguring resets the drawn scene, and a new font or a + * dark/light switch rebuilds the glyph atlas. + */ +export function syncCanvasView( + view: CanvasView, + { + display, + font, + themeId, + }: { display?: CanvasDisplayOptions; font?: FontSpec; themeId?: string }, +): void { + let state = synced.get(view); + if (!state) { + state = {}; + synced.set(view, state); + } + if (display) { + // Functions (onLineNumberClick) are left out of the key: they read + // settings when called. + const key = JSON.stringify(display); + if (key !== state.display) { + state.display = key; + view.reconfigure(display); + } + } + if (font) { + const key = JSON.stringify(font); + if (key !== state.font) { + state.font = key; + view.setFont(font); + } + } + if (themeId != null) { + const key = String(themeId); + if (key !== state.theme) { + state.theme = key; + setViewTheme.call(view, getCanvasTheme(key)); + } + } +} + +/** Remember what a new view was created with, so syncing skips it. */ +export function markCanvasViewSynced( + view: CanvasView, + { + display, + font, + themeId, + }: { display: CanvasDisplayOptions; font: FontSpec; themeId: string }, +): void { + synced.set(view, { + display: JSON.stringify(display), + font: JSON.stringify(font), + theme: String(themeId), + }); +} diff --git a/src/cm/createEditorView.ts b/src/cm/createEditorView.ts new file mode 100644 index 0000000000..f2d4677866 --- /dev/null +++ b/src/cm/createEditorView.ts @@ -0,0 +1,63 @@ +import { CanvasView, type CanvasViewConfig } from "@acode/canvas-view"; +import type { EditorState } from "@codemirror/state"; +import { EditorView } from "@codemirror/view"; + +/** + * The editor renderers Acode can create. "dom" is CodeMirror's own + * EditorView; "canvas" is @acode/canvas-view, which draws the text with + * the GPU on CodeMirror's state (opt-in, experimental). + */ +export type EditorRenderer = "dom" | "canvas"; + +export type CanvasEditorOptions = Omit; + +interface CreateEditorViewOptions { + state: EditorState; + parent: HTMLElement; + /** Options for a canvas view; without them a DOM EditorView is created. */ + canvas?: CanvasEditorOptions | null; +} + +let canvasSupport: boolean | null = null; + +/** + * Whether this WebView can run the canvas renderer (it needs WebGL2). The + * probe context is released right away: pages only get ~16 of them. + */ +export function isCanvasRendererSupported(): boolean { + if (canvasSupport != null) return canvasSupport; + try { + const gl = document.createElement("canvas").getContext("webgl2"); + canvasSupport = !!gl; + gl?.getExtension("WEBGL_lose_context")?.loseContext(); + } catch { + canvasSupport = false; + } + return canvasSupport; +} + +export function isCanvasView(view: unknown): view is CanvasView { + return view instanceof CanvasView; +} + +/** + * Create the editor view for a pane. Code after construction treats both + * kinds the same: CanvasView implements the EditorView members Acode and + * CodeMirror's commands use (`state`, `dispatch`, `setState`, `scrollDOM`, + * `contentDOM`, `coordsAtPos`, `requestMeasure`...). + */ +export default function createEditorView({ + state, + parent, + canvas = null, +}: CreateEditorViewOptions): EditorView { + if (canvas) { + // `view instanceof EditorView` holds for canvas views too. + return new CanvasView({ + ...canvas, + state, + parent, + }) as unknown as EditorView; + } + return new EditorView({ state, parent }); +} diff --git a/src/cm/touchSelectionMenu.js b/src/cm/touchSelectionMenu.js index b017acb1fb..69e3c755ba 100644 --- a/src/cm/touchSelectionMenu.js +++ b/src/cm/touchSelectionMenu.js @@ -176,6 +176,17 @@ export default function createTouchSelectionMenu(view, options = {}) { return new TouchSelectionMenuController(view, options); } +/** + * The selection menu for a canvas editor (@acode/canvas-view). The view owns + * the touch selection gestures and handles, and asks for the menu through its + * `touchSelection.onMenu` hook, which goes to `handleCanvasMenu()`. Only the + * menu UI (items, layout, actions) is shared with the DOM editor's controller, + * so it listens to no pointer or selection events of its own. + */ +export function createCanvasSelectionMenu(view, options = {}) { + return new TouchSelectionMenuController(view, { ...options, canvas: true }); +} + class TouchSelectionMenuController { #view; #container; @@ -200,9 +211,11 @@ class TouchSelectionMenuController { #menuAnimation = null; #viewAnimations = []; #overflowExpanded = false; + #canvas = false; constructor(view, options = {}) { this.#view = view; + this.#canvas = !!options.canvas; this.#container = options.container || view.dom.closest(".editor-container") || view.dom; this.#getActiveFile = options.getActiveFile || (() => null); @@ -217,11 +230,19 @@ class TouchSelectionMenuController { #bindEvents() { const root = this.#view.dom; - root.addEventListener("contextmenu", this.#onContextMenu, true); - document.addEventListener("pointerdown", this.#onGlobalPointerDown, true); - document.addEventListener("pointermove", this.#onGlobalPointerMove, true); - document.addEventListener("pointerup", this.#onGlobalPointerUp, true); - document.addEventListener("pointercancel", this.#onGlobalPointerUp, true); + if (this.#canvas) { + document.addEventListener( + "pointerdown", + this.#onCanvasOutsidePointerDown, + true, + ); + } else { + root.addEventListener("contextmenu", this.#onContextMenu, true); + document.addEventListener("pointerdown", this.#onGlobalPointerDown, true); + document.addEventListener("pointermove", this.#onGlobalPointerMove, true); + document.addEventListener("pointerup", this.#onGlobalPointerUp, true); + document.addEventListener("pointercancel", this.#onGlobalPointerUp, true); + } this.#tooltipObserver = new MutationObserver((mutations) => { const relevant = mutations.some((m) => @@ -231,9 +252,13 @@ class TouchSelectionMenuController { (n.matches?.(".cm-tooltip") || n.querySelector?.(".cm-tooltip")), ), ); - if (!relevant || !this.#menuActive || !this.#shouldShowMenu()) { + if (!relevant || !this.#menuActive) return; + if (this.#canvas) { + // Keep the menu where the view asked for it, clear of the tooltip. + if (this.#menuAnchor) this.#positionMenu(this.#menuAnchor); return; } + if (!this.#shouldShowMenu()) return; this.#showMenuDeferred(); }); this.#tooltipObserver.observe(this.#view.dom, { @@ -244,6 +269,11 @@ class TouchSelectionMenuController { destroy() { const root = this.#view.dom; + document.removeEventListener( + "pointerdown", + this.#onCanvasOutsidePointerDown, + true, + ); root.removeEventListener("contextmenu", this.#onContextMenu, true); document.removeEventListener( "pointerdown", @@ -285,10 +315,15 @@ class TouchSelectionMenuController { cancelAnimationFrame(this.#stateSyncRaf); this.#stateSyncRaf = 0; this.#hideMenu(true); + if (this.#canvas) this.#view.touchSelection?.hideMenu(); } setSelection(value) { if (!this.#enabled) return; + if (this.#canvas) { + if (value) this.#view.touchSelection?.openMenu(); + return; + } if (value) { this.#dismissedSelection = null; this.#menuRequested = true; @@ -302,6 +337,11 @@ class TouchSelectionMenuController { setMenu(value) { this.#menuRequested = !!value; if (!this.#enabled) return; + if (this.#canvas) { + if (value) this.#view.touchSelection?.openMenu(); + else this.#view.touchSelection?.hideMenu(); + return; + } if (!value) { this.#clearMenuShowTimer(); this.#hideMenu(); @@ -316,7 +356,8 @@ class TouchSelectionMenuController { } onScrollStart() { - if (!this.#enabled) return; + // A canvas view hides and re-shows the menu around its own scrolls. + if (!this.#enabled || this.#canvas) return; if (this.#isScrolling) return; this.#clearMenuShowTimer(); this.#isScrolling = true; @@ -325,13 +366,15 @@ class TouchSelectionMenuController { } onScrollEnd() { - if (!this.#enabled || !this.#isScrolling) return; + if (!this.#enabled || this.#canvas || !this.#isScrolling) return; this.#isScrolling = false; if (this.#shouldShowMenu()) this.#scheduleMenuShow(MENU_SHOW_DELAY); } onStateChanged(meta = {}) { - if (!this.#enabled) return; + // A canvas view drops its menu itself on keyboard input and outside + // selection changes. + if (!this.#enabled || this.#canvas) return; if (meta.selectionChanged) this.#cancelReadOnlyTap(); if ( meta.selectionChanged && @@ -364,8 +407,44 @@ class TouchSelectionMenuController { this.#isScrolling = false; this.#clearMenuShowTimer(); this.#hideMenu(true); + if (this.#canvas) this.#view.touchSelection?.hideMenu(); + } + + /** + * A canvas view's `touchSelection.onMenu` event: show the menu over the + * selection (or caret) it reports, or hide it. Hides during scrolls and + * handle drags are transient; the view sends "show" again when they end. + */ + handleCanvasMenu(event) { + if (!this.#canvas) return; + if (event?.type !== "show") { + this.#hideMenu(); + return; + } + if (!this.#enabled) return; + const rect = event.selectionRect; + if (!rect) return; + this.#showMenu({ + x: rect.left + rect.width / 2, + top: rect.top, + bottom: rect.bottom, + hasSelection: event.trigger === "selection", + }); } + #onCanvasOutsidePointerDown = (event) => { + if (!this.#menuActive) return; + const target = event.target; + if ( + target instanceof Node && + (this.$menu.contains(target) || this.#view.dom.contains(target)) + ) { + return; + } + this.#hideMenu(); + this.#view.touchSelection?.hideMenu(); + }; + #onContextMenu = (event) => { if (!this.#enabled) return; if (this.#isIgnoredPointerTarget(event.target)) return; @@ -829,6 +908,7 @@ class TouchSelectionMenuController { this.#menuRequested = false; this.#dismissedSelection = this.#selectionSignature(); this.#hideMenu(); + if (this.#canvas) this.#view.touchSelection?.hideMenu(); focusEditorIfEditable(this.#view); } }; diff --git a/src/lib/editorManager.js b/src/lib/editorManager.js index ea2b573299..e933a59c48 100644 --- a/src/lib/editorManager.js +++ b/src/lib/editorManager.js @@ -29,6 +29,13 @@ import { wrapWithAbbreviation, } from "@emmetio/codemirror6-plugin"; import createBaseExtensions from "cm/baseExtensions"; +import { + getCanvasDisplayOptions, + getCanvasFont, + getCanvasTheme, + markCanvasViewSynced, + syncCanvasView, +} from "cm/canvasOptions"; import { setKeyBindings as applyKeyBindings, executeCommand, @@ -38,6 +45,10 @@ import { registerExternalCommand, removeExternalCommand, } from "cm/commandRegistry"; +import createEditorView, { + isCanvasRendererSupported, + isCanvasView, +} from "cm/createEditorView"; import { blurEditorIfReadOnly, createEditorReadOnlyExtension, @@ -66,7 +77,9 @@ import { getModesByName, initModes, } from "cm/modelist"; -import createTouchSelectionMenu from "cm/touchSelectionMenu"; +import createTouchSelectionMenu, { + createCanvasSelectionMenu, +} from "cm/touchSelectionMenu"; import "cm/supportedModes"; import { onProviderRegistered } from "fileSystem"; import { autocompletion } from "@codemirror/autocomplete"; @@ -960,6 +973,15 @@ async function EditorManager($header, $body) { const languageCompartment = new Compartment(); // Compartment for LSP extensions so we can swap per file const lspCompartment = new Compartment(); + // The renderer is chosen at startup ("Renderer" in editor settings applies + // on reload). The canvas renderer draws line numbers, folding, guides, + // whitespace, wrapping and scroll room itself, so the option specs below + // leave those extensions out and canvas views get options instead. + let canvasRenderer = appSettings.value.editorRenderer === "canvas"; + if (canvasRenderer && !isCanvasRendererSupported()) { + canvasRenderer = false; + console.warn("Canvas renderer needs WebGL2; using the DOM editor."); + } const diagnosticsClientExt = lspDiagnosticsClientExtension(); const buildDiagnosticsUiExt = () => lspDiagnosticsUiExtension(appSettings?.value?.lintGutter !== false); @@ -1161,6 +1183,7 @@ async function EditorManager($header, $body) { keys: ["linenumbers", "relativeLineNumbers"], compartments: [lineNumberCompartment], build() { + if (canvasRenderer) return []; return makeLineNumberExtension(); }, }, @@ -1168,6 +1191,7 @@ async function EditorManager($header, $body) { keys: ["rainbowBrackets"], compartments: [rainbowCompartment], build() { + if (canvasRenderer) return []; return makeRainbowBracketExtension(); }, }, @@ -1176,7 +1200,7 @@ async function EditorManager($header, $body) { compartments: [indentGuidesCompartment], build() { const enabled = appSettings?.value?.indentGuides ?? false; - if (!enabled) return []; + if (!enabled || canvasRenderer) return []; return indentGuides({ highlightActiveGuide: false, hideOnBlankLines: false, @@ -1201,6 +1225,7 @@ async function EditorManager($header, $body) { keys: ["textWrap", "wrappingIndent"], compartments: [wrapCompartment], build() { + if (canvasRenderer) return []; return makeWrapExtension(); }, }, @@ -1216,6 +1241,7 @@ async function EditorManager($header, $body) { keys: ["rtlText"], compartments: [rtlCompartment], build() { + if (canvasRenderer) return []; const rtl = !!appSettings?.value?.rtlText; return EditorView.theme({ "&": { direction: rtl ? "rtl" : "ltr" }, @@ -1227,7 +1253,7 @@ async function EditorManager($header, $body) { compartments: [whitespaceCompartment], build() { const show = !!appSettings?.value?.showSpaces; - return show + return show && !canvasRenderer ? [ highlightWhitespace(), highlightTrailingWhitespace(), @@ -1241,7 +1267,7 @@ async function EditorManager($header, $body) { compartments: [lineBreakMarkerCompartment], build() { const showSpaces = !!appSettings?.value?.showSpaces; - return showSpaces ? lineBreakMarker : []; + return showSpaces && !canvasRenderer ? lineBreakMarker : []; }, }, { @@ -1249,7 +1275,7 @@ async function EditorManager($header, $body) { compartments: [foldThemeCompartment], build() { const fade = !!appSettings?.value?.fadeFoldWidgets; - if (!fade) return []; + if (!fade || canvasRenderer) return []; return EditorView.theme({ ".cm-gutter.cm-foldGutter .cm-gutterElement": { opacity: 0, @@ -1293,7 +1319,7 @@ async function EditorManager($header, $body) { compartments: [scrollPastEndCompartment], build() { const value = appSettings?.value?.scrollPastEnd || "medium"; - if (value === "none") { + if (value === "none" || canvasRenderer) { return []; } const factorMap = { @@ -1377,9 +1403,102 @@ async function EditorManager($header, $body) { } target.dispatch({ effects }); } + if (isCanvasView(target)) syncCanvasOptions(target); + } + } + + function getCanvasDisplayOptionsFromSettings() { + return getCanvasDisplayOptions(appSettings.value, { + themeId: appSettings.value.editorTheme || "one_dark", + onLineNumberClick: handleCanvasLineNumberClick, + }); + } + + /** Apply the current settings to a canvas view (only what changed). */ + function syncCanvasOptions(target) { + syncCanvasView(target, { + display: getCanvasDisplayOptionsFromSettings(), + font: getCanvasFont(appSettings.value, getEditorFontFamily()), + }); + } + + function syncAllCanvasViews() { + for (const pane of panes) { + if (isCanvasView(pane.editor)) syncCanvasOptions(pane.editor); + } + } + + /** + * Taps on a canvas view's line numbers select the line, like the DOM + * editor's line number gutter. Its fold markers sit in a narrow column + * right of the numbers: taps there fall through to the fold toggle. + */ + function handleCanvasLineNumberClick(line, event, view) { + const rect = view.scrollDOM.getBoundingClientRect(); + const foldColumn = view.gutterWidth - view.cellWidth * 1.5; + if (event.clientX - rect.left >= foldColumn) return false; + const docLine = view.state.doc.line(line); + return handleLineNumberClick(view, view.lineBlockAt(docLine.from), event, { + shiftClickSelection: appSettings.value.shiftClickSelection !== false, + }); + } + + /** + * Create a pane's editor view with the configured renderer. Canvas views + * get the settings as options, and drive the selection menu through their + * touch selection hooks. + */ + function createPaneEditorView(pane, state, parent) { + if (!canvasRenderer) return createEditorView({ state, parent }); + const themeId = appSettings.value.editorTheme || "one_dark"; + const display = getCanvasDisplayOptionsFromSettings(); + const font = getCanvasFont(appSettings.value, getEditorFontFamily()); + try { + const view = createEditorView({ + state, + parent, + canvas: { + ...display, + font, + theme: getCanvasTheme(themeId), + input: appSettings.value.useTextareaForIME ? "textarea" : "auto", + touchSelection: { + onMenu: (event) => + pane.touchSelectionController?.handleCanvasMenu?.(event), + onHaptic() { + if (appSettings.value.vibrateOnTap) { + navigator.vibrate?.(config.VIBRATION_TIME); + } + }, + }, + }, + }); + markCanvasViewSynced(view, { display, font, themeId }); + return view; + } catch (error) { + // Use the DOM editor for the rest of the session, with the + // extensions the canvas would have drawn. + canvasRenderer = false; + for (const leftover of parent.querySelectorAll(":scope > .cv-editor")) { + leftover.remove(); + } + warnRecoverable( + "Canvas renderer failed to start; using the DOM editor.", + error, + "canvas-renderer", + ); + const view = createEditorView({ state, parent }); + applyOptions(null, view); + return view; } } + function createPaneTouchSelectionMenu(view, options) { + return isCanvasView(view) + ? createCanvasSelectionMenu(view, options) + : createTouchSelectionMenu(view, options); + } + function setThemeForEditors(themeId) { panes.forEach((pane) => { if (pane.editor) { @@ -1685,10 +1804,7 @@ async function EditorManager($header, $body) { const editorState = createEmptyEditorState(); - editor = new EditorView({ - state: editorState, - parent: $container, - }); + editor = createPaneEditorView(primaryPane, editorState, $container); editor.__editorPane = primaryPane; primaryPane.editor = editor; activePane = primaryPane; @@ -1734,7 +1850,7 @@ async function EditorManager($header, $body) { }, }); - touchSelectionController = createTouchSelectionMenu(editor, { + touchSelectionController = createPaneTouchSelectionMenu(editor, { container: $container, getActiveFile: () => manager?.activeFile || null, isShiftSelectionActive, @@ -1772,6 +1888,9 @@ async function EditorManager($header, $body) { const id = String(themeId || ""); const ext = getThemeExtensions(id, [oneDark]); targetEditor.dispatch({ effects: themeCompartment.reconfigure(ext) }); + if (isCanvasView(targetEditor)) { + syncCanvasView(targetEditor, { themeId: id }); + } return true; } catch (_) { return false; @@ -2407,15 +2526,16 @@ async function EditorManager($header, $body) { } async function createPaneEditor(pane) { - const paneEditor = new EditorView({ - state: createEmptyEditorState(), - parent: pane.editorContainer, - }); + const paneEditor = createPaneEditorView( + pane, + createEmptyEditorState(), + pane.editorContainer, + ); pane.editor = paneEditor; paneEditor.__editorPane = pane; applyEditorCompatibility(paneEditor); await applyKeyBindings(paneEditor); - pane.touchSelectionController = createTouchSelectionMenu(paneEditor, { + pane.touchSelectionController = createPaneTouchSelectionMenu(paneEditor, { container: pane.editorContainer, getActiveFile: () => pane.activeFile || null, isShiftSelectionActive, @@ -3656,6 +3776,11 @@ async function EditorManager($header, $body) { applyOptions(["indentGuides"]); }); + // Options only the canvas renderer applies live; the DOM editor picks up + // highlightActiveLine when the state is recreated. + appSettings.on("update:highlightActiveLine", syncAllCanvasViews); + appSettings.on("update:scrollSpeed", syncAllCanvasViews); + // Keep file.session and cache in sync on every edit function getDocSyncListener() { return EditorView.updateListener.of((update) => { diff --git a/tests/unit/canvasOptions.test.ts b/tests/unit/canvasOptions.test.ts new file mode 100644 index 0000000000..5161efcb57 --- /dev/null +++ b/tests/unit/canvasOptions.test.ts @@ -0,0 +1,139 @@ +import { HighlightStyle, syntaxHighlighting } from "@codemirror/language"; +import { Compartment, Prec } from "@codemirror/state"; +import { tags as t } from "@lezer/highlight"; +import { + findHighlightStyle, + fontSizeToPx, + getCanvasDisplayOptions, + getCanvasFont, + getCanvasTheme, +} from "cm/canvasOptions"; +import { getThemeConfig, getThemes } from "cm/themes"; +import { describe, expect, it } from "vitest"; + +describe("canvas renderer theme bridge", () => { + it("finds the HighlightStyle inside wrapped theme extensions", () => { + const style = HighlightStyle.define([{ tag: t.keyword, color: "#f00" }]); + const compartment = new Compartment(); + const extension = [ + [], + compartment.of(Prec.high([syntaxHighlighting(style)])), + ]; + expect(findHighlightStyle(extension)).toBe(style); + expect(findHighlightStyle([])).toBeNull(); + }); + + it("gives every built-in theme syntax colors and its config colors", () => { + for (const theme of getThemes()) { + const spec = getCanvasTheme(theme.id); + const config = getThemeConfig(theme.id); + expect(spec.background, theme.id).toBe(config.background); + expect(spec.foreground, theme.id).toBe(config.foreground); + expect(spec.dark, theme.id).toBe(config.dark !== false); + expect(spec.highlight, theme.id).toBeInstanceOf(HighlightStyle); + // Acode's own scrollbars are used instead of the canvas thumbs. + expect(spec.scrollbar).toBe("transparent"); + } + }); + + it("maps the theme's line number and bracket colors", () => { + const config = getThemeConfig("dracula"); + expect(getCanvasTheme("dracula")).toMatchObject({ + selection: config.selection, + cursor: config.cursor, + gutterForeground: config.lineNumber, + gutterActiveForeground: config.lineNumberActive, + classes: { + "cm-matchingBracket": { background: config.matchingBracket }, + }, + }); + }); +}); + +describe("canvas renderer settings", () => { + const defaults = { + linenumbers: true, + relativeLineNumbers: false, + fadeFoldWidgets: false, + textWrap: false, + wrappingIndent: "same", + leftMargin: 50, + scrollPastEnd: "medium", + scrollSpeed: "NORMAL", + indentGuides: false, + rainbowBrackets: true, + showSpaces: false, + highlightActiveLine: true, + cursorWidth: 2, + rtlText: false, + }; + + it("maps Acode's editor settings to canvas options", () => { + const options = getCanvasDisplayOptions(defaults, { themeId: "one_dark" }); + expect(options).toMatchObject({ + lineNumbers: true, + foldMarkers: "always", + lineWrapping: false, + wrapIndent: "same", + horizontalScrollPastEnd: 50, + scrollPastEnd: 0.5, + scrollSpeed: "normal", + indentGuides: false, + showWhitespace: false, + highlightActiveLine: true, + cursorWidth: 2, + textDirection: "ltr", + }); + expect(options.rainbowBrackets).toMatchObject({ + colors: expect.any(Array), + }); + }); + + it("follows changed settings", () => { + const options = getCanvasDisplayOptions( + { + ...defaults, + relativeLineNumbers: true, + fadeFoldWidgets: true, + textWrap: true, + wrappingIndent: "deepIndent", + scrollPastEnd: "none", + scrollSpeed: "FAST_X2", + indentGuides: true, + rainbowBrackets: false, + showSpaces: true, + cursorWidth: 40, + rtlText: true, + }, + { themeId: "one_dark" }, + ); + expect(options).toMatchObject({ + lineNumbers: "relative", + foldMarkers: "fade", + lineWrapping: true, + wrapIndent: "deepIndent", + scrollPastEnd: 0, + scrollSpeed: "fastX2", + indentGuides: { active: false }, + rainbowBrackets: false, + showWhitespace: { spaces: true, trailing: true, lineBreaks: true }, + cursorWidth: 10, + textDirection: "auto", + }); + expect( + getCanvasDisplayOptions( + { ...defaults, linenumbers: false }, + { themeId: "one_dark" }, + ).lineNumbers, + ).toBe(false); + }); + + it("converts the editor font settings", () => { + expect(fontSizeToPx("14px")).toBe(14); + expect(fontSizeToPx("12pt")).toBe(16); + expect(fontSizeToPx("bad")).toBe(12); + expect( + getCanvasFont({ fontSize: "13px", lineHeight: 2 }, "Roboto Mono"), + ).toEqual({ family: "Roboto Mono", size: 13, lineHeight: 2 }); + }); +}); From 359a656a1a2d1843ec04f120aaab4f39b2bbfcd2 Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Tue, 20 Oct 2026 22:28:51 +0530 Subject: [PATCH 04/14] perf(editor): leave out CodeMirror's fold gutter with the canvas renderer The canvas renderer draws its own fold markers, but foldGutter() was still in the state, and its markers plugin rebuilt (unused) markers for the viewport on every update and tab switch. Canvas views now get only the folding state (codeFolding) with indentation folding. --- src/cm/baseExtensions.ts | 14 +++++++++++++- src/lib/editorManager.js | 17 +++++++++++------ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/cm/baseExtensions.ts b/src/cm/baseExtensions.ts index d55a6c7bfd..70b53db701 100644 --- a/src/cm/baseExtensions.ts +++ b/src/cm/baseExtensions.ts @@ -7,6 +7,7 @@ import { import { history } from "@codemirror/commands"; import { bracketMatching, + codeFolding as codeFoldingState, defaultHighlightStyle, foldGutter, indentOnInput, @@ -31,6 +32,11 @@ import indentationFolding from "./indentationFolding"; export interface BaseExtensionOptions { autoIndent?: boolean; codeFolding?: boolean; + /** + * Show CodeMirror's fold gutter with code folding. The canvas renderer + * draws its own fold markers, so it only needs the folding state. + */ + foldGutter?: boolean; autoCloseBrackets?: boolean; bracketMatching?: boolean; highlightActiveLine?: boolean; @@ -46,6 +52,7 @@ export default function createBaseExtensions( const { autoIndent = true, codeFolding = true, + foldGutter: showFoldGutter = true, autoCloseBrackets = true, bracketMatching: enableBracketMatching = true, highlightActiveLine: enableHighlightActiveLine = true, @@ -54,7 +61,12 @@ export default function createBaseExtensions( const extensions: Extension[] = [highlightSpecialChars(), history()]; if (enableHighlightActiveLine) extensions.push(highlightActiveLineGutter()); - if (codeFolding) extensions.push(foldGutter(), indentationFolding); + if (codeFolding) { + extensions.push( + showFoldGutter ? foldGutter() : codeFoldingState(), + indentationFolding, + ); + } extensions.push(drawSelection()); extensions.push(dropCursor()); extensions.push(EditorState.allowMultipleSelections.of(true)); diff --git a/src/lib/editorManager.js b/src/lib/editorManager.js index e933a59c48..72d22fa04b 100644 --- a/src/lib/editorManager.js +++ b/src/lib/editorManager.js @@ -1122,12 +1122,17 @@ async function EditorManager($header, $body) { function getBaseExtensionOptions() { const values = appSettings?.value || {}; - return Object.fromEntries( - Object.entries(baseExtensionDefaults).map(([key, defaultValue]) => [ - key, - values[key] ?? defaultValue, - ]), - ); + return { + ...Object.fromEntries( + Object.entries(baseExtensionDefaults).map(([key, defaultValue]) => [ + key, + values[key] ?? defaultValue, + ]), + ), + // Canvas views draw fold markers themselves; CodeMirror's fold + // gutter would only rebuild its (unused) markers on every update. + foldGutter: !canvasRenderer, + }; } function createConfiguredBaseExtensions() { From 3dfb79672992e8ddc2604bf388dbb4f908c1d653 Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Wed, 21 Oct 2026 10:03:44 +0530 Subject: [PATCH 05/14] fix(editor): open the color picker on canvas views isViewEditable read contentDOM's contenteditable and aria-readonly attributes. A canvas view's contentDOM is its hidden input element, which has neither, so taps on color chips (regex and LSP document colors) were swallowed as if the editor were read-only. Read the state's editable and readOnly facets instead, which is what the DOM editor's attributes reflect. Co-Authored-By: Claude Opus 5.5 --- src/cm/colorChip.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cm/colorChip.ts b/src/cm/colorChip.ts index 5d51889185..87c1219938 100644 --- a/src/cm/colorChip.ts +++ b/src/cm/colorChip.ts @@ -88,9 +88,9 @@ export function colorChipDecoration( } export function isViewEditable(view: EditorView): boolean { - const readOnly = view.contentDOM.ariaReadOnly === "true"; - const editable = view.contentDOM.contentEditable === "true"; - return !readOnly && editable; + // From the state, not contentDOM's attributes: a canvas view's + // contentDOM is its hidden input element, which has neither. + return view.state.facet(EditorView.editable) && !view.state.readOnly; } export function hasLspColorProvider(view: EditorView): boolean { From d0206f1e0fe9dd39b8c94c1d3d209c87bb3bd3c1 Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Wed, 21 Oct 2026 10:03:45 +0530 Subject: [PATCH 06/14] feat(editor): leave read-only handling to canvas views Acode's read-only setup works around the DOM editor on mobile: it makes the content non-editable, blurs on every focus so no keyboard opens, and shows CodeMirror's cursor layer with CSS. A canvas view handles read-only documents itself: it ignores typing, cut, paste and drops, takes focus with inputmode="none" (so its cursor blinks and hardware shortcuts work, without a keyboard), and drops focus when it becomes read-only. For canvas views the extension is now only EditorState.readOnly plus the filter for Acode's own editing commands, and blurEditorIfReadOnly / focusEditorIfEditable leave them focused. Co-Authored-By: Claude Opus 5.5 --- src/cm/editorReadOnly.ts | 34 +++++++++++++++++++++--- src/lib/editorManager.js | 12 ++++++--- tests/unit/editorReadOnly.test.ts | 17 ++++++++++++ tests/unit/pluginFileRestoration.test.js | 1 + 4 files changed, 57 insertions(+), 7 deletions(-) diff --git a/src/cm/editorReadOnly.ts b/src/cm/editorReadOnly.ts index 767b740c18..c8bb3ae3b9 100644 --- a/src/cm/editorReadOnly.ts +++ b/src/cm/editorReadOnly.ts @@ -7,6 +7,7 @@ import { Transaction, } from "@codemirror/state"; import { EditorView } from "@codemirror/view"; +import { isCanvasView } from "./createEditorView"; interface PointerTapSnapshot { pointerId: number; @@ -75,10 +76,29 @@ const readOnlyCursor = [ }), ]; +export interface EditorReadOnlyOptions { + /** The extension is for a canvas view (@acode/canvas-view). */ + canvas?: boolean; +} + /** * Keep CodeMirror's document and DOM editability in sync. */ -export function createEditorReadOnlyExtension(readOnly: boolean): Extension { +export function createEditorReadOnlyExtension( + readOnly: boolean, + { canvas = false }: EditorReadOnlyOptions = {}, +): Extension { + // A canvas view handles read-only files itself: it ignores typing, + // cut, paste and drops, takes focus without opening the keyboard (so + // its cursor shows and hardware-keyboard shortcuts work), and drops + // focus when the editor becomes read-only. It stays editable; only the + // filter for Acode's own editing commands applies. + if (canvas) { + return [ + EditorState.readOnly.of(readOnly), + ...(readOnly ? [readOnlyUserChangeFilter] : []), + ]; + } return [ EditorState.readOnly.of(readOnly), EditorView.editable.of(!readOnly), @@ -103,7 +123,8 @@ export function blurEditorIfReadOnly( view: EditorView, readOnly: boolean, ): void { - if (!readOnly) return; + // A read-only canvas view keeps focus without a keyboard. + if (!readOnly || isCanvasView(view)) return; const activeElement = document.activeElement; if ( activeElement instanceof HTMLElement && @@ -121,7 +142,9 @@ export function reconfigureEditorReadOnly( readOnly: boolean, ): void { view.dispatch({ - effects: compartment.reconfigure(createEditorReadOnlyExtension(readOnly)), + effects: compartment.reconfigure( + createEditorReadOnlyExtension(readOnly, { canvas: isCanvasView(view) }), + ), }); blurEditorIfReadOnly(view, readOnly); } @@ -129,7 +152,10 @@ export function reconfigureEditorReadOnly( /** Focus editable editors and actively settle read-only editors unfocused. */ export function focusEditorIfEditable(view: EditorView): boolean { if (view.state.readOnly) { - blurEditorIfReadOnly(view, true); + // A canvas view focuses without opening the keyboard. (Checked on + // `unknown`: the type guard would narrow an EditorView to never.) + if (isCanvasView(view as unknown)) view.focus(); + else blurEditorIfReadOnly(view, true); return false; } view.focus(); diff --git a/src/lib/editorManager.js b/src/lib/editorManager.js index 72d22fa04b..186cb0c8b5 100644 --- a/src/lib/editorManager.js +++ b/src/lib/editorManager.js @@ -1799,7 +1799,7 @@ async function EditorManager($header, $body) { searchExtension: search(), // Ensure read-only can be toggled later via compartment readOnlyExtension: readOnlyCompartment.of( - createEditorReadOnlyExtension(false), + createEditorReadOnlyExtension(false, { canvas: canvasRenderer }), ), // Editor options driven by settings via compartments optionExtensions: getBaseExtensionsFromOptions(), @@ -2961,7 +2961,9 @@ async function EditorManager($header, $body) { ...getBaseExtensionsFromOptions(), languageCompartment.of([]), lspCompartment.of([]), - readOnlyCompartment.of(createEditorReadOnlyExtension(true)), + readOnlyCompartment.of( + createEditorReadOnlyExtension(true, { canvas: canvasRenderer }), + ), ...(text === undefined ? [placeholder(`Loading ${file.filename || "file"}...`)] : []), @@ -3103,7 +3105,11 @@ async function EditorManager($header, $body) { // Apply read-only state based on file.editable/loading using Compartment try { const ro = !file.editable || !!file.loading; - exts.push(readOnlyCompartment.of(createEditorReadOnlyExtension(ro))); + exts.push( + readOnlyCompartment.of( + createEditorReadOnlyExtension(ro, { canvas: canvasRenderer }), + ), + ); } catch (e) { // safe to ignore; editor will remain editable by default } diff --git a/tests/unit/editorReadOnly.test.ts b/tests/unit/editorReadOnly.test.ts index 0ae77e8409..1b67e10eb3 100644 --- a/tests/unit/editorReadOnly.test.ts +++ b/tests/unit/editorReadOnly.test.ts @@ -53,6 +53,23 @@ function expectReadOnly(view: EditorView) { } describe("editor read-only configuration", () => { + it("leaves focus and input to a canvas view, keeping the edit filter", () => { + const state = EditorState.create({ + doc: "abc", + extensions: createEditorReadOnlyExtension(true, { canvas: true }), + }); + expect(state.readOnly).toBe(true); + // Canvas views take focus without a keyboard while read-only. + expect(state.facet(EditorView.editable)).toBe(true); + const typed = state.update({ + changes: { from: 0, insert: "x" }, + userEvent: "input.type", + }); + expect(typed.state.doc.toString()).toBe("abc"); + const synced = state.update({ changes: { from: 0, insert: "x" } }); + expect(synced.state.doc.toString()).toBe("xabc"); + }); + it("keeps an editable editor focusable for text input", () => { const { view } = createEditor(); diff --git a/tests/unit/pluginFileRestoration.test.js b/tests/unit/pluginFileRestoration.test.js index 4589c1812a..ed12bdd3c0 100644 --- a/tests/unit/pluginFileRestoration.test.js +++ b/tests/unit/pluginFileRestoration.test.js @@ -627,6 +627,7 @@ it.each([ placeholder, createEditorReadOnlyExtension, blurEditorIfReadOnly, + canvasRenderer: false, loadingPreviews, manager: { activeFile: file }, touchSelectionController: null, From a86fef557effc814594842372ab7ed22e77fe205 Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Wed, 21 Oct 2026 10:03:45 +0530 Subject: [PATCH 07/14] feat(editor): selection matches and active indent guide on canvas With the canvas renderer: - highlightSelectionMatches is on (it's off by default for the DOM editor), drawn with the theme's selectionMatch color, or its selection color, as the themes' CSS does; - indent guides highlight the block around the cursor (one rect per frame on the canvas); - the base extensions leave out drawSelection, dropCursor, highlightActiveLine (and its gutter) and highlightSpecialChars, which the canvas draws itself. The canvas view only skipped their plugins; the active line gutter still rebuilt its markers on every selection change. Co-Authored-By: Claude Opus 5.5 --- src/cm/baseExtensions.ts | 21 ++++++++++++++++----- src/cm/canvasOptions.ts | 26 +++++++++++++++++++++----- src/lib/editorManager.js | 4 ++++ tests/unit/canvasOptions.test.ts | 3 ++- 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/cm/baseExtensions.ts b/src/cm/baseExtensions.ts index 70b53db701..c429b2063c 100644 --- a/src/cm/baseExtensions.ts +++ b/src/cm/baseExtensions.ts @@ -37,6 +37,12 @@ export interface BaseExtensionOptions { * draws its own fold markers, so it only needs the folding state. */ foldGutter?: boolean; + /** + * The canvas renderer draws the selection, cursors, drop cursor, active + * line (and its gutter) and special characters itself, so those + * extensions are left out. + */ + canvas?: boolean; autoCloseBrackets?: boolean; bracketMatching?: boolean; highlightActiveLine?: boolean; @@ -53,22 +59,25 @@ export default function createBaseExtensions( autoIndent = true, codeFolding = true, foldGutter: showFoldGutter = true, + canvas = false, autoCloseBrackets = true, bracketMatching: enableBracketMatching = true, highlightActiveLine: enableHighlightActiveLine = true, highlightSelectionMatches: enableHighlightSelectionMatches = true, } = options; - const extensions: Extension[] = [highlightSpecialChars(), history()]; + const extensions: Extension[] = [history()]; + if (!canvas) extensions.push(highlightSpecialChars()); - if (enableHighlightActiveLine) extensions.push(highlightActiveLineGutter()); + if (enableHighlightActiveLine && !canvas) { + extensions.push(highlightActiveLineGutter()); + } if (codeFolding) { extensions.push( showFoldGutter ? foldGutter() : codeFoldingState(), indentationFolding, ); } - extensions.push(drawSelection()); - extensions.push(dropCursor()); + if (!canvas) extensions.push(drawSelection(), dropCursor()); extensions.push(EditorState.allowMultipleSelections.of(true)); if (autoIndent) extensions.push(indentOnInput()); extensions.push( @@ -78,7 +87,9 @@ export default function createBaseExtensions( if (autoCloseBrackets) extensions.push(closeBrackets()); extensions.push(rectangularSelection()); extensions.push(crosshairCursor()); - if (enableHighlightActiveLine) extensions.push(highlightActiveLine()); + if (enableHighlightActiveLine && !canvas) { + extensions.push(highlightActiveLine()); + } if (enableHighlightSelectionMatches) { extensions.push(highlightSelectionMatches()); } diff --git a/src/cm/canvasOptions.ts b/src/cm/canvasOptions.ts index 0f904cba01..750e38f34d 100644 --- a/src/cm/canvasOptions.ts +++ b/src/cm/canvasOptions.ts @@ -34,6 +34,7 @@ interface ThemeConfig { lineNumber?: string; lineNumberActive?: string; matchingBracket?: string; + selectionMatch?: string; [color: string]: unknown; } @@ -111,6 +112,22 @@ function highlightFromConfig(config: ThemeConfig): TokenStyleSpec[] { return specs.filter((spec) => spec.color); } +/** + * Decoration colors the canvas draws natively, as the themes' CSS has them: + * selection matches use the theme's `selectionMatch`, else its selection. + */ +function themeClasses(config: ThemeConfig): CanvasThemeSpec["classes"] { + const classes: NonNullable = {}; + if (config.matchingBracket) { + classes["cm-matchingBracket"] = { background: config.matchingBracket }; + } + const selectionMatch = config.selectionMatch || config.selection; + if (selectionMatch) { + classes["cm-selectionMatch"] = { background: selectionMatch }; + } + return Object.keys(classes).length ? classes : undefined; +} + /** * The canvas theme for an Acode editor theme: colors from its config, syntax * colors from the HighlightStyle in its extensions. The theme's @@ -141,9 +158,7 @@ export function getCanvasTheme(themeId: string): CanvasThemeSpec { // Acode draws its own (draggable) scrollbars over the editor. scrollbar: "transparent", highlight, - classes: config.matchingBracket - ? { "cm-matchingBracket": { background: config.matchingBracket } } - : undefined, + classes: themeClasses(config), }; } @@ -218,8 +233,9 @@ export function getCanvasDisplayOptions( horizontalScrollPastEnd: Number(settings.leftMargin ?? 50) || 0, scrollPastEnd: SCROLL_PAST_END[settings.scrollPastEnd || "medium"] ?? 1, scrollSpeed: SCROLL_SPEED[settings.scrollSpeed] ?? "normal", - // The DOM editor's guides don't highlight the active block either. - indentGuides: settings.indentGuides ? { active: false } : false, + // Unlike the DOM editor's guides, the block around the cursor is + // highlighted: it's one rect per frame on the canvas. + indentGuides: settings.indentGuides ? { active: true } : false, rainbowBrackets: rainbow ? { colors: getRainbowBracketColors(getThemeConfig(themeId)) } : false, diff --git a/src/lib/editorManager.js b/src/lib/editorManager.js index 186cb0c8b5..6d3823b229 100644 --- a/src/lib/editorManager.js +++ b/src/lib/editorManager.js @@ -1132,6 +1132,10 @@ async function EditorManager($header, $body) { // Canvas views draw fold markers themselves; CodeMirror's fold // gutter would only rebuild its (unused) markers on every update. foldGutter: !canvasRenderer, + canvas: canvasRenderer, + // Off by default for the DOM editor (its cost per selection + // change); canvas views draw the matches cheaply. + ...(canvasRenderer ? { highlightSelectionMatches: true } : {}), }; } diff --git a/tests/unit/canvasOptions.test.ts b/tests/unit/canvasOptions.test.ts index 5161efcb57..3bc5f34388 100644 --- a/tests/unit/canvasOptions.test.ts +++ b/tests/unit/canvasOptions.test.ts @@ -45,6 +45,7 @@ describe("canvas renderer theme bridge", () => { gutterActiveForeground: config.lineNumberActive, classes: { "cm-matchingBracket": { background: config.matchingBracket }, + "cm-selectionMatch": { background: config.selection }, }, }); }); @@ -114,7 +115,7 @@ describe("canvas renderer settings", () => { wrapIndent: "deepIndent", scrollPastEnd: 0, scrollSpeed: "fastX2", - indentGuides: { active: false }, + indentGuides: { active: true }, rainbowBrackets: false, showWhitespace: { spaces: true, trailing: true, lineBreaks: true }, cursorWidth: 10, From 366cd8c8a0d5322991615c9af9b14e30beb7373a Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Wed, 21 Oct 2026 10:24:22 +0530 Subject: [PATCH 08/14] feat(editor): show lint underlines on canvas views on touch devices On touch devices the LSP linter drops every in-text mark (markerFilter), so only the lint gutter showed an error. Canvas views draw the wavy underlines and lint points natively, without the DOM editor's layout cost, so they keep them; hover tooltips stay filtered on touch. Co-Authored-By: Claude Opus 5.5 --- src/cm/lsp/diagnostics.ts | 13 +++++++++---- src/lib/editorManager.js | 8 ++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/cm/lsp/diagnostics.ts b/src/cm/lsp/diagnostics.ts index 36b4e243d2..6e85eddd61 100644 --- a/src/cm/lsp/diagnostics.ts +++ b/src/cm/lsp/diagnostics.ts @@ -543,10 +543,15 @@ export function lspDiagnosticsClientExtension(): { }; } -export function lspDiagnosticsUiExtension(includeGutter = true): Extension[] { - const diagnosticsMarkerFilter = isCoarsePointerDevice() - ? () => [] - : undefined; +export function lspDiagnosticsUiExtension( + includeGutter = true, + { canvas = false }: { canvas?: boolean } = {}, +): Extension[] { + // Touch devices get no in-text marks in the DOM editor. Canvas views + // (@acode/canvas-view) draw them natively (wavy underlines, lint + // points) at no layout cost, so they keep them; tooltips stay off. + const diagnosticsMarkerFilter = + isCoarsePointerDevice() && !canvas ? () => [] : undefined; const diagnosticsTooltipFilter = isCoarsePointerDevice() ? () => [] : undefined; diff --git a/src/lib/editorManager.js b/src/lib/editorManager.js index 6d3823b229..052714eca7 100644 --- a/src/lib/editorManager.js +++ b/src/lib/editorManager.js @@ -984,7 +984,9 @@ async function EditorManager($header, $body) { } const diagnosticsClientExt = lspDiagnosticsClientExtension(); const buildDiagnosticsUiExt = () => - lspDiagnosticsUiExtension(appSettings?.value?.lintGutter !== false); + lspDiagnosticsUiExtension(appSettings?.value?.lintGutter !== false, { + canvas: canvasRenderer, + }); const UNTITLED_URI_PREFIX = "untitled://acode/"; function getEditorFontFamily() { @@ -3743,7 +3745,9 @@ async function EditorManager($header, $body) { appSettings.on("update:lintGutter", function (value) { lspClientManager.setOptions({ - diagnosticsUiExtension: lspDiagnosticsUiExtension(value !== false), + diagnosticsUiExtension: lspDiagnosticsUiExtension(value !== false, { + canvas: canvasRenderer, + }), }); const active = manager.activeFile; if (active?.type === "editor") { From a6d046b81e2cfd0d78f906982ca8ea2eaabda3cd Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Wed, 21 Oct 2026 10:24:22 +0530 Subject: [PATCH 09/14] perf(editor): draw color chips on canvas views without DOM ColorChipWidget gets toCanvas(): the same box toDOM styles (0.9em, 2px margins, rounded, a faint border), in cells of the view's font. Canvas views then make no element per chip; they call toDOM only when a chip is tapped, so the color picker still opens. Co-Authored-By: Claude Opus 5.5 --- src/cm/colorChip.ts | 22 +++++++++++++++++ tests/unit/colorChip.test.ts | 46 ++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 tests/unit/colorChip.test.ts diff --git a/src/cm/colorChip.ts b/src/cm/colorChip.ts index 87c1219938..d67210fec0 100644 --- a/src/cm/colorChip.ts +++ b/src/cm/colorChip.ts @@ -1,3 +1,4 @@ +import type { CanvasWidgetSpec } from "@acode/canvas-view"; import type { LSPClient } from "@codemirror/lsp-client"; import { LSPPlugin } from "@codemirror/lsp-client"; import type { Range } from "@codemirror/state"; @@ -73,6 +74,27 @@ export class ColorChipWidget extends WidgetType { return el; } + /** + * How a canvas view (@acode/canvas-view) draws the chip: the box + * `toDOM` styles, without making its element. The view only calls + * `toDOM` when the chip is tapped, so the color picker still opens. + */ + toCanvas(view: EditorView): CanvasWidgetSpec { + const cell = view.defaultCharacterWidth || 8; + const em = (view as unknown as { fontSizePx?: number }).fontSizePx || 14; + return { + text: "", + cells: Math.max(0.5, Math.round(((0.9 * em) / cell) * 4) / 4), + padLeft: 2 / cell, + padRight: 2 / cell, + style: { + background: this.payload.css, + border: "rgba(0,0,0,0.2)", + rounded: true, + }, + }; + } + ignoreEvent(): boolean { return false; } diff --git a/tests/unit/colorChip.test.ts b/tests/unit/colorChip.test.ts new file mode 100644 index 0000000000..836b71a3c4 --- /dev/null +++ b/tests/unit/colorChip.test.ts @@ -0,0 +1,46 @@ +// @vitest-environment happy-dom + +import type { EditorView } from "@codemirror/view"; +import { ColorChipWidget, getColorChipPayload } from "cm/colorChip"; +import { describe, expect, it, vi } from "vitest"; + +// The picker dialog and color utilities (JSX) and the LSP client loader +// aren't needed here. +vi.mock("../../src/dialogs/color.js", () => ({ default: vi.fn() })); +vi.mock("../../src/utils/color/index.js", () => ({ default: vi.fn() })); +vi.mock("cm/lsp/clientLoader", () => ({ getAllLspPlugins: () => [] })); + +const payload = { + from: 4, + to: 11, + css: "#ff8800", + source: "regex" as const, +}; + +describe("color chip widget", () => { + it("tells a canvas view the box toDOM styles, in cells of its font", () => { + const view = { + defaultCharacterWidth: 8, + fontSizePx: 16, + } as unknown as EditorView; + const spec = new ColorChipWidget(payload).toCanvas(view); + // 0.9em of a 16px font is 14.4px: 1.8 cells, rounded to quarters. + expect(spec).toEqual({ + text: "", + cells: 1.75, + padLeft: 0.25, + padRight: 0.25, + style: { + background: "#ff8800", + border: "rgba(0,0,0,0.2)", + rounded: true, + }, + }); + }); + + it("still makes a DOM element carrying its payload (taps on canvas views)", () => { + const el = new ColorChipWidget(payload).toDOM(); + expect(el.style.backgroundColor).toBeTruthy(); + expect(getColorChipPayload(el)).toBe(payload); + }); +}); From 3bfc4f806f1b9adf58936f28c910f7166d51e775 Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Wed, 21 Oct 2026 12:03:12 +0530 Subject: [PATCH 10/14] fix(editor): apply the line height setting to the editor's lines The font theme set `lineHeight` on the editor element, but CodeMirror's base theme sets `line-height: 1.4` on `.cm-scroller`, so the setting never reached the lines or gutters: the DOM editor was always 1.4 while canvas views used the setting, and the two renderers didn't match. Set it on `.cm-scroller`, so both renderers use it (canvas-view measures it like CodeMirror, to the DOM's exact value). The default becomes 1.4, what the editor always showed, and stored values of the old default (2), which nobody ever saw, are migrated once so editors don't change size. --- src/cm/canvasOptions.ts | 3 ++- src/lib/editorManager.js | 7 +++++-- src/lib/settings.js | 12 +++++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/cm/canvasOptions.ts b/src/cm/canvasOptions.ts index 750e38f34d..04e8379784 100644 --- a/src/cm/canvasOptions.ts +++ b/src/cm/canvasOptions.ts @@ -189,13 +189,14 @@ export function fontSizeToPx(value: unknown): number { } } +/** Same font size and line height as `makeFontTheme` gives the DOM editor. */ export function getCanvasFont(settings: Settings, family: string): FontSpec { const lineHeight = Number(settings.lineHeight); return { family, size: fontSizeToPx(settings.fontSize || "12px"), lineHeight: - Number.isFinite(lineHeight) && lineHeight > 0 ? lineHeight : 1.6, + Number.isFinite(lineHeight) && lineHeight > 0 ? lineHeight : 1.4, }; } diff --git a/src/lib/editorManager.js b/src/lib/editorManager.js index 052714eca7..219b942dc2 100644 --- a/src/lib/editorManager.js +++ b/src/lib/editorManager.js @@ -1034,10 +1034,13 @@ async function EditorManager($header, $body) { function makeFontTheme() { const fontSize = appSettings?.value?.fontSize || "12px"; - const lineHeight = appSettings?.value?.lineHeight || 1.6; + const lineHeight = appSettings?.value?.lineHeight || 1.4; const fontFamily = getEditorFontFamily(); return EditorView.theme({ - "&": { fontSize, lineHeight: String(lineHeight) }, + "&": { fontSize }, + // On the scroller, where CodeMirror's base theme sets 1.4: a line + // height on the editor itself never reached the lines or gutters. + ".cm-scroller": { lineHeight: String(lineHeight) }, ".cm-content": { fontFamily }, ".cm-gutter": { fontFamily }, ".cm-tooltip, .cm-tooltip *": { fontFamily }, diff --git a/src/lib/settings.js b/src/lib/settings.js index 0423bb0712..c6e9b53332 100644 --- a/src/lib/settings.js +++ b/src/lib/settings.js @@ -170,7 +170,8 @@ class Settings { scrollbarHeight: 50, showSpaces: false, confirmOnExit: true, - lineHeight: 2, + lineHeight: 1.4, + lineHeightMigrated: true, leftMargin: 50, checkFiles: true, checkForAppUpdates: false, @@ -248,6 +249,15 @@ class Settings { const settings = helpers.parseJSON(await fs.readFile("utf8")); if (settings) { + // The old default line height (2) never reached the editor's lines + // (CodeMirror's 1.4 won), so nobody saw it. Now that it applies, + // keep what the editor showed instead of doubling line spacing. + if ( + settings.lineHeightMigrated === undefined && + settings.lineHeight === 2 + ) + settings.lineHeight = 1.4; + // make sure that all the settings are present Object.keys(this.#defaultSettings).forEach((setting) => { const value = settings[setting]; From 217cae121ad8556d31d6c9a21c70dbb0083447e2 Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Wed, 21 Oct 2026 12:03:12 +0530 Subject: [PATCH 11/14] feat(editor): canvas backend and sticky scroll settings Two settings for the canvas renderer, next to "Renderer": - "Canvas backend": Auto (WebGL2), WebGL2 or WebGPU (falls back to WebGL2 where unavailable). Views are created with it, so a change asks to reload when the canvas renderer is on. - "Sticky scroll": keeps the header lines of the scopes around the top line pinned while scrolling. Applies live to canvas views. --- src/cm/canvasOptions.ts | 11 +++++++++++ src/lang/ar-ye.json | 7 ++++++- src/lang/az-az.json | 7 ++++++- src/lang/be-by.json | 7 ++++++- src/lang/bn-bd.json | 7 ++++++- src/lang/cs-cz.json | 7 ++++++- src/lang/de-de.json | 7 ++++++- src/lang/en-us.json | 7 ++++++- src/lang/es-sv.json | 7 ++++++- src/lang/fr-fr.json | 7 ++++++- src/lang/he-il.json | 7 ++++++- src/lang/hi-in.json | 7 ++++++- src/lang/hu-hu.json | 7 ++++++- src/lang/id-id.json | 7 ++++++- src/lang/ir-fa.json | 7 ++++++- src/lang/it-it.json | 7 ++++++- src/lang/ja-jp.json | 7 ++++++- src/lang/ko-kr.json | 7 ++++++- src/lang/ln-ln.json | 7 ++++++- src/lang/ml-in.json | 7 ++++++- src/lang/mm-unicode.json | 7 ++++++- src/lang/mm-zawgyi.json | 7 ++++++- src/lang/pl-pl.json | 7 ++++++- src/lang/pt-br.json | 7 ++++++- src/lang/pu-in.json | 7 ++++++- src/lang/ru-ru.json | 7 ++++++- src/lang/tl-ph.json | 7 ++++++- src/lang/tr-tr.json | 7 ++++++- src/lang/uk-ua.json | 7 ++++++- src/lang/uz-uz.json | 7 ++++++- src/lang/vi-vn.json | 7 ++++++- src/lang/zh-cn.json | 7 ++++++- src/lang/zh-hant.json | 7 ++++++- src/lang/zh-tw.json | 7 ++++++- src/lib/editorManager.js | 3 +++ src/lib/settings.js | 2 ++ src/settings/editorSettings.js | 35 +++++++++++++++++++++++++++++++--- 37 files changed, 246 insertions(+), 36 deletions(-) diff --git a/src/cm/canvasOptions.ts b/src/cm/canvasOptions.ts index 04e8379784..4d9eb7aa05 100644 --- a/src/cm/canvasOptions.ts +++ b/src/cm/canvasOptions.ts @@ -243,9 +243,20 @@ export function getCanvasDisplayOptions( showWhitespace: showSpaces ? { spaces: true, trailing: true, lineBreaks: true } : false, + stickyScroll: !!settings.stickyScroll, }; } +const CANVAS_BACKENDS = ["auto", "webgl2", "webgpu"] as const; + +/** The GPU API for new canvas views ("webgpu" falls back to WebGL2). */ +export function getCanvasBackend( + settings: Settings, +): CanvasViewConfig["backend"] { + const value = settings.canvasBackend; + return CANVAS_BACKENDS.includes(value) ? value : "auto"; +} + interface CanvasSyncState { display?: string; font?: string; diff --git a/src/lang/ar-ye.json b/src/lang/ar-ye.json index f6350c794b..08be3015f1 100644 --- a/src/lang/ar-ye.json +++ b/src/lang/ar-ye.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/az-az.json b/src/lang/az-az.json index e88755c291..61081abd1b 100644 --- a/src/lang/az-az.json +++ b/src/lang/az-az.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/be-by.json b/src/lang/be-by.json index c9d1855cce..92f3eb96af 100644 --- a/src/lang/be-by.json +++ b/src/lang/be-by.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/bn-bd.json b/src/lang/bn-bd.json index 54a7cfec95..d34b1ba75b 100644 --- a/src/lang/bn-bd.json +++ b/src/lang/bn-bd.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/cs-cz.json b/src/lang/cs-cz.json index dcf4fa256d..3246f82be2 100644 --- a/src/lang/cs-cz.json +++ b/src/lang/cs-cz.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/de-de.json b/src/lang/de-de.json index 536591735d..7edb80d97d 100644 --- a/src/lang/de-de.json +++ b/src/lang/de-de.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/en-us.json b/src/lang/en-us.json index df03ef2b35..0306650561 100644 --- a/src/lang/en-us.json +++ b/src/lang/en-us.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/es-sv.json b/src/lang/es-sv.json index 3231bdf8f0..dd457089a1 100644 --- a/src/lang/es-sv.json +++ b/src/lang/es-sv.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/fr-fr.json b/src/lang/fr-fr.json index 65936f84e5..0b4cde073b 100644 --- a/src/lang/fr-fr.json +++ b/src/lang/fr-fr.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/he-il.json b/src/lang/he-il.json index 9d4112947d..e3e55d9c14 100644 --- a/src/lang/he-il.json +++ b/src/lang/he-il.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/hi-in.json b/src/lang/hi-in.json index 50c46a9a37..b1c38ef535 100644 --- a/src/lang/hi-in.json +++ b/src/lang/hi-in.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/hu-hu.json b/src/lang/hu-hu.json index 2dd71eab6e..137c363d64 100644 --- a/src/lang/hu-hu.json +++ b/src/lang/hu-hu.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/id-id.json b/src/lang/id-id.json index 5f0478e909..15d552cf03 100644 --- a/src/lang/id-id.json +++ b/src/lang/id-id.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/ir-fa.json b/src/lang/ir-fa.json index 433498444f..3cfdb039e6 100644 --- a/src/lang/ir-fa.json +++ b/src/lang/ir-fa.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/it-it.json b/src/lang/it-it.json index a3e145108f..7687a9ddf4 100644 --- a/src/lang/it-it.json +++ b/src/lang/it-it.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/ja-jp.json b/src/lang/ja-jp.json index 0af76a7656..d3aac78f10 100644 --- a/src/lang/ja-jp.json +++ b/src/lang/ja-jp.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/ko-kr.json b/src/lang/ko-kr.json index a2f45a160f..5c03da25d3 100644 --- a/src/lang/ko-kr.json +++ b/src/lang/ko-kr.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/ln-ln.json b/src/lang/ln-ln.json index 17a11020c5..5eebb3e77c 100644 --- a/src/lang/ln-ln.json +++ b/src/lang/ln-ln.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/ml-in.json b/src/lang/ml-in.json index 950d81407a..2eee43116f 100644 --- a/src/lang/ml-in.json +++ b/src/lang/ml-in.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/mm-unicode.json b/src/lang/mm-unicode.json index b33147c437..2bedccc149 100644 --- a/src/lang/mm-unicode.json +++ b/src/lang/mm-unicode.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/mm-zawgyi.json b/src/lang/mm-zawgyi.json index 08e5c3220c..8e792d7fd7 100644 --- a/src/lang/mm-zawgyi.json +++ b/src/lang/mm-zawgyi.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/pl-pl.json b/src/lang/pl-pl.json index 17701a5981..7b96217cca 100644 --- a/src/lang/pl-pl.json +++ b/src/lang/pl-pl.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/pt-br.json b/src/lang/pt-br.json index c8b254054e..874a7538c9 100644 --- a/src/lang/pt-br.json +++ b/src/lang/pt-br.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/pu-in.json b/src/lang/pu-in.json index 0edcc860fe..9dac5441ce 100644 --- a/src/lang/pu-in.json +++ b/src/lang/pu-in.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/ru-ru.json b/src/lang/ru-ru.json index 15b2eac0bf..855c7100cf 100644 --- a/src/lang/ru-ru.json +++ b/src/lang/ru-ru.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/tl-ph.json b/src/lang/tl-ph.json index 2cdf3e1624..61d62bffc2 100644 --- a/src/lang/tl-ph.json +++ b/src/lang/tl-ph.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/tr-tr.json b/src/lang/tr-tr.json index 3265917f4c..2e1f91446c 100644 --- a/src/lang/tr-tr.json +++ b/src/lang/tr-tr.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/uk-ua.json b/src/lang/uk-ua.json index eb89ac7897..090ae544cf 100644 --- a/src/lang/uk-ua.json +++ b/src/lang/uk-ua.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/uz-uz.json b/src/lang/uz-uz.json index fab0acf10e..c7f88ca6b6 100644 --- a/src/lang/uz-uz.json +++ b/src/lang/uz-uz.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/vi-vn.json b/src/lang/vi-vn.json index 08f8757177..cca52209e5 100644 --- a/src/lang/vi-vn.json +++ b/src/lang/vi-vn.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/zh-cn.json b/src/lang/zh-cn.json index b94eefb281..57619cf494 100644 --- a/src/lang/zh-cn.json +++ b/src/lang/zh-cn.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/zh-hant.json b/src/lang/zh-hant.json index defd18ef09..f19f2ab8ad 100644 --- a/src/lang/zh-hant.json +++ b/src/lang/zh-hant.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lang/zh-tw.json b/src/lang/zh-tw.json index 3d62702441..7992ae06f8 100644 --- a/src/lang/zh-tw.json +++ b/src/lang/zh-tw.json @@ -891,5 +891,10 @@ "editor renderer": "Renderer", "editor renderer dom": "DOM (default)", "editor renderer canvas": "Canvas (experimental)", - "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload." + "settings-info-editor-renderer": "Canvas draws code with the GPU for smoother scrolling in large files. It is experimental: some plugins may not work with it. Applies after a reload.", + "canvas backend": "Canvas backend", + "canvas backend auto": "Auto (WebGL2)", + "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", + "sticky scroll": "Sticky scroll", + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." } diff --git a/src/lib/editorManager.js b/src/lib/editorManager.js index 219b942dc2..c9a7867370 100644 --- a/src/lib/editorManager.js +++ b/src/lib/editorManager.js @@ -30,6 +30,7 @@ import { } from "@emmetio/codemirror6-plugin"; import createBaseExtensions from "cm/baseExtensions"; import { + getCanvasBackend, getCanvasDisplayOptions, getCanvasFont, getCanvasTheme, @@ -1475,6 +1476,7 @@ async function EditorManager($header, $body) { ...display, font, theme: getCanvasTheme(themeId), + backend: getCanvasBackend(appSettings.value), input: appSettings.value.useTextareaForIME ? "textarea" : "auto", touchSelection: { onMenu: (event) => @@ -3802,6 +3804,7 @@ async function EditorManager($header, $body) { // highlightActiveLine when the state is recreated. appSettings.on("update:highlightActiveLine", syncAllCanvasViews); appSettings.on("update:scrollSpeed", syncAllCanvasViews); + appSettings.on("update:stickyScroll", syncAllCanvasViews); // Keep file.session and cache in sync on every edit function getDocSyncListener() { diff --git a/src/lib/settings.js b/src/lib/settings.js index c6e9b53332..64970a588f 100644 --- a/src/lib/settings.js +++ b/src/lib/settings.js @@ -134,6 +134,8 @@ class Settings { cursorWidth: 2, editorTheme: "one_dark", editorRenderer: "dom", + canvasBackend: "auto", + stickyScroll: false, textWrap: false, wrappingIndent: "same", softTab: true, diff --git a/src/settings/editorSettings.js b/src/settings/editorSettings.js index e3b163b761..0716f82bad 100644 --- a/src/settings/editorSettings.js +++ b/src/settings/editorSettings.js @@ -12,6 +12,11 @@ export default function editorSettings() { ["dom", strings["editor renderer dom"]], ["canvas", strings["editor renderer canvas"]], ]; + const canvasBackendOptions = [ + ["auto", strings["canvas backend auto"]], + ["webgl2", "WebGL2"], + ["webgpu", "WebGPU"], + ]; const wrappingIndentOptions = [ ["none", strings.none], ["same", strings["wrap-indent-same"]], @@ -45,6 +50,24 @@ export default function editorSettings() { info: strings["settings-info-editor-renderer"], category: categories.textLayout, }, + { + key: "canvasBackend", + text: strings["canvas backend"], + value: values.canvasBackend ?? "auto", + valueText: (value) => + canvasBackendOptions.find(([key]) => key === value)?.[1] ?? + canvasBackendOptions[0][1], + select: canvasBackendOptions, + info: strings["settings-info-canvas-backend"], + category: categories.textLayout, + }, + { + key: "stickyScroll", + text: strings["sticky scroll"], + checkbox: values.stickyScroll ?? false, + info: strings["settings-info-sticky-scroll"], + category: categories.textLayout, + }, { key: "editorFont", text: strings["editor font"], @@ -292,10 +315,16 @@ export default function editorSettings() { return; case "editorRenderer": - // Editors are created with their renderer: reload to switch. - if (value === appSettings.value.editorRenderer) return; + case "canvasBackend": + // Editors are created with their renderer and backend: reload + // to switch (a backend change only matters to canvas editors). + if (value === appSettings.value[key]) return; await appSettings.update({ [key]: value }); - if (await confirm(strings.info, strings["reload to apply"])) { + if ( + (key === "editorRenderer" || + appSettings.value.editorRenderer === "canvas") && + (await confirm(strings.info, strings["reload to apply"])) + ) { location.reload(); } return; From 69265c8914915a701fff2da390c5ce352bd8587c Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Sun, 25 Oct 2026 10:22:54 +0530 Subject: [PATCH 12/14] build: use @acode.app/canvas-view from npm canvas-view is now published as @acode.app/canvas-view, so depend on it instead of a local `bun link` checkout and drop the rspack and tsconfig workarounds that kept the linked copy on Acode's CodeMirror packages. Raise @codemirror/view, state, language and @lezer/common to the versions canvas-view requires, in dependencies and overrides, so a single copy of each is installed. The newer view flushes on every selectionchange, and happy-dom fires that event synchronously from Selection methods, which re-entered EditorView.update in the read-only cursor test. Queue it there like a browser does. Co-Authored-By: Claude Opus 5.5 --- bun.lock | 236 +++--------------------------- package-lock.json | 54 +++++-- package.json | 18 +-- rspack.config.js | 17 +-- src/cm/canvasOptions.ts | 4 +- src/cm/colorChip.ts | 4 +- src/cm/createEditorView.ts | 4 +- src/cm/editorReadOnly.ts | 2 +- src/cm/lsp/diagnostics.ts | 2 +- src/cm/touchSelectionMenu.js | 2 +- tests/unit/editorReadOnly.test.ts | 22 ++- tsconfig.json | 6 - 12 files changed, 96 insertions(+), 275 deletions(-) diff --git a/bun.lock b/bun.lock index 11ccb7ae77..7101931768 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,7 @@ "": { "name": "com.foxdebug.acode", "dependencies": { - "@acode/canvas-view": "link:@acode/canvas-view", + "@acode.app/canvas-view": "^0.0.1", "@codemirror/autocomplete": "^6.20.3", "@codemirror/commands": "^6.11.0", "@codemirror/lang-angular": "^0.1.4", @@ -29,18 +29,18 @@ "@codemirror/lang-wast": "^6.0.2", "@codemirror/lang-xml": "^6.1.0", "@codemirror/lang-yaml": "^6.1.3", - "@codemirror/language": "^6.12.4", + "@codemirror/language": "^6.13.1", "@codemirror/language-data": "^6.5.2", "@codemirror/legacy-modes": "^6.5.3", "@codemirror/lint": "^6.9.7", "@codemirror/lsp-client": "file:codemirror-lsp-client", "@codemirror/search": "^6.7.1", - "@codemirror/state": "^6.7.1", + "@codemirror/state": "^6.7.6", "@codemirror/theme-one-dark": "^6.1.3", - "@codemirror/view": "^6.43.9", + "@codemirror/view": "^6.43.14", "@deadlyjack/ajax": "^1.2.6", "@emmetio/codemirror6-plugin": "^0.4.1", - "@lezer/common": "^1.5.2", + "@lezer/common": "^1.5.4", "@lezer/highlight": "^1.2.5", "@lezer/lr": "^1.4.10", "@ungap/custom-elements": "^1.3.0", @@ -150,17 +150,17 @@ "overrides": { "@codemirror/autocomplete": "^6.20.3", "@codemirror/commands": "^6.11.0", - "@codemirror/language": "^6.12.4", + "@codemirror/language": "^6.13.1", "@codemirror/lint": "^6.9.7", "@codemirror/search": "^6.7.1", - "@codemirror/state": "^6.7.1", - "@codemirror/view": "^6.43.9", - "@lezer/common": "^1.5.2", + "@codemirror/state": "^6.7.6", + "@codemirror/view": "^6.43.14", + "@lezer/common": "^1.5.4", "@lezer/lr": "^1.4.10", "tar": "^7.5.22", }, "packages": { - "@acode/canvas-view": ["@acode/canvas-view@link:@acode/canvas-view", {}], + "@acode.app/canvas-view": ["@acode.app/canvas-view@0.0.1", "", { "dependencies": { "@codemirror/language": "^6.13.1", "@codemirror/state": "^6.7.6", "@codemirror/view": "^6.43.14", "@lezer/common": "^1.5.4", "@lezer/highlight": "^1.2.5", "style-mod": "^4.1.2" } }, "sha512-brS4L24KCMJUOGXgzwSl2AVv2kpStleD85fLtEXaxPpn0jroRrXmyid2aSqZv4bitralRVu8huzhosDSl9HSww=="], "@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="], @@ -420,7 +420,7 @@ "@codemirror/lang-yaml": ["@codemirror/lang-yaml@6.1.3", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.2.0", "@lezer/lr": "^1.0.0", "@lezer/yaml": "^1.0.0" } }, "sha512-AZ8DJBuXGVHybpBQhmZtgew5//4hv3tdkXnr3vDmOUMJRuB6vn/uuwtmTOTlqEaQFg3hQSVeA90NmvIQyUV6FQ=="], - "@codemirror/language": ["@codemirror/language@6.12.4", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A=="], + "@codemirror/language": ["@codemirror/language@6.13.1", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/streamparser": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-zfvU2r3TyylQLMj/te+5CnQ0rSqZi8B1oTCBbQkagcLW4Xc6imab6qN+pEJhsuNOLaJqiRyBXp9UhWA1DhVvDw=="], "@codemirror/language-data": ["@codemirror/language-data@6.5.2", "", { "dependencies": { "@codemirror/lang-angular": "^0.1.0", "@codemirror/lang-cpp": "^6.0.0", "@codemirror/lang-css": "^6.0.0", "@codemirror/lang-go": "^6.0.0", "@codemirror/lang-html": "^6.0.0", "@codemirror/lang-java": "^6.0.0", "@codemirror/lang-javascript": "^6.0.0", "@codemirror/lang-jinja": "^6.0.0", "@codemirror/lang-json": "^6.0.0", "@codemirror/lang-less": "^6.0.0", "@codemirror/lang-liquid": "^6.0.0", "@codemirror/lang-markdown": "^6.0.0", "@codemirror/lang-php": "^6.0.0", "@codemirror/lang-python": "^6.0.0", "@codemirror/lang-rust": "^6.0.0", "@codemirror/lang-sass": "^6.0.0", "@codemirror/lang-sql": "^6.0.0", "@codemirror/lang-vue": "^0.1.1", "@codemirror/lang-wast": "^6.0.0", "@codemirror/lang-xml": "^6.0.0", "@codemirror/lang-yaml": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/legacy-modes": "^6.4.0" } }, "sha512-CPkWBKrNS8stYbEU5kwBwTf3JB1kghlbh4FSAwzGW2TEscdeHHH4FGysREW86Mqnj3Qn09s0/6Ea/TutmoTobg=="], @@ -432,11 +432,13 @@ "@codemirror/search": ["@codemirror/search@6.7.1", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.37.0", "crelt": "^1.0.5" } }, "sha512-uMe5UO6PamJtSHrXhhHOzSX3ReWtiJrva6GnPMwSOrZtiExb5X5eExhr2OUZQVvdxPsKpY3Ro2mFbQadpPWmHA=="], - "@codemirror/state": ["@codemirror/state@6.7.1", "", { "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A=="], + "@codemirror/state": ["@codemirror/state@6.7.6", "", { "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "sha512-kAz+AncRtKuIknedxT1bq4XwXv4UowhbkHU1myPrtVb/jZtImWuV5BXzv5vK6i3kYACsdiZiQKFQQ5Mq7elW8w=="], + + "@codemirror/streamparser": ["@codemirror/streamparser@6.0.0", "", {}, "sha512-JTFPF6xrfjt0mnfGPgzcLiMwYmvIyNaoizp8BYzv8FE8KrT6GBGDZ190rtt+O7Wke3gG/MFHyiY9YGNeimof4w=="], "@codemirror/theme-one-dark": ["@codemirror/theme-one-dark@6.1.3", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/highlight": "^1.0.0" } }, "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA=="], - "@codemirror/view": ["@codemirror/view@6.43.9", "", { "dependencies": { "@codemirror/state": "^6.7.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-sTuUzTpPMFebRhg6dawChoKKgndIwfjmJgKVxBefPElcU2NwQ6AFroupk0SFqEerQyZOGRfDNnSN8Dw/lMAsXw=="], + "@codemirror/view": ["@codemirror/view@6.43.15", "", { "dependencies": { "@codemirror/state": "^6.7.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-HHsfpqFeHyXLs983pSRe38Xajs0vFWZQ2BuX0m7Ki8w3jZ5iD8cuR3lBz2/5/X53Dhf32SBHKh6rtSTB6tKeGw=="], "@deadlyjack/ajax": ["@deadlyjack/ajax@1.2.6", "", {}, "sha512-VwZU8YUflO2/V/dl3dluu+3jg8Ghz/W5fwxD5Z21OZXKeV73d+vStKVBe4wi+Av2KbTR35K7Z+5Q3iIpjB41MA=="], @@ -478,7 +480,7 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - "@lezer/common": ["@lezer/common@1.5.2", "", {}, "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ=="], + "@lezer/common": ["@lezer/common@1.5.4", "", {}, "sha512-xt3Q4z64xM+xhg4M7NXqw1N//1O74bUFo5kz6fwMeC4qwxSuRdwkYSmjcqCEEKYkNw5e67f2hcNqBoNERAEU0w=="], "@lezer/cpp": ["@lezer/cpp@1.1.5", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-DIhSXmYtJKLehrjzDFN+2cPt547ySQ41nA8yqcDf/GxMc+YM736xqltFkvADL2M0VebU5I+3+4ks2Vv+Kyq3Aw=="], @@ -1990,48 +1992,22 @@ "@babel/generator/@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], - "@babel/helper-annotate-as-pure/@babel/types": ["@babel/types@8.0.0", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.0" } }, "sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw=="], - - "@babel/helper-compilation-targets/semver": ["semver@7.8.4", "", { "bin": "bin/semver.js" }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="], - "@babel/helper-create-class-features-plugin/@babel/traverse": ["@babel/traverse@8.0.0", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/generator": "^8.0.0", "@babel/helper-globals": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/template": "^8.0.0", "@babel/types": "^8.0.0", "obug": "^2.1.1" } }, "sha512-bxTj/W2VclGE6CctlfQOpxg8MPDzXArRqkOBePw8EHfebcjF7fETWSS3BriEECo+UiU/Yblq+xUtSImFu7cTbw=="], - "@babel/helper-create-class-features-plugin/semver": ["semver@7.8.4", "", { "bin": "bin/semver.js" }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="], - - "@babel/helper-create-regexp-features-plugin/semver": ["semver@7.8.4", "", { "bin": "bin/semver.js" }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="], - "@babel/helper-member-expression-to-functions/@babel/traverse": ["@babel/traverse@8.0.0", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/generator": "^8.0.0", "@babel/helper-globals": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/template": "^8.0.0", "@babel/types": "^8.0.0", "obug": "^2.1.1" } }, "sha512-bxTj/W2VclGE6CctlfQOpxg8MPDzXArRqkOBePw8EHfebcjF7fETWSS3BriEECo+UiU/Yblq+xUtSImFu7cTbw=="], - "@babel/helper-member-expression-to-functions/@babel/types": ["@babel/types@8.0.0", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.0" } }, "sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw=="], - "@babel/helper-module-imports/@babel/traverse": ["@babel/traverse@8.0.0", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/generator": "^8.0.0", "@babel/helper-globals": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/template": "^8.0.0", "@babel/types": "^8.0.0", "obug": "^2.1.1" } }, "sha512-bxTj/W2VclGE6CctlfQOpxg8MPDzXArRqkOBePw8EHfebcjF7fETWSS3BriEECo+UiU/Yblq+xUtSImFu7cTbw=="], - "@babel/helper-module-imports/@babel/types": ["@babel/types@8.0.0", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.0" } }, "sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw=="], - - "@babel/helper-module-transforms/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - "@babel/helper-module-transforms/@babel/traverse": ["@babel/traverse@8.0.0", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/generator": "^8.0.0", "@babel/helper-globals": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/template": "^8.0.0", "@babel/types": "^8.0.0", "obug": "^2.1.1" } }, "sha512-bxTj/W2VclGE6CctlfQOpxg8MPDzXArRqkOBePw8EHfebcjF7fETWSS3BriEECo+UiU/Yblq+xUtSImFu7cTbw=="], - "@babel/helper-optimise-call-expression/@babel/types": ["@babel/types@8.0.0", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.0" } }, "sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw=="], - "@babel/helper-remap-async-to-generator/@babel/traverse": ["@babel/traverse@8.0.0", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/generator": "^8.0.0", "@babel/helper-globals": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/template": "^8.0.0", "@babel/types": "^8.0.0", "obug": "^2.1.1" } }, "sha512-bxTj/W2VclGE6CctlfQOpxg8MPDzXArRqkOBePw8EHfebcjF7fETWSS3BriEECo+UiU/Yblq+xUtSImFu7cTbw=="], "@babel/helper-replace-supers/@babel/traverse": ["@babel/traverse@8.0.0", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/generator": "^8.0.0", "@babel/helper-globals": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/template": "^8.0.0", "@babel/types": "^8.0.0", "obug": "^2.1.1" } }, "sha512-bxTj/W2VclGE6CctlfQOpxg8MPDzXArRqkOBePw8EHfebcjF7fETWSS3BriEECo+UiU/Yblq+xUtSImFu7cTbw=="], "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse": ["@babel/traverse@8.0.0", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/generator": "^8.0.0", "@babel/helper-globals": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/template": "^8.0.0", "@babel/types": "^8.0.0", "obug": "^2.1.1" } }, "sha512-bxTj/W2VclGE6CctlfQOpxg8MPDzXArRqkOBePw8EHfebcjF7fETWSS3BriEECo+UiU/Yblq+xUtSImFu7cTbw=="], - "@babel/helper-skip-transparent-expression-wrappers/@babel/types": ["@babel/types@8.0.0", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.0" } }, "sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw=="], - - "@babel/helper-wrap-function/@babel/template": ["@babel/template@8.0.0", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0" } }, "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ=="], - "@babel/helper-wrap-function/@babel/traverse": ["@babel/traverse@8.0.0", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/generator": "^8.0.0", "@babel/helper-globals": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/template": "^8.0.0", "@babel/types": "^8.0.0", "obug": "^2.1.1" } }, "sha512-bxTj/W2VclGE6CctlfQOpxg8MPDzXArRqkOBePw8EHfebcjF7fETWSS3BriEECo+UiU/Yblq+xUtSImFu7cTbw=="], - "@babel/helper-wrap-function/@babel/types": ["@babel/types@8.0.0", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.0" } }, "sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw=="], - - "@babel/helpers/@babel/template": ["@babel/template@8.0.0", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0" } }, "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ=="], - - "@babel/helpers/@babel/types": ["@babel/types@8.0.0", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.0" } }, "sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw=="], - "@babel/parser/@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], "@babel/plugin-transform-async-generator-functions/@babel/traverse": ["@babel/traverse@8.0.0", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/generator": "^8.0.0", "@babel/helper-globals": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/template": "^8.0.0", "@babel/types": "^8.0.0", "obug": "^2.1.1" } }, "sha512-bxTj/W2VclGE6CctlfQOpxg8MPDzXArRqkOBePw8EHfebcjF7fETWSS3BriEECo+UiU/Yblq+xUtSImFu7cTbw=="], @@ -2040,10 +2016,6 @@ "@babel/plugin-transform-classes/@babel/traverse": ["@babel/traverse@8.0.0", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/generator": "^8.0.0", "@babel/helper-globals": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/template": "^8.0.0", "@babel/types": "^8.0.0", "obug": "^2.1.1" } }, "sha512-bxTj/W2VclGE6CctlfQOpxg8MPDzXArRqkOBePw8EHfebcjF7fETWSS3BriEECo+UiU/Yblq+xUtSImFu7cTbw=="], - "@babel/plugin-transform-modules-systemjs/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - - "@babel/preset-env/semver": ["semver@7.8.4", "", { "bin": "bin/semver.js" }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="], - "@babel/preset-modules/@babel/types": ["@babel/types@8.0.4", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.4" } }, "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g=="], "@babel/template/@babel/parser": ["@babel/parser@8.0.0", "", { "dependencies": { "@babel/types": "^8.0.0" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ=="], @@ -2140,8 +2112,6 @@ "ignore-walk/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], - "jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], - "jshint/minimatch": ["minimatch@3.0.8", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q=="], "jshint/strip-json-comments": ["strip-json-comments@1.0.4", "", { "bin": "cli.js" }, "sha512-AOPG8EBc5wAikaG1/7uFCNFJwnKOuQwFTpYBdTW6OvWHeZBQBrAA/amefHGrEiOnCPcLFZK6FUPtWVKpQVIRgg=="], @@ -2192,8 +2162,6 @@ "raw-loader/schema-utils": ["schema-utils@3.3.0", "", { "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" } }, "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg=="], - "rollup-plugin-dts/@babel/code-frame": ["@babel/code-frame@8.0.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^8.0.0", "js-tokens": "^10.0.0" } }, "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw=="], - "sass/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], @@ -2212,156 +2180,68 @@ "@babel/generator/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], - "@babel/helper-annotate-as-pure/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="], - - "@babel/helper-annotate-as-pure/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - - "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/code-frame": ["@babel/code-frame@8.0.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^8.0.0", "js-tokens": "^10.0.0" } }, "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw=="], - "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/generator": ["@babel/generator@8.0.0", "", { "dependencies": { "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", "jsesc": "^3.0.2" } }, "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g=="], "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/helper-globals": ["@babel/helper-globals@8.0.0", "", {}, "sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw=="], "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/parser": ["@babel/parser@8.0.0", "", { "dependencies": { "@babel/types": "^8.0.0" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ=="], - "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/template": ["@babel/template@8.0.0", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0" } }, "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ=="], - - "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/types": ["@babel/types@8.0.0", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.0" } }, "sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw=="], - - "@babel/helper-member-expression-to-functions/@babel/traverse/@babel/code-frame": ["@babel/code-frame@8.0.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^8.0.0", "js-tokens": "^10.0.0" } }, "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw=="], - "@babel/helper-member-expression-to-functions/@babel/traverse/@babel/generator": ["@babel/generator@8.0.0", "", { "dependencies": { "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", "jsesc": "^3.0.2" } }, "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g=="], "@babel/helper-member-expression-to-functions/@babel/traverse/@babel/helper-globals": ["@babel/helper-globals@8.0.0", "", {}, "sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw=="], "@babel/helper-member-expression-to-functions/@babel/traverse/@babel/parser": ["@babel/parser@8.0.0", "", { "dependencies": { "@babel/types": "^8.0.0" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ=="], - "@babel/helper-member-expression-to-functions/@babel/traverse/@babel/template": ["@babel/template@8.0.0", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0" } }, "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ=="], - - "@babel/helper-member-expression-to-functions/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="], - - "@babel/helper-member-expression-to-functions/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - - "@babel/helper-module-imports/@babel/traverse/@babel/code-frame": ["@babel/code-frame@8.0.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^8.0.0", "js-tokens": "^10.0.0" } }, "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw=="], - "@babel/helper-module-imports/@babel/traverse/@babel/generator": ["@babel/generator@8.0.0", "", { "dependencies": { "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", "jsesc": "^3.0.2" } }, "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g=="], "@babel/helper-module-imports/@babel/traverse/@babel/helper-globals": ["@babel/helper-globals@8.0.0", "", {}, "sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw=="], "@babel/helper-module-imports/@babel/traverse/@babel/parser": ["@babel/parser@8.0.0", "", { "dependencies": { "@babel/types": "^8.0.0" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ=="], - "@babel/helper-module-imports/@babel/traverse/@babel/template": ["@babel/template@8.0.0", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0" } }, "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ=="], - - "@babel/helper-module-imports/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="], - - "@babel/helper-module-imports/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - - "@babel/helper-module-transforms/@babel/traverse/@babel/code-frame": ["@babel/code-frame@8.0.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^8.0.0", "js-tokens": "^10.0.0" } }, "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw=="], - "@babel/helper-module-transforms/@babel/traverse/@babel/generator": ["@babel/generator@8.0.0", "", { "dependencies": { "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", "jsesc": "^3.0.2" } }, "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g=="], "@babel/helper-module-transforms/@babel/traverse/@babel/helper-globals": ["@babel/helper-globals@8.0.0", "", {}, "sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw=="], "@babel/helper-module-transforms/@babel/traverse/@babel/parser": ["@babel/parser@8.0.0", "", { "dependencies": { "@babel/types": "^8.0.0" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ=="], - "@babel/helper-module-transforms/@babel/traverse/@babel/template": ["@babel/template@8.0.0", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0" } }, "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ=="], - - "@babel/helper-module-transforms/@babel/traverse/@babel/types": ["@babel/types@8.0.0", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.0" } }, "sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw=="], - - "@babel/helper-optimise-call-expression/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="], - - "@babel/helper-optimise-call-expression/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - - "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/code-frame": ["@babel/code-frame@8.0.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^8.0.0", "js-tokens": "^10.0.0" } }, "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw=="], - "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/generator": ["@babel/generator@8.0.0", "", { "dependencies": { "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", "jsesc": "^3.0.2" } }, "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g=="], "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/helper-globals": ["@babel/helper-globals@8.0.0", "", {}, "sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw=="], "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/parser": ["@babel/parser@8.0.0", "", { "dependencies": { "@babel/types": "^8.0.0" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ=="], - "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/template": ["@babel/template@8.0.0", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0" } }, "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ=="], - - "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/types": ["@babel/types@8.0.0", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.0" } }, "sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw=="], - - "@babel/helper-replace-supers/@babel/traverse/@babel/code-frame": ["@babel/code-frame@8.0.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^8.0.0", "js-tokens": "^10.0.0" } }, "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw=="], - "@babel/helper-replace-supers/@babel/traverse/@babel/generator": ["@babel/generator@8.0.0", "", { "dependencies": { "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", "jsesc": "^3.0.2" } }, "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g=="], "@babel/helper-replace-supers/@babel/traverse/@babel/helper-globals": ["@babel/helper-globals@8.0.0", "", {}, "sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw=="], "@babel/helper-replace-supers/@babel/traverse/@babel/parser": ["@babel/parser@8.0.0", "", { "dependencies": { "@babel/types": "^8.0.0" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ=="], - "@babel/helper-replace-supers/@babel/traverse/@babel/template": ["@babel/template@8.0.0", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0" } }, "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ=="], - - "@babel/helper-replace-supers/@babel/traverse/@babel/types": ["@babel/types@8.0.0", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.0" } }, "sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw=="], - - "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/code-frame": ["@babel/code-frame@8.0.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^8.0.0", "js-tokens": "^10.0.0" } }, "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw=="], - "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/generator": ["@babel/generator@8.0.0", "", { "dependencies": { "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", "jsesc": "^3.0.2" } }, "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g=="], "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/helper-globals": ["@babel/helper-globals@8.0.0", "", {}, "sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw=="], "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/parser": ["@babel/parser@8.0.0", "", { "dependencies": { "@babel/types": "^8.0.0" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ=="], - "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/template": ["@babel/template@8.0.0", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0" } }, "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ=="], - - "@babel/helper-skip-transparent-expression-wrappers/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="], - - "@babel/helper-skip-transparent-expression-wrappers/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - - "@babel/helper-wrap-function/@babel/template/@babel/code-frame": ["@babel/code-frame@8.0.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^8.0.0", "js-tokens": "^10.0.0" } }, "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw=="], - - "@babel/helper-wrap-function/@babel/template/@babel/parser": ["@babel/parser@8.0.0", "", { "dependencies": { "@babel/types": "^8.0.0" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ=="], - - "@babel/helper-wrap-function/@babel/traverse/@babel/code-frame": ["@babel/code-frame@8.0.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^8.0.0", "js-tokens": "^10.0.0" } }, "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw=="], - "@babel/helper-wrap-function/@babel/traverse/@babel/generator": ["@babel/generator@8.0.0", "", { "dependencies": { "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", "jsesc": "^3.0.2" } }, "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g=="], "@babel/helper-wrap-function/@babel/traverse/@babel/helper-globals": ["@babel/helper-globals@8.0.0", "", {}, "sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw=="], "@babel/helper-wrap-function/@babel/traverse/@babel/parser": ["@babel/parser@8.0.0", "", { "dependencies": { "@babel/types": "^8.0.0" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ=="], - "@babel/helper-wrap-function/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="], - - "@babel/helper-wrap-function/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - - "@babel/helpers/@babel/template/@babel/code-frame": ["@babel/code-frame@8.0.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^8.0.0", "js-tokens": "^10.0.0" } }, "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw=="], - - "@babel/helpers/@babel/template/@babel/parser": ["@babel/parser@8.0.0", "", { "dependencies": { "@babel/types": "^8.0.0" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ=="], - - "@babel/helpers/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="], - - "@babel/helpers/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - "@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], "@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], - "@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/code-frame": ["@babel/code-frame@8.0.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^8.0.0", "js-tokens": "^10.0.0" } }, "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw=="], - "@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/generator": ["@babel/generator@8.0.0", "", { "dependencies": { "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", "jsesc": "^3.0.2" } }, "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g=="], "@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/helper-globals": ["@babel/helper-globals@8.0.0", "", {}, "sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw=="], "@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/parser": ["@babel/parser@8.0.0", "", { "dependencies": { "@babel/types": "^8.0.0" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ=="], - "@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/template": ["@babel/template@8.0.0", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0" } }, "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ=="], - - "@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/types": ["@babel/types@8.0.0", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.0" } }, "sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw=="], - - "@babel/plugin-transform-classes/@babel/traverse/@babel/code-frame": ["@babel/code-frame@8.0.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^8.0.0", "js-tokens": "^10.0.0" } }, "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw=="], - "@babel/plugin-transform-classes/@babel/traverse/@babel/generator": ["@babel/generator@8.0.0", "", { "dependencies": { "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", "jsesc": "^3.0.2" } }, "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g=="], "@babel/plugin-transform-classes/@babel/traverse/@babel/parser": ["@babel/parser@8.0.0", "", { "dependencies": { "@babel/types": "^8.0.0" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ=="], - "@babel/plugin-transform-classes/@babel/traverse/@babel/template": ["@babel/template@8.0.0", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0" } }, "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ=="], - - "@babel/plugin-transform-classes/@babel/traverse/@babel/types": ["@babel/types@8.0.0", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.0" } }, "sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw=="], - - "@babel/preset-modules/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="], - "@babel/preset-modules/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.4", "", {}, "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg=="], "@babel/traverse/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], @@ -2388,10 +2268,6 @@ "@marijn/testtool/mocha/serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="], - "@marijn/testtool/mocha/strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], - - "@marijn/testtool/mocha/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], - "@marijn/testtool/mocha/workerpool": ["workerpool@6.5.1", "", {}, "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA=="], "@marijn/testtool/mocha/yargs": ["yargs@16.2.2", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w=="], @@ -2424,6 +2300,8 @@ "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="], + "glob/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + "htmlparser2/readable-stream/isarray": ["isarray@0.0.1", "", {}, "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="], "htmlparser2/readable-stream/string_decoder": ["string_decoder@0.10.31", "", {}, "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="], @@ -2454,82 +2332,10 @@ "raw-loader/schema-utils/ajv-keywords": ["ajv-keywords@3.5.2", "", { "peerDependencies": { "ajv": "^6.9.1" } }, "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ=="], - "rollup-plugin-dts/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.4", "", {}, "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg=="], - - "rollup-plugin-dts/@babel/code-frame/js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], - "sass/chokidar/readdirp": ["readdirp@5.1.1", "", {}, "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA=="], "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - - "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/code-frame/js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], - - "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="], - - "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - - "@babel/helper-member-expression-to-functions/@babel/traverse/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - - "@babel/helper-member-expression-to-functions/@babel/traverse/@babel/code-frame/js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], - - "@babel/helper-module-imports/@babel/traverse/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - - "@babel/helper-module-imports/@babel/traverse/@babel/code-frame/js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], - - "@babel/helper-module-transforms/@babel/traverse/@babel/code-frame/js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], - - "@babel/helper-module-transforms/@babel/traverse/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="], - - "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - - "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/code-frame/js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], - - "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="], - - "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - - "@babel/helper-replace-supers/@babel/traverse/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - - "@babel/helper-replace-supers/@babel/traverse/@babel/code-frame/js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], - - "@babel/helper-replace-supers/@babel/traverse/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="], - - "@babel/helper-replace-supers/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - - "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - - "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/code-frame/js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], - - "@babel/helper-wrap-function/@babel/template/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - - "@babel/helper-wrap-function/@babel/template/@babel/code-frame/js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], - - "@babel/helper-wrap-function/@babel/traverse/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - - "@babel/helper-wrap-function/@babel/traverse/@babel/code-frame/js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], - - "@babel/helpers/@babel/template/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - - "@babel/helpers/@babel/template/@babel/code-frame/js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], - - "@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - - "@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/code-frame/js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], - - "@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="], - - "@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - - "@babel/plugin-transform-classes/@babel/traverse/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - - "@babel/plugin-transform-classes/@babel/traverse/@babel/code-frame/js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], - - "@babel/plugin-transform-classes/@babel/traverse/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="], - - "@babel/plugin-transform-classes/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0", "", {}, "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g=="], - "@marijn/testtool/mocha/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], "@marijn/testtool/mocha/minimatch/brace-expansion": ["brace-expansion@2.1.7", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g=="], @@ -2552,8 +2358,6 @@ "ignore-walk/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - "jshint/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "mocha/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "mocha/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -2568,8 +2372,6 @@ "@marijn/testtool/mocha/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], - "@marijn/testtool/mocha/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "@marijn/testtool/mocha/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "@marijn/testtool/mocha/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], @@ -2582,8 +2384,6 @@ "cacache/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - "cli/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "mocha/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "mocha/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], diff --git a/package-lock.json b/package-lock.json index 41fccea233..002eea0e62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.13.5", "license": "MIT", "dependencies": { + "@acode.app/canvas-view": "^0.0.1", "@codemirror/autocomplete": "^6.20.3", "@codemirror/commands": "^6.11.0", "@codemirror/lang-angular": "^0.1.4", @@ -32,18 +33,18 @@ "@codemirror/lang-wast": "^6.0.2", "@codemirror/lang-xml": "^6.1.0", "@codemirror/lang-yaml": "^6.1.3", - "@codemirror/language": "^6.12.4", + "@codemirror/language": "^6.13.1", "@codemirror/language-data": "^6.5.2", "@codemirror/legacy-modes": "^6.5.3", "@codemirror/lint": "^6.9.7", "@codemirror/lsp-client": "file:codemirror-lsp-client", "@codemirror/search": "^6.7.1", - "@codemirror/state": "^6.7.1", + "@codemirror/state": "^6.7.6", "@codemirror/theme-one-dark": "^6.1.3", - "@codemirror/view": "^6.43.9", + "@codemirror/view": "^6.43.14", "@deadlyjack/ajax": "^1.2.6", "@emmetio/codemirror6-plugin": "^0.4.1", - "@lezer/common": "^1.5.2", + "@lezer/common": "^1.5.4", "@lezer/highlight": "^1.2.5", "@lezer/lr": "^1.4.10", "@ungap/custom-elements": "^1.3.0", @@ -170,6 +171,20 @@ "typescript-language-server": "^4.3.4" } }, + "node_modules/@acode.app/canvas-view": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@acode.app/canvas-view/-/canvas-view-0.0.1.tgz", + "integrity": "sha512-brS4L24KCMJUOGXgzwSl2AVv2kpStleD85fLtEXaxPpn0jroRrXmyid2aSqZv4bitralRVu8huzhosDSl9HSww==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.13.1", + "@codemirror/state": "^6.7.6", + "@codemirror/view": "^6.43.14", + "@lezer/common": "^1.5.4", + "@lezer/highlight": "^1.2.5", + "style-mod": "^4.1.2" + } + }, "node_modules/@antfu/install-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", @@ -3971,12 +3986,13 @@ } }, "node_modules/@codemirror/language": { - "version": "6.12.4", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.4.tgz", - "integrity": "sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==", + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.13.1.tgz", + "integrity": "sha512-zfvU2r3TyylQLMj/te+5CnQ0rSqZi8B1oTCBbQkagcLW4Xc6imab6qN+pEJhsuNOLaJqiRyBXp9UhWA1DhVvDw==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.0.0", + "@codemirror/streamparser": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0", @@ -4051,14 +4067,20 @@ } }, "node_modules/@codemirror/state": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.1.tgz", - "integrity": "sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A==", + "version": "6.7.6", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.6.tgz", + "integrity": "sha512-kAz+AncRtKuIknedxT1bq4XwXv4UowhbkHU1myPrtVb/jZtImWuV5BXzv5vK6i3kYACsdiZiQKFQQ5Mq7elW8w==", "license": "MIT", "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, + "node_modules/@codemirror/streamparser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@codemirror/streamparser/-/streamparser-6.0.0.tgz", + "integrity": "sha512-JTFPF6xrfjt0mnfGPgzcLiMwYmvIyNaoizp8BYzv8FE8KrT6GBGDZ190rtt+O7Wke3gG/MFHyiY9YGNeimof4w==", + "license": "MIT" + }, "node_modules/@codemirror/theme-one-dark": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", @@ -4072,9 +4094,9 @@ } }, "node_modules/@codemirror/view": { - "version": "6.43.9", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.9.tgz", - "integrity": "sha512-sTuUzTpPMFebRhg6dawChoKKgndIwfjmJgKVxBefPElcU2NwQ6AFroupk0SFqEerQyZOGRfDNnSN8Dw/lMAsXw==", + "version": "6.43.15", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.15.tgz", + "integrity": "sha512-HHsfpqFeHyXLs983pSRe38Xajs0vFWZQ2BuX0m7Ki8w3jZ5iD8cuR3lBz2/5/X53Dhf32SBHKh6rtSTB6tKeGw==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.7.0", @@ -4282,9 +4304,9 @@ } }, "node_modules/@lezer/common": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", - "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.4.tgz", + "integrity": "sha512-xt3Q4z64xM+xhg4M7NXqw1N//1O74bUFo5kz6fwMeC4qwxSuRdwkYSmjcqCEEKYkNw5e67f2hcNqBoNERAEU0w==", "license": "MIT" }, "node_modules/@lezer/cpp": { diff --git a/package.json b/package.json index 5aa69608d0..92a04a5a70 100644 --- a/package.json +++ b/package.json @@ -125,7 +125,7 @@ "ws": "^8.21.3" }, "dependencies": { - "@acode/canvas-view": "link:@acode/canvas-view", + "@acode.app/canvas-view": "^0.0.1", "@codemirror/autocomplete": "^6.20.3", "@codemirror/commands": "^6.11.0", "@codemirror/lang-angular": "^0.1.4", @@ -149,18 +149,18 @@ "@codemirror/lang-wast": "^6.0.2", "@codemirror/lang-xml": "^6.1.0", "@codemirror/lang-yaml": "^6.1.3", - "@codemirror/language": "^6.12.4", + "@codemirror/language": "^6.13.1", "@codemirror/language-data": "^6.5.2", "@codemirror/legacy-modes": "^6.5.3", "@codemirror/lint": "^6.9.7", "@codemirror/lsp-client": "file:codemirror-lsp-client", "@codemirror/search": "^6.7.1", - "@codemirror/state": "^6.7.1", + "@codemirror/state": "^6.7.6", "@codemirror/theme-one-dark": "^6.1.3", - "@codemirror/view": "^6.43.9", + "@codemirror/view": "^6.43.14", "@deadlyjack/ajax": "^1.2.6", "@emmetio/codemirror6-plugin": "^0.4.1", - "@lezer/common": "^1.5.2", + "@lezer/common": "^1.5.4", "@lezer/highlight": "^1.2.5", "@lezer/lr": "^1.4.10", "@ungap/custom-elements": "^1.3.0", @@ -208,12 +208,12 @@ "overrides": { "@codemirror/autocomplete": "^6.20.3", "@codemirror/commands": "^6.11.0", - "@codemirror/language": "^6.12.4", + "@codemirror/language": "^6.13.1", "@codemirror/lint": "^6.9.7", "@codemirror/search": "^6.7.1", - "@codemirror/state": "^6.7.1", - "@codemirror/view": "^6.43.9", - "@lezer/common": "^1.5.2", + "@codemirror/state": "^6.7.6", + "@codemirror/view": "^6.43.14", + "@lezer/common": "^1.5.4", "@lezer/lr": "^1.4.10", "tar": "^7.5.22" }, diff --git a/rspack.config.js b/rspack.config.js index dcfca0aa1d..c811c6b778 100644 --- a/rspack.config.js +++ b/rspack.config.js @@ -12,19 +12,7 @@ module.exports = (env, options) => { ? ''.concat(devProto, '://', devHost, ':', devPort) : ''; - // @acode/canvas-view is linked from a local checkout (`bun link`), so its - // files resolve to their real path outside node_modules. It ships pre-built - // code, and its imports must resolve from Acode's node_modules: a second - // copy of @codemirror/state or view (the checkout's own) breaks facets. - const canvasViewDist = /[\\/]canvas-view[\\/]dist[\\/]/; - const rules = [ - { - test: canvasViewDist, - resolve: { - modules: [path.resolve(__dirname, 'node_modules')], - }, - }, { test: /typescript[\\/]lib[\\/]lib\..*\.d\.ts$/, type: 'asset/source', @@ -58,10 +46,7 @@ module.exports = (env, options) => { // dependency, so it resolves to its real path outside node_modules. It // still ships pre-built code and must not go through the source loaders. { - include: [ - /[\\/](?:node_modules|codemirror-lsp-client)[\\/]/, - canvasViewDist, - ], + include: /[\\/](?:node_modules|codemirror-lsp-client)[\\/]/, use: [ { loader: 'builtin:swc-loader', diff --git a/src/cm/canvasOptions.ts b/src/cm/canvasOptions.ts index 4d9eb7aa05..48f3b803e2 100644 --- a/src/cm/canvasOptions.ts +++ b/src/cm/canvasOptions.ts @@ -4,7 +4,7 @@ import { type CanvasViewConfig, type FontSpec, type TokenStyleSpec, -} from "@acode/canvas-view"; +} from "@acode.app/canvas-view"; import { HighlightStyle } from "@codemirror/language"; import { oneDark } from "@codemirror/theme-one-dark"; import { tags as t } from "@lezer/highlight"; @@ -12,7 +12,7 @@ import { getRainbowBracketColors } from "cm/rainbowBrackets"; import { getThemeById, getThemeConfig, getThemeExtensions } from "cm/themes"; /** - * Maps Acode's editor settings and themes to @acode/canvas-view options. + * Maps Acode's editor settings and themes to @acode.app/canvas-view options. * * The canvas renderer draws line numbers, fold markers, the active line, * indent guides, rainbow brackets, whitespace, wrapping and scroll room diff --git a/src/cm/colorChip.ts b/src/cm/colorChip.ts index d67210fec0..c319709472 100644 --- a/src/cm/colorChip.ts +++ b/src/cm/colorChip.ts @@ -1,4 +1,4 @@ -import type { CanvasWidgetSpec } from "@acode/canvas-view"; +import type { CanvasWidgetSpec } from "@acode.app/canvas-view"; import type { LSPClient } from "@codemirror/lsp-client"; import { LSPPlugin } from "@codemirror/lsp-client"; import type { Range } from "@codemirror/state"; @@ -75,7 +75,7 @@ export class ColorChipWidget extends WidgetType { } /** - * How a canvas view (@acode/canvas-view) draws the chip: the box + * How a canvas view (@acode.app/canvas-view) draws the chip: the box * `toDOM` styles, without making its element. The view only calls * `toDOM` when the chip is tapped, so the color picker still opens. */ diff --git a/src/cm/createEditorView.ts b/src/cm/createEditorView.ts index f2d4677866..bf3a2b1e2d 100644 --- a/src/cm/createEditorView.ts +++ b/src/cm/createEditorView.ts @@ -1,10 +1,10 @@ -import { CanvasView, type CanvasViewConfig } from "@acode/canvas-view"; +import { CanvasView, type CanvasViewConfig } from "@acode.app/canvas-view"; import type { EditorState } from "@codemirror/state"; import { EditorView } from "@codemirror/view"; /** * The editor renderers Acode can create. "dom" is CodeMirror's own - * EditorView; "canvas" is @acode/canvas-view, which draws the text with + * EditorView; "canvas" is @acode.app/canvas-view, which draws the text with * the GPU on CodeMirror's state (opt-in, experimental). */ export type EditorRenderer = "dom" | "canvas"; diff --git a/src/cm/editorReadOnly.ts b/src/cm/editorReadOnly.ts index c8bb3ae3b9..07f5e15700 100644 --- a/src/cm/editorReadOnly.ts +++ b/src/cm/editorReadOnly.ts @@ -77,7 +77,7 @@ const readOnlyCursor = [ ]; export interface EditorReadOnlyOptions { - /** The extension is for a canvas view (@acode/canvas-view). */ + /** The extension is for a canvas view (@acode.app/canvas-view). */ canvas?: boolean; } diff --git a/src/cm/lsp/diagnostics.ts b/src/cm/lsp/diagnostics.ts index 6e85eddd61..6768b27b2a 100644 --- a/src/cm/lsp/diagnostics.ts +++ b/src/cm/lsp/diagnostics.ts @@ -548,7 +548,7 @@ export function lspDiagnosticsUiExtension( { canvas = false }: { canvas?: boolean } = {}, ): Extension[] { // Touch devices get no in-text marks in the DOM editor. Canvas views - // (@acode/canvas-view) draw them natively (wavy underlines, lint + // (@acode.app/canvas-view) draw them natively (wavy underlines, lint // points) at no layout cost, so they keep them; tooltips stay off. const diagnosticsMarkerFilter = isCoarsePointerDevice() && !canvas ? () => [] : undefined; diff --git a/src/cm/touchSelectionMenu.js b/src/cm/touchSelectionMenu.js index 69e3c755ba..7f45f0afa2 100644 --- a/src/cm/touchSelectionMenu.js +++ b/src/cm/touchSelectionMenu.js @@ -177,7 +177,7 @@ export default function createTouchSelectionMenu(view, options = {}) { } /** - * The selection menu for a canvas editor (@acode/canvas-view). The view owns + * The selection menu for a canvas editor (@acode.app/canvas-view). The view owns * the touch selection gestures and handles, and asks for the menu through its * `touchSelection.onMenu` hook, which goes to `handleCanvasMenu()`. Only the * menu UI (items, layout, actions) is shared with the DOM editor's controller, diff --git a/tests/unit/editorReadOnly.test.ts b/tests/unit/editorReadOnly.test.ts index 1b67e10eb3..8893b2042f 100644 --- a/tests/unit/editorReadOnly.test.ts +++ b/tests/unit/editorReadOnly.test.ts @@ -11,10 +11,30 @@ import { resolveReadOnlyContextSelection, shouldCommitReadOnlyTap, } from "cm/editorReadOnly"; -import { afterEach, describe, expect, it } from "vitest"; +import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; const views: EditorView[] = []; +// happy-dom dispatches selectionchange synchronously from Selection methods, +// while browsers queue it as a task. CodeMirror's view flushes on every +// selectionchange, so a synchronous one fired while the view syncs its own +// DOM selection re-enters EditorView.update. Queue it like a browser does. +const dispatchDocumentEvent = document.dispatchEvent; + +beforeAll(() => { + document.dispatchEvent = function (event) { + if (event.type !== "selectionchange") { + return dispatchDocumentEvent.call(this, event); + } + setTimeout(() => dispatchDocumentEvent.call(this, event)); + return true; + }; +}); + +afterAll(() => { + document.dispatchEvent = dispatchDocumentEvent; +}); + afterEach(() => { while (views.length) views.pop()?.destroy(); document.body.replaceChildren(); diff --git a/tsconfig.json b/tsconfig.json index ce52141963..1a07510751 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,13 +15,7 @@ "declaration": false, "allowJs": true, "checkJs": false, - // Linked @acode/canvas-view must see Acode's CodeMirror types, not its checkout's. "paths": { - "@codemirror/language": ["./node_modules/@codemirror/language"], - "@codemirror/state": ["./node_modules/@codemirror/state"], - "@codemirror/view": ["./node_modules/@codemirror/view"], - "@lezer/common": ["./node_modules/@lezer/common"], - "@lezer/highlight": ["./node_modules/@lezer/highlight"], "*": ["./src/*"] }, "jsx": "preserve", From f1a77583e4cde7c791a203d5de4c5290012edced Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Sun, 25 Oct 2026 10:31:11 +0530 Subject: [PATCH 13/14] fix(editor): keep edits and DOM extensions across renderer changes - Flush pending cache writes and save the session before the reload that applies a renderer or canvas backend change, as the pause handler does, so recent edits aren't lost. - Include the renderer in the editor state signature, and give the DOM fallback pane a fresh empty state, so states built for canvas views (without fold controls and the active line) aren't reused by DOM ones. - Declare the canvas backend and sticky scroll strings in LangStrings. Co-Authored-By: Claude Opus 5.5 --- src/lang/index.d.ts | 5 +++++ src/lib/editorManager.js | 17 +++++++++++++---- src/settings/editorSettings.js | 9 ++++++++- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/lang/index.d.ts b/src/lang/index.d.ts index 2305bfac42..7a07566c79 100644 --- a/src/lang/index.d.ts +++ b/src/lang/index.d.ts @@ -895,6 +895,11 @@ declare type LangStrings = { "editor renderer dom": string; "editor renderer canvas": string; "settings-info-editor-renderer": string; + "canvas backend": string; + "canvas backend auto": string; + "settings-info-canvas-backend": string; + "sticky scroll": string; + "settings-info-sticky-scroll": string; }; declare var strings: LangStrings; diff --git a/src/lib/editorManager.js b/src/lib/editorManager.js index c9a7867370..fdc6db09ac 100644 --- a/src/lib/editorManager.js +++ b/src/lib/editorManager.js @@ -1151,9 +1151,12 @@ async function EditorManager($header, $body) { function getBaseExtensionSignature() { const options = getBaseExtensionOptions(); - return JSON.stringify( - baseExtensionSettings.map((key) => [key, options[key]]), - ); + // The renderer is part of it: states built for canvas views leave out + // what the canvas draws, so a DOM fallback must not reuse them. + return JSON.stringify([ + ...baseExtensionSettings.map((key) => [key, options[key]]), + ["canvas", options.canvas], + ]); } function makeRainbowBracketExtension() { @@ -1503,7 +1506,13 @@ async function EditorManager($header, $body) { error, "canvas-renderer", ); - const view = createEditorView({ state, parent }); + // Panes start with an empty state; rebuild it with the DOM + // editor's extensions. Files' states follow on their next show, + // since the renderer is in their extension signature. + const view = createEditorView({ + state: createEmptyEditorState(), + parent, + }); applyOptions(null, view); return view; } diff --git a/src/settings/editorSettings.js b/src/settings/editorSettings.js index 0716f82bad..fe35196f9d 100644 --- a/src/settings/editorSettings.js +++ b/src/settings/editorSettings.js @@ -325,7 +325,7 @@ export default function editorSettings() { appSettings.value.editorRenderer === "canvas") && (await confirm(strings.info, strings["reload to apply"])) ) { - location.reload(); + await reloadApp(); } return; @@ -339,4 +339,11 @@ export default function editorSettings() { break; } } + + /** Reload once open files' edits are cached and the session is saved. */ + async function reloadApp() { + await window.editorManager?.flushCacheWrites?.(); + window.acode?.exec("save-state"); + location.reload(); + } } From 2b26422a09b1716ee28ada1e9998ed31cae3bd7d Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Sun, 25 Oct 2026 10:33:16 +0530 Subject: [PATCH 14/14] feat(settings): more canvas renderer settings, shown only with canvas - Cursor blink rate, sticky scroll max lines and gutter marker layout (shared column, column per gutter, over line numbers), mapped to the matching canvas-view options and applied live. - Bring back the scroll speed setting, which canvas views use. - Canvas-only settings (backend, sticky scroll, its max lines, gutter markers, cursor blink rate, scroll speed) are listed only while the canvas renderer is selected, and follow renderer changes. Co-Authored-By: Claude Opus 5.5 --- src/cm/canvasOptions.ts | 25 +++++++++- src/lang/ar-ye.json | 12 ++++- src/lang/az-az.json | 12 ++++- src/lang/be-by.json | 12 ++++- src/lang/bn-bd.json | 12 ++++- src/lang/cs-cz.json | 12 ++++- src/lang/de-de.json | 12 ++++- src/lang/en-us.json | 12 ++++- src/lang/es-sv.json | 12 ++++- src/lang/fr-fr.json | 12 ++++- src/lang/he-il.json | 12 ++++- src/lang/hi-in.json | 12 ++++- src/lang/hu-hu.json | 12 ++++- src/lang/id-id.json | 12 ++++- src/lang/index.d.ts | 10 ++++ src/lang/ir-fa.json | 12 ++++- src/lang/it-it.json | 12 ++++- src/lang/ja-jp.json | 12 ++++- src/lang/ko-kr.json | 12 ++++- src/lang/ln-ln.json | 12 ++++- src/lang/ml-in.json | 12 ++++- src/lang/mm-unicode.json | 12 ++++- src/lang/mm-zawgyi.json | 12 ++++- src/lang/pl-pl.json | 12 ++++- src/lang/pt-br.json | 12 ++++- src/lang/pu-in.json | 12 ++++- src/lang/ru-ru.json | 12 ++++- src/lang/tl-ph.json | 12 ++++- src/lang/tr-tr.json | 12 ++++- src/lang/uk-ua.json | 12 ++++- src/lang/uz-uz.json | 12 ++++- src/lang/vi-vn.json | 12 ++++- src/lang/zh-cn.json | 12 ++++- src/lang/zh-hant.json | 12 ++++- src/lang/zh-tw.json | 12 ++++- src/lib/editorManager.js | 3 ++ src/lib/settings.js | 3 ++ src/settings/editorSettings.js | 86 +++++++++++++++++++++++++++++++- src/settings/scrollSettings.js | 24 ++++++--- tests/unit/canvasOptions.test.ts | 25 ++++++++++ 40 files changed, 530 insertions(+), 42 deletions(-) diff --git a/src/cm/canvasOptions.ts b/src/cm/canvasOptions.ts index 48f3b803e2..fdaaac54d2 100644 --- a/src/cm/canvasOptions.ts +++ b/src/cm/canvasOptions.ts @@ -52,6 +52,22 @@ const SCROLL_SPEED: Record = { FAST_X2: "fastX2", }; +const GUTTER_MARKERS = ["shared", "columns", "overlay"] as const; + +/** A finite number within [min, max], else the fallback. */ +function clampSetting( + value: unknown, + min: number, + max: number, + fallback: number, +): number { + const number = Number(value); + if (value == null || value === "" || !Number.isFinite(number)) { + return fallback; + } + return Math.min(Math.max(Math.round(number), min), max); +} + /** * Find the HighlightStyle a theme passes to `syntaxHighlighting()`, by * walking its extension tree (arrays, precedence wrappers, compartments, @@ -228,6 +244,11 @@ export function getCanvasDisplayOptions( Number.isFinite(cursorWidth) && cursorWidth > 0 ? Math.min(cursorWidth, 10) : 2, + // 0 keeps the cursor steady. + cursorBlinkRate: clampSetting(settings.cursorBlinkRate, 0, 10000, 1200), + gutterMarkers: GUTTER_MARKERS.includes(settings.canvasGutterMarkers) + ? settings.canvasGutterMarkers + : "shared", lineWrapping: !!settings.textWrap, wrapIndent: settings.wrappingIndent ?? "same", textDirection: settings.rtlText ? "auto" : "ltr", @@ -243,7 +264,9 @@ export function getCanvasDisplayOptions( showWhitespace: showSpaces ? { spaces: true, trailing: true, lineBreaks: true } : false, - stickyScroll: !!settings.stickyScroll, + stickyScroll: settings.stickyScroll + ? { maxLines: clampSetting(settings.stickyScrollMaxLines, 1, 10, 5) } + : false, }; } diff --git a/src/lang/ar-ye.json b/src/lang/ar-ye.json index 08be3015f1..4e79917b50 100644 --- a/src/lang/ar-ye.json +++ b/src/lang/ar-ye.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/az-az.json b/src/lang/az-az.json index 61081abd1b..b7ee2ee9b5 100644 --- a/src/lang/az-az.json +++ b/src/lang/az-az.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/be-by.json b/src/lang/be-by.json index 92f3eb96af..4740aac8e7 100644 --- a/src/lang/be-by.json +++ b/src/lang/be-by.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/bn-bd.json b/src/lang/bn-bd.json index d34b1ba75b..081131579a 100644 --- a/src/lang/bn-bd.json +++ b/src/lang/bn-bd.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/cs-cz.json b/src/lang/cs-cz.json index 3246f82be2..002613c29a 100644 --- a/src/lang/cs-cz.json +++ b/src/lang/cs-cz.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/de-de.json b/src/lang/de-de.json index 7edb80d97d..4b0ec32516 100644 --- a/src/lang/de-de.json +++ b/src/lang/de-de.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/en-us.json b/src/lang/en-us.json index 0306650561..569c59521f 100644 --- a/src/lang/en-us.json +++ b/src/lang/en-us.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/es-sv.json b/src/lang/es-sv.json index dd457089a1..3ad4fd47ec 100644 --- a/src/lang/es-sv.json +++ b/src/lang/es-sv.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/fr-fr.json b/src/lang/fr-fr.json index 0b4cde073b..5542fc28d4 100644 --- a/src/lang/fr-fr.json +++ b/src/lang/fr-fr.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/he-il.json b/src/lang/he-il.json index e3e55d9c14..8743df929e 100644 --- a/src/lang/he-il.json +++ b/src/lang/he-il.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/hi-in.json b/src/lang/hi-in.json index b1c38ef535..8cf91745bf 100644 --- a/src/lang/hi-in.json +++ b/src/lang/hi-in.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/hu-hu.json b/src/lang/hu-hu.json index 137c363d64..d6cab64f8a 100644 --- a/src/lang/hu-hu.json +++ b/src/lang/hu-hu.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/id-id.json b/src/lang/id-id.json index 15d552cf03..5e48e7d93e 100644 --- a/src/lang/id-id.json +++ b/src/lang/id-id.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/index.d.ts b/src/lang/index.d.ts index 7a07566c79..64eed6cb1e 100644 --- a/src/lang/index.d.ts +++ b/src/lang/index.d.ts @@ -900,6 +900,16 @@ declare type LangStrings = { "settings-info-canvas-backend": string; "sticky scroll": string; "settings-info-sticky-scroll": string; + "sticky scroll max lines": string; + "settings-info-sticky-scroll-max-lines": string; + "gutter markers": string; + "gutter markers shared": string; + "gutter markers columns": string; + "gutter markers overlay": string; + "settings-info-gutter-markers": string; + "cursor blink rate": string; + "settings-info-cursor-blink-rate": string; + "settings-info-scroll-speed": string; }; declare var strings: LangStrings; diff --git a/src/lang/ir-fa.json b/src/lang/ir-fa.json index 3cfdb039e6..77126e16bb 100644 --- a/src/lang/ir-fa.json +++ b/src/lang/ir-fa.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/it-it.json b/src/lang/it-it.json index 7687a9ddf4..1a4912f01c 100644 --- a/src/lang/it-it.json +++ b/src/lang/it-it.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/ja-jp.json b/src/lang/ja-jp.json index d3aac78f10..b35bd225a0 100644 --- a/src/lang/ja-jp.json +++ b/src/lang/ja-jp.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/ko-kr.json b/src/lang/ko-kr.json index 5c03da25d3..5413bbd4b9 100644 --- a/src/lang/ko-kr.json +++ b/src/lang/ko-kr.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/ln-ln.json b/src/lang/ln-ln.json index 5eebb3e77c..002ad75981 100644 --- a/src/lang/ln-ln.json +++ b/src/lang/ln-ln.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/ml-in.json b/src/lang/ml-in.json index 2eee43116f..7982934ab1 100644 --- a/src/lang/ml-in.json +++ b/src/lang/ml-in.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/mm-unicode.json b/src/lang/mm-unicode.json index 2bedccc149..5cf1d2e41f 100644 --- a/src/lang/mm-unicode.json +++ b/src/lang/mm-unicode.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/mm-zawgyi.json b/src/lang/mm-zawgyi.json index 8e792d7fd7..b6ae8a405c 100644 --- a/src/lang/mm-zawgyi.json +++ b/src/lang/mm-zawgyi.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/pl-pl.json b/src/lang/pl-pl.json index 7b96217cca..7bd6623217 100644 --- a/src/lang/pl-pl.json +++ b/src/lang/pl-pl.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/pt-br.json b/src/lang/pt-br.json index 874a7538c9..f8b6eba9d8 100644 --- a/src/lang/pt-br.json +++ b/src/lang/pt-br.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/pu-in.json b/src/lang/pu-in.json index 9dac5441ce..fa5d2b46bb 100644 --- a/src/lang/pu-in.json +++ b/src/lang/pu-in.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/ru-ru.json b/src/lang/ru-ru.json index 855c7100cf..79b0196c57 100644 --- a/src/lang/ru-ru.json +++ b/src/lang/ru-ru.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/tl-ph.json b/src/lang/tl-ph.json index 61d62bffc2..0e95b2f41c 100644 --- a/src/lang/tl-ph.json +++ b/src/lang/tl-ph.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/tr-tr.json b/src/lang/tr-tr.json index 2e1f91446c..2dd36d4069 100644 --- a/src/lang/tr-tr.json +++ b/src/lang/tr-tr.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/uk-ua.json b/src/lang/uk-ua.json index 090ae544cf..fa2161245d 100644 --- a/src/lang/uk-ua.json +++ b/src/lang/uk-ua.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/uz-uz.json b/src/lang/uz-uz.json index c7f88ca6b6..c03876491f 100644 --- a/src/lang/uz-uz.json +++ b/src/lang/uz-uz.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/vi-vn.json b/src/lang/vi-vn.json index cca52209e5..fdc61e41af 100644 --- a/src/lang/vi-vn.json +++ b/src/lang/vi-vn.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/zh-cn.json b/src/lang/zh-cn.json index 57619cf494..39e9c7907f 100644 --- a/src/lang/zh-cn.json +++ b/src/lang/zh-cn.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/zh-hant.json b/src/lang/zh-hant.json index f19f2ab8ad..9bba937281 100644 --- a/src/lang/zh-hant.json +++ b/src/lang/zh-hant.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lang/zh-tw.json b/src/lang/zh-tw.json index 7992ae06f8..54d8e9c222 100644 --- a/src/lang/zh-tw.json +++ b/src/lang/zh-tw.json @@ -896,5 +896,15 @@ "canvas backend auto": "Auto (WebGL2)", "settings-info-canvas-backend": "The GPU API the canvas renderer draws with. WebGPU falls back to WebGL2 where it isn't available. Applies after a reload.", "sticky scroll": "Sticky scroll", - "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling. Canvas renderer only." + "settings-info-sticky-scroll": "Keep the first lines of the functions, classes and blocks around the top line pinned while scrolling.", + "sticky scroll max lines": "Sticky scroll lines", + "settings-info-sticky-scroll-max-lines": "The most header lines sticky scroll keeps pinned at the top (1–10).", + "gutter markers": "Gutter markers", + "gutter markers shared": "One column", + "gutter markers columns": "Column per gutter", + "gutter markers overlay": "Over line numbers", + "settings-info-gutter-markers": "Where lint and other gutter markers go: one shared column, a column for each gutter, or over the line numbers to save width.", + "cursor blink rate": "Cursor blink rate", + "settings-info-cursor-blink-rate": "How long one cursor blink takes, in milliseconds. 0 keeps the cursor steady.", + "settings-info-scroll-speed": "How far a fling or a mouse wheel turn scrolls the editor." } diff --git a/src/lib/editorManager.js b/src/lib/editorManager.js index fdc6db09ac..d56f521b66 100644 --- a/src/lib/editorManager.js +++ b/src/lib/editorManager.js @@ -3814,6 +3814,9 @@ async function EditorManager($header, $body) { appSettings.on("update:highlightActiveLine", syncAllCanvasViews); appSettings.on("update:scrollSpeed", syncAllCanvasViews); appSettings.on("update:stickyScroll", syncAllCanvasViews); + appSettings.on("update:stickyScrollMaxLines", syncAllCanvasViews); + appSettings.on("update:cursorBlinkRate", syncAllCanvasViews); + appSettings.on("update:canvasGutterMarkers", syncAllCanvasViews); // Keep file.session and cache in sync on every edit function getDocSyncListener() { diff --git a/src/lib/settings.js b/src/lib/settings.js index 64970a588f..1fbf774c0d 100644 --- a/src/lib/settings.js +++ b/src/lib/settings.js @@ -135,7 +135,10 @@ class Settings { editorTheme: "one_dark", editorRenderer: "dom", canvasBackend: "auto", + canvasGutterMarkers: "shared", stickyScroll: false, + stickyScrollMaxLines: 5, + cursorBlinkRate: 1200, textWrap: false, wrappingIndent: "same", softTab: true, diff --git a/src/settings/editorSettings.js b/src/settings/editorSettings.js index fe35196f9d..0ba895e3ec 100644 --- a/src/settings/editorSettings.js +++ b/src/settings/editorSettings.js @@ -5,6 +5,15 @@ import fonts from "lib/fonts"; import appSettings from "lib/settings"; import scrollSettings from "./scrollSettings"; +/** Settings only canvas editors use; shown with the canvas renderer. */ +const CANVAS_ONLY_SETTINGS = [ + "canvasBackend", + "stickyScroll", + "stickyScrollMaxLines", + "canvasGutterMarkers", + "cursorBlinkRate", +]; + export default function editorSettings() { const title = strings["editor settings"]; const values = appSettings.value; @@ -17,6 +26,11 @@ export default function editorSettings() { ["webgl2", "WebGL2"], ["webgpu", "WebGPU"], ]; + const gutterMarkerOptions = [ + ["shared", strings["gutter markers shared"]], + ["columns", strings["gutter markers columns"]], + ["overlay", strings["gutter markers overlay"]], + ]; const wrappingIndentOptions = [ ["none", strings.none], ["same", strings["wrap-indent-same"]], @@ -68,6 +82,21 @@ export default function editorSettings() { info: strings["settings-info-sticky-scroll"], category: categories.textLayout, }, + { + key: "stickyScrollMaxLines", + text: strings["sticky scroll max lines"], + value: values.stickyScrollMaxLines ?? 5, + prompt: strings["sticky scroll max lines"], + promptType: "number", + promptOptions: { + test(value) { + value = Number.parseInt(value); + return value >= 1 && value <= 10; + }, + }, + info: strings["settings-info-sticky-scroll-max-lines"], + category: categories.textLayout, + }, { key: "editorFont", text: strings["editor font"], @@ -244,6 +273,17 @@ export default function editorSettings() { info: strings["settings-info-editor-lint-gutter"], category: categories.guidesIndicators, }, + { + key: "canvasGutterMarkers", + text: strings["gutter markers"], + value: values.canvasGutterMarkers ?? "shared", + valueText: (value) => + gutterMarkerOptions.find(([key]) => key === value)?.[1] ?? + gutterMarkerOptions[0][1], + select: gutterMarkerOptions, + info: strings["settings-info-gutter-markers"], + category: categories.guidesIndicators, + }, { key: "showSpaces", text: strings["show spaces"], @@ -286,6 +326,21 @@ export default function editorSettings() { info: strings["settings-info-editor-shift-click-selection"], category: categories.cursorSelection, }, + { + key: "cursorBlinkRate", + text: strings["cursor blink rate"], + value: values.cursorBlinkRate ?? 1200, + prompt: strings["cursor blink rate"] + " (ms)", + promptType: "number", + promptOptions: { + test(value) { + value = Number.parseInt(value); + return value >= 0 && value <= 10000; + }, + }, + info: strings["settings-info-cursor-blink-rate"], + category: categories.cursorSelection, + }, { key: "rtlText", text: strings["line based rtl switching"], @@ -295,7 +350,13 @@ export default function editorSettings() { }, ]; - return settingsPage(title, items, callback, undefined, { + for (const item of items) { + if (CANVAS_ONLY_SETTINGS.includes(item.key)) { + item.hidden = !isCanvasSettingVisible(item.key); + } + } + + const page = settingsPage(title, items, callback, undefined, { preserveOrder: true, pageClassName: "detail-settings-page", listClassName: "detail-settings-list", @@ -303,6 +364,24 @@ export default function editorSettings() { valueInTail: true, }); + // The page is created once and kept, so follow the renderer from here. + appSettings.on("update:editorRenderer", updateCanvasSettings); + appSettings.on("update:stickyScroll", updateCanvasSettings); + + return page; + + function isCanvasSettingVisible(key) { + const { editorRenderer, stickyScroll } = appSettings.value; + if (editorRenderer !== "canvas") return false; + return key !== "stickyScrollMaxLines" || !!stickyScroll; + } + + function updateCanvasSettings() { + for (const key of CANVAS_ONLY_SETTINGS) { + page.setItemVisibility(key, isCanvasSettingVisible(key)); + } + } + /** * Callback for settings page when an item is clicked * @param {string} key @@ -329,6 +408,11 @@ export default function editorSettings() { } return; + case "stickyScrollMaxLines": + case "cursorBlinkRate": + appSettings.update({ [key]: Number.parseInt(value) }); + break; + case "editorFont": fonts.setFont(value); diff --git a/src/settings/scrollSettings.js b/src/settings/scrollSettings.js index dbb72f4d30..cb5eb49f7f 100644 --- a/src/settings/scrollSettings.js +++ b/src/settings/scrollSettings.js @@ -7,18 +7,21 @@ export default function scrollSettings() { const title = strings["scroll settings"]; const items = [ - /*{ + { key: "scrollSpeed", text: strings["scroll speed"], value: values.scrollSpeed, valueText: getScrollSpeedString, select: [ - [constants.SCROLL_SPEED_FAST_X2, `${strings.fast} x2`], - [constants.SCROLL_SPEED_FAST, strings.fast], - [constants.SCROLL_SPEED_NORMAL, strings.normal], - [constants.SCROLL_SPEED_SLOW, strings.slow], + [config.SCROLL_SPEED_FAST_X2, `${strings.fast} x2`], + [config.SCROLL_SPEED_FAST, strings.fast], + [config.SCROLL_SPEED_NORMAL, strings.normal], + [config.SCROLL_SPEED_SLOW, strings.slow], ], - },*/ + info: strings["settings-info-scroll-speed"], + // Only canvas editors use it. + hidden: values.editorRenderer !== "canvas", + }, /*{ key: "reverseScrolling", text: strings["reverse scrolling"], @@ -86,7 +89,7 @@ export default function scrollSettings() { }, ]; - return settingsPage(title, items, callback, undefined, { + const page = settingsPage(title, items, callback, undefined, { preserveOrder: true, pageClassName: "detail-settings-page", listClassName: "detail-settings-list", @@ -95,6 +98,13 @@ export default function scrollSettings() { groupByDefault: true, }); + // The page is created once and kept, so follow the renderer from here. + appSettings.on("update:editorRenderer", (renderer) => { + page.setItemVisibility("scrollSpeed", renderer === "canvas"); + }); + + return page; + function callback(key, value) { appSettings.update({ [key]: key === "leftMargin" ? Number(value) : value, diff --git a/tests/unit/canvasOptions.test.ts b/tests/unit/canvasOptions.test.ts index 3bc5f34388..0b949f7aa0 100644 --- a/tests/unit/canvasOptions.test.ts +++ b/tests/unit/canvasOptions.test.ts @@ -83,6 +83,9 @@ describe("canvas renderer settings", () => { showWhitespace: false, highlightActiveLine: true, cursorWidth: 2, + cursorBlinkRate: 1200, + gutterMarkers: "shared", + stickyScroll: false, textDirection: "ltr", }); expect(options.rainbowBrackets).toMatchObject({ @@ -104,6 +107,10 @@ describe("canvas renderer settings", () => { rainbowBrackets: false, showSpaces: true, cursorWidth: 40, + cursorBlinkRate: "0", + canvasGutterMarkers: "overlay", + stickyScroll: true, + stickyScrollMaxLines: 30, rtlText: true, }, { themeId: "one_dark" }, @@ -119,6 +126,9 @@ describe("canvas renderer settings", () => { rainbowBrackets: false, showWhitespace: { spaces: true, trailing: true, lineBreaks: true }, cursorWidth: 10, + cursorBlinkRate: 0, + gutterMarkers: "overlay", + stickyScroll: { maxLines: 10 }, textDirection: "auto", }); expect( @@ -127,6 +137,21 @@ describe("canvas renderer settings", () => { { themeId: "one_dark" }, ).lineNumbers, ).toBe(false); + expect( + getCanvasDisplayOptions( + { + ...defaults, + stickyScroll: true, + canvasGutterMarkers: "bad", + cursorBlinkRate: "bad", + }, + { themeId: "one_dark" }, + ), + ).toMatchObject({ + stickyScroll: { maxLines: 5 }, + gutterMarkers: "shared", + cursorBlinkRate: 1200, + }); }); it("converts the editor font settings", () => {