From a14510ff2f5531b2c1a78aa24730db2e2b59b2bc Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:33:58 +0530 Subject: [PATCH 1/3] feat(editor): add configurable word wrap indentation --- src/cm/indentedLineWrapping.ts | 30 ++++++++++++++++++++----- src/lib/editorManager.js | 8 +++++-- src/lib/settings.js | 1 + src/settings/editorSettings.js | 17 ++++++++++++++ tests/unit/indentedLineWrapping.test.ts | 16 +++++++++++++ 5 files changed, 64 insertions(+), 8 deletions(-) diff --git a/src/cm/indentedLineWrapping.ts b/src/cm/indentedLineWrapping.ts index 305506ad4f..cddc531441 100644 --- a/src/cm/indentedLineWrapping.ts +++ b/src/cm/indentedLineWrapping.ts @@ -1,6 +1,7 @@ import { EditorState, type Extension, + Facet, RangeSetBuilder, StateEffect, } from "@codemirror/state"; @@ -13,15 +14,23 @@ import { } from "@codemirror/view"; const wrapWidth = StateEffect.define(); +export type WrappingIndent = "none" | "same" | "indent" | "deepIndent"; +const wrappingIndent = Facet.define({ + combine: (values) => values[0] ?? "same", +}); /** Keep tab stops on the first visual row unchanged by the negative indent. */ export function wrappedIndentColumns( text: string, tabSize: number, limit: number, + mode: WrappingIndent = "same", ): number { + const extra = + mode === "indent" ? tabSize : mode === "deepIndent" ? 2 * tabSize : 0; // Unindented/minified lines need no tab scan, even when they are megabytes long. - if (limit <= 0 || (text[0] !== " " && text[0] !== "\t")) return 0; + if (mode === "none" || limit <= 0) return 0; + if (!extra && text[0] !== " " && text[0] !== "\t") return 0; // Include content tabs deliberately: negative text-indent changes the origin // of every tab stop on the first visual row. For " key\tvalue", using 2ch // shifts "value" left by two columns in Chromium; 4ch preserves its position. @@ -36,7 +45,7 @@ export function wrappedIndentColumns( else break; if (columns >= cap) return cap; } - return Math.min(cap, Math.ceil(columns / step) * step); + return Math.min(cap, Math.ceil((columns + extra) / step) * step); } interface CachedIndent { @@ -51,6 +60,7 @@ function decorate( ): { decorations: DecorationSet; cache: Map } { const builder = new RangeSetBuilder(); const tabSize = view.state.facet(EditorState.tabSize); + const mode = view.state.facet(wrappingIndent); const cache = new Map(); const styles = new Map(); let lastLine = -1; @@ -60,7 +70,7 @@ function decorate( if (line.from > lastLine) { let entry = previous.get(line.from); if (!entry || entry.text !== line.text) { - const columns = wrappedIndentColumns(line.text, tabSize, limit); + const columns = wrappedIndentColumns(line.text, tabSize, limit, mode); let decoration = columns ? styles.get(columns) : null; if (columns && !decoration) { decoration = Decoration.line({ @@ -133,7 +143,10 @@ const plugin = ViewPlugin.fromClass( const tabSizeChanged = update.startState.facet(EditorState.tabSize) !== update.state.facet(EditorState.tabSize); - if (changed || tabSizeChanged) { + const modeChanged = + update.startState.facet(wrappingIndent) !== + update.state.facet(wrappingIndent); + if (changed || tabSizeChanged || modeChanged) { this.cache.clear(); } else if (update.docChanged) { // Keep unchanged visible lines cached when earlier edits shift them. @@ -148,7 +161,8 @@ const plugin = ViewPlugin.fromClass( changed || update.docChanged || update.viewportChanged || - tabSizeChanged + tabSizeChanged || + modeChanged ) { const result = decorate(update.view, this.limit, this.cache); this.decorations = result.decorations; @@ -168,9 +182,13 @@ const plugin = ViewPlugin.fromClass( * tabs occur after the leading whitespace. Oversized indents are capped * at half the available columns so narrow panes still have room for content. */ -export function indentedLineWrapping(): Extension { +export function indentedLineWrapping(mode: WrappingIndent = "same"): Extension { + if (mode === "none") return EditorView.lineWrapping; + // Settings imported from older or manually edited files may be invalid. + if (mode !== "indent" && mode !== "deepIndent") mode = "same"; return [ EditorView.lineWrapping, + wrappingIndent.of(mode), plugin, EditorView.baseTheme({ ".cm-line.cm-wrap-indent": { diff --git a/src/lib/editorManager.js b/src/lib/editorManager.js index d1b85514ee..1bf1b1b789 100644 --- a/src/lib/editorManager.js +++ b/src/lib/editorManager.js @@ -1034,7 +1034,8 @@ async function EditorManager($header, $body) { } function makeWrapExtension() { - if (appSettings?.value?.textWrap) return indentedLineWrapping(); + if (appSettings?.value?.textWrap) + return indentedLineWrapping(appSettings.value.wrappingIndent); return horizontalScrollPastEnd( Number(appSettings?.value?.leftMargin ?? 50), ); @@ -1194,7 +1195,7 @@ async function EditorManager($header, $body) { }, }, { - keys: ["textWrap"], + keys: ["textWrap", "wrappingIndent"], compartments: [wrapCompartment], build() { return makeWrapExtension(); @@ -3411,6 +3412,9 @@ async function EditorManager($header, $body) { updateMargin(); applyOptions(["textWrap"]); }); + appSettings.on("update:wrappingIndent", function () { + applyOptions(["wrappingIndent"]); + }); appSettings.on("update:leftMargin", function () { applyOptions(["textWrap"]); diff --git a/src/lib/settings.js b/src/lib/settings.js index 8827faadbc..fd6f3cd79d 100644 --- a/src/lib/settings.js +++ b/src/lib/settings.js @@ -134,6 +134,7 @@ class Settings { cursorWidth: 2, editorTheme: "one_dark", textWrap: false, + wrappingIndent: "same", softTab: true, tabSize: 2, retryRemoteFsAfterFail: true, diff --git a/src/settings/editorSettings.js b/src/settings/editorSettings.js index 29e9070178..ec4c34b00a 100644 --- a/src/settings/editorSettings.js +++ b/src/settings/editorSettings.js @@ -7,6 +7,12 @@ import scrollSettings from "./scrollSettings"; export default function editorSettings() { const title = strings["editor settings"]; const values = appSettings.value; + const wrappingIndentOptions = [ + ["none", strings.none], + ["same", strings["wrap-indent-same"]], + ["indent", strings["wrap-indent-indent"]], + ["deepIndent", strings["wrap-indent-deep"]], + ]; const categories = { scrolling: strings["settings-category-scrolling"], textLayout: strings["settings-category-text-layout"], @@ -67,6 +73,17 @@ export default function editorSettings() { info: strings["settings-info-editor-text-wrap"], category: categories.textLayout, }, + { + key: "wrappingIndent", + text: strings["wrapped line indent"], + value: values.wrappingIndent ?? "same", + valueText: (value) => + wrappingIndentOptions.find(([key]) => key === value)?.[1] ?? + wrappingIndentOptions[1][1], + select: wrappingIndentOptions, + info: strings["settings-info-editor-wrapping-indent"], + category: categories.textLayout, + }, { key: "autosave", text: strings.autosave, diff --git a/tests/unit/indentedLineWrapping.test.ts b/tests/unit/indentedLineWrapping.test.ts index 8557f57e0d..4dbff4f5d5 100644 --- a/tests/unit/indentedLineWrapping.test.ts +++ b/tests/unit/indentedLineWrapping.test.ts @@ -21,4 +21,20 @@ describe("wrapped line indentation", () => { expect(wrappedIndentColumns("\tcode", 4, 3)).toBe(0); expect(wrappedIndentColumns(" code", 4, 0)).toBe(0); }); + it("adds one or two tab-sized levels to wrapped continuations", () => { + expect(wrappedIndentColumns("code", 2, 40, "indent")).toBe(2); + expect(wrappedIndentColumns("code", 2, 40, "deepIndent")).toBe(4); + expect(wrappedIndentColumns(" code", 2, 40, "indent")).toBe(4); + expect(wrappedIndentColumns(" code", 2, 40, "deepIndent")).toBe(6); + expect(wrappedIndentColumns(" code", 4, 40, "deepIndent")).toBe(10); + }); + it("disables continuation indentation in none mode", () => { + expect(wrappedIndentColumns("\t code", 4, 40, "none")).toBe(0); + }); + it("keeps tab alignment and the width cap with extra indentation", () => { + expect(wrappedIndentColumns(" key\tvalue", 4, 40, "indent")).toBe(8); + expect(wrappedIndentColumns("key\tvalue", 4, 40, "deepIndent")).toBe(8); + expect(wrappedIndentColumns(" key\tvalue", 4, 7, "deepIndent")).toBe(4); + expect(wrappedIndentColumns(" code", 4, 7, "deepIndent")).toBe(7); + }); }); From 7d3ff2011e9c021939e76a5348901cc879786be2 Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:37:21 +0530 Subject: [PATCH 2/3] chore: translation strings --- src/lang/ar-ye.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 | 5 +++++ 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/index.d.ts | 6 ++++++ 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 ++++++- 33 files changed, 197 insertions(+), 31 deletions(-) diff --git a/src/lang/ar-ye.json b/src/lang/ar-ye.json index ad534d7ace..eb5a33428d 100644 --- a/src/lang/ar-ye.json +++ b/src/lang/ar-ye.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "يجب ألا يتجاوز التقييم {max} حرفًا.", "plugin-review:error-downvote-comment": "أخبر المطوّر بما يمكن تحسينه.", "plugin-review:error-empty": "أضف تعليقًا أو اختر «أعجبني».", - "plugin-review:unrated": "غير مقيّم" + "plugin-review:unrated": "غير مقيّم", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/be-by.json b/src/lang/be-by.json index a60729700e..b59491ea87 100644 --- a/src/lang/be-by.json +++ b/src/lang/be-by.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "Водгук павінен змяшчаць не больш за {max} сімвалаў.", "plugin-review:error-downvote-comment": "Раскажыце распрацоўшчыку, што можна палепшыць.", "plugin-review:error-empty": "Дадайце каментарый або выберыце «Падабаецца».", - "plugin-review:unrated": "Без ацэнкі" + "plugin-review:unrated": "Без ацэнкі", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/bn-bd.json b/src/lang/bn-bd.json index 5250056b28..28d101f00c 100644 --- a/src/lang/bn-bd.json +++ b/src/lang/bn-bd.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "পর্যালোচনা {max} অক্ষর বা তার কম হতে হবে।", "plugin-review:error-downvote-comment": "কী উন্নত করা যায় তা ডেভেলপারকে জানান।", "plugin-review:error-empty": "একটি মন্তব্য যোগ করুন বা পছন্দ নির্বাচন করুন।", - "plugin-review:unrated": "এখনও মূল্যায়ন নেই" + "plugin-review:unrated": "এখনও মূল্যায়ন নেই", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/cs-cz.json b/src/lang/cs-cz.json index c1dc855231..2646d43475 100644 --- a/src/lang/cs-cz.json +++ b/src/lang/cs-cz.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "Recenze může mít nejvýše {max} znaků.", "plugin-review:error-downvote-comment": "Sdělte vývojáři, co by se dalo zlepšit.", "plugin-review:error-empty": "Přidejte komentář nebo zvolte Líbí se mi.", - "plugin-review:unrated": "Nehodnoceno" + "plugin-review:unrated": "Nehodnoceno", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/de-de.json b/src/lang/de-de.json index f90e6267c1..bf78e973df 100644 --- a/src/lang/de-de.json +++ b/src/lang/de-de.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "Die Bewertung darf höchstens {max} Zeichen lang sein.", "plugin-review:error-downvote-comment": "Sage dem Entwickler, was verbessert werden könnte.", "plugin-review:error-empty": "Füge einen Kommentar hinzu oder wähle „Gefällt mir“.", - "plugin-review:unrated": "Nicht bewertet" + "plugin-review:unrated": "Nicht bewertet", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/en-us.json b/src/lang/en-us.json index 0393c9e451..0af439c2df 100644 --- a/src/lang/en-us.json +++ b/src/lang/en-us.json @@ -92,6 +92,11 @@ "success": "Success", "tab size": "Tab size", "text wrap": "Text wrap / Word wrap", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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.", "theme": "Theme", "title-listfiles": "List files", "ui zoom": "UI zoom", diff --git a/src/lang/es-sv.json b/src/lang/es-sv.json index 3490e0d6f7..97e96193ac 100644 --- a/src/lang/es-sv.json +++ b/src/lang/es-sv.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "La reseña debe tener {max} caracteres o menos.", "plugin-review:error-downvote-comment": "Dile al desarrollador qué podría mejorar.", "plugin-review:error-empty": "Agrega un comentario o elige Me gusta.", - "plugin-review:unrated": "Sin calificar" + "plugin-review:unrated": "Sin calificar", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/fr-fr.json b/src/lang/fr-fr.json index f25d7e1b7b..103ae9920d 100644 --- a/src/lang/fr-fr.json +++ b/src/lang/fr-fr.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "L’avis doit contenir au maximum {max} caractères.", "plugin-review:error-downvote-comment": "Indiquez au développeur ce qui pourrait être amélioré.", "plugin-review:error-empty": "Ajoutez un commentaire ou choisissez J’aime.", - "plugin-review:unrated": "Non évalué" + "plugin-review:unrated": "Non évalué", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/he-il.json b/src/lang/he-il.json index fa5d00dea0..e0b76ca2e3 100644 --- a/src/lang/he-il.json +++ b/src/lang/he-il.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "הביקורת צריכה להכיל עד {max} תווים.", "plugin-review:error-downvote-comment": "ספר למפתח מה אפשר לשפר.", "plugin-review:error-empty": "הוסף תגובה או בחר באהבתי.", - "plugin-review:unrated": "ללא דירוג" + "plugin-review:unrated": "ללא דירוג", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/hi-in.json b/src/lang/hi-in.json index 3cb9cfa0cd..c042240877 100644 --- a/src/lang/hi-in.json +++ b/src/lang/hi-in.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "समीक्षा {max} वर्णों या उससे कम की होनी चाहिए।", "plugin-review:error-downvote-comment": "डेवलपर को बताएँ कि क्या बेहतर किया जा सकता है।", "plugin-review:error-empty": "टिप्पणी जोड़ें या पसंद चुनें।", - "plugin-review:unrated": "अभी रेटिंग नहीं" + "plugin-review:unrated": "अभी रेटिंग नहीं", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/hu-hu.json b/src/lang/hu-hu.json index 7e37831507..c37b3e26e3 100644 --- a/src/lang/hu-hu.json +++ b/src/lang/hu-hu.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "A megjegyzés szövege legfeljebb {max} karakter lehet.", "plugin-review:error-downvote-comment": "Írja meg a fejlesztőknek, hogy min lehetne javítani.", "plugin-review:error-empty": "Adjon hozzá megjegyzést vagy válassza a „Tetszik” lehetőséget.", - "plugin-review:unrated": "Még nem értékelte senki" + "plugin-review:unrated": "Még nem értékelte senki", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/id-id.json b/src/lang/id-id.json index b10bf0dabc..4d45630eb0 100644 --- a/src/lang/id-id.json +++ b/src/lang/id-id.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "Ulasan harus terdiri dari {max} karakter atau kurang.", "plugin-review:error-downvote-comment": "Beri tahu developer apa yang dapat ditingkatkan.", "plugin-review:error-empty": "Tambahkan komentar atau pilih Suka.", - "plugin-review:unrated": "Belum dinilai" + "plugin-review:unrated": "Belum dinilai", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/index.d.ts b/src/lang/index.d.ts index f6d4a9427d..f643241de0 100644 --- a/src/lang/index.d.ts +++ b/src/lang/index.d.ts @@ -95,6 +95,11 @@ declare type LangStrings = { "success": string; "tab size": string; "text wrap": string; + "wrapped line indent": string; + "wrap-indent-same": string; + "wrap-indent-indent": string; + "wrap-indent-deep": string; + "settings-info-editor-wrapping-indent": string; "theme": string; "title-listfiles": string; "ui zoom": string; @@ -772,6 +777,7 @@ declare type LangStrings = { "app font": string; "app icon": string; "app icon changed": string; + "app icon change warning": string; "settings-info-app-font-family": string; "settings-info-app-icon": string; "unsaved selected tabs warning": string; diff --git a/src/lang/ir-fa.json b/src/lang/ir-fa.json index f969387eb1..e9f47b3b15 100644 --- a/src/lang/ir-fa.json +++ b/src/lang/ir-fa.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "نظر باید حداکثر {max} نویسه باشد.", "plugin-review:error-downvote-comment": "به توسعه‌دهنده بگویید چه چیزی می‌تواند بهتر شود.", "plugin-review:error-empty": "یک دیدگاه بنویسید یا «پسندیدم» را انتخاب کنید.", - "plugin-review:unrated": "بدون امتیاز" + "plugin-review:unrated": "بدون امتیاز", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/it-it.json b/src/lang/it-it.json index f4926326c3..5cced6fc43 100644 --- a/src/lang/it-it.json +++ b/src/lang/it-it.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "La recensione deve contenere al massimo {max} caratteri.", "plugin-review:error-downvote-comment": "Spiega allo sviluppatore cosa potrebbe essere migliorato.", "plugin-review:error-empty": "Aggiungi un commento o scegli Mi piace.", - "plugin-review:unrated": "Non valutato" + "plugin-review:unrated": "Non valutato", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/ja-jp.json b/src/lang/ja-jp.json index 9e54dd1618..f80382673b 100644 --- a/src/lang/ja-jp.json +++ b/src/lang/ja-jp.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "レビューは {max} 文字以内で入力してください。", "plugin-review:error-downvote-comment": "改善してほしい点を開発者に伝えてください。", "plugin-review:error-empty": "コメントを追加するか、高評価を選んでください。", - "plugin-review:unrated": "未評価" + "plugin-review:unrated": "未評価", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/ko-kr.json b/src/lang/ko-kr.json index 812e2327f4..f33b9773c3 100644 --- a/src/lang/ko-kr.json +++ b/src/lang/ko-kr.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "리뷰는 {max}자 이하여야 합니다.", "plugin-review:error-downvote-comment": "개발자에게 개선할 점을 알려 주세요.", "plugin-review:error-empty": "댓글을 추가하거나 좋아요를 선택하세요.", - "plugin-review:unrated": "평가 없음" + "plugin-review:unrated": "평가 없음", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/ln-ln.json b/src/lang/ln-ln.json index 8f3319303f..e9d379d27d 100644 --- a/src/lang/ln-ln.json +++ b/src/lang/ln-ln.json @@ -876,5 +876,10 @@ "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", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/ml-in.json b/src/lang/ml-in.json index 4e419e288c..04cf2f7878 100644 --- a/src/lang/ml-in.json +++ b/src/lang/ml-in.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "അവലോകനം {max} അക്ഷരങ്ങളോ അതിൽ കുറവോ ആയിരിക്കണം.", "plugin-review:error-downvote-comment": "എന്താണ് മെച്ചപ്പെടുത്താനാകുന്നതെന്ന് ഡെവലപ്പറോട് പറയുക.", "plugin-review:error-empty": "ഒരു അഭിപ്രായം ചേർക്കുക അല്ലെങ്കിൽ ഇഷ്ടപ്പെട്ടു തിരഞ്ഞെടുക്കുക.", - "plugin-review:unrated": "വിലയിരുത്തിയിട്ടില്ല" + "plugin-review:unrated": "വിലയിരുത്തിയിട്ടില്ല", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/mm-unicode.json b/src/lang/mm-unicode.json index eec52d1b75..f14e6b82bc 100644 --- a/src/lang/mm-unicode.json +++ b/src/lang/mm-unicode.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "သုံးသပ်ချက်သည် စာလုံးရေ {max} လုံး သို့မဟုတ် ထိုထက်နည်းရမည်။", "plugin-review:error-downvote-comment": "မည်သည့်အရာကို ပိုကောင်းအောင်လုပ်နိုင်ကြောင်း developer ကို ပြောပါ။", "plugin-review:error-empty": "မှတ်ချက်ထည့်ပါ သို့မဟုတ် ကြိုက်သည်ကို ရွေးပါ။", - "plugin-review:unrated": "အဆင့်မသတ်မှတ်ရသေးပါ" + "plugin-review:unrated": "အဆင့်မသတ်မှတ်ရသေးပါ", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/mm-zawgyi.json b/src/lang/mm-zawgyi.json index 56b97b915a..5fb0e344bb 100644 --- a/src/lang/mm-zawgyi.json +++ b/src/lang/mm-zawgyi.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "သုံးသပ္ခ်က္သည္ စာလုံးေရ {max} လုံး သို႔မဟုတ္ ထိုထက္နည္းရမည္။", "plugin-review:error-downvote-comment": "မည္သည့္အရာကို ပိုေကာင္းေအာင္လုပ္ႏိုင္ေၾကာင္း developer ကို ေျပာပါ။", "plugin-review:error-empty": "မွတ္ခ်က္ထည့္ပါ သို႔မဟုတ္ ႀကိဳက္သည္ကို ေ႐ြးပါ။", - "plugin-review:unrated": "အဆင့္မသတ္မွတ္ရေသးပါ" + "plugin-review:unrated": "အဆင့္မသတ္မွတ္ရေသးပါ", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/pl-pl.json b/src/lang/pl-pl.json index 51092eb795..5aafd67b6c 100644 --- a/src/lang/pl-pl.json +++ b/src/lang/pl-pl.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "Opinia może mieć maksymalnie {max} znaków.", "plugin-review:error-downvote-comment": "Powiedz deweloperowi, co można poprawić.", "plugin-review:error-empty": "Dodaj komentarz lub wybierz „Lubię”.", - "plugin-review:unrated": "Bez oceny" + "plugin-review:unrated": "Bez oceny", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/pt-br.json b/src/lang/pt-br.json index 7c4f32fba4..64f45d6402 100644 --- a/src/lang/pt-br.json +++ b/src/lang/pt-br.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "A avaliação deve ter no máximo {max} caracteres.", "plugin-review:error-downvote-comment": "Diga ao desenvolvedor o que pode ser melhorado.", "plugin-review:error-empty": "Adicione um comentário ou escolha Gostei.", - "plugin-review:unrated": "Sem avaliação" + "plugin-review:unrated": "Sem avaliação", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/pu-in.json b/src/lang/pu-in.json index bf24369e58..60912d7a67 100644 --- a/src/lang/pu-in.json +++ b/src/lang/pu-in.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "ਸਮੀਖਿਆ {max} ਅੱਖਰਾਂ ਜਾਂ ਇਸ ਤੋਂ ਘੱਟ ਦੀ ਹੋਣੀ ਚਾਹੀਦੀ ਹੈ।", "plugin-review:error-downvote-comment": "ਡਿਵੈਲਪਰ ਨੂੰ ਦੱਸੋ ਕਿ ਕੀ ਸੁਧਾਰਿਆ ਜਾ ਸਕਦਾ ਹੈ।", "plugin-review:error-empty": "ਟਿੱਪਣੀ ਸ਼ਾਮਲ ਕਰੋ ਜਾਂ ਪਸੰਦ ਚੁਣੋ।", - "plugin-review:unrated": "ਹਾਲੇ ਮੁਲਾਂਕਣ ਨਹੀਂ" + "plugin-review:unrated": "ਹਾਲੇ ਮੁਲਾਂਕਣ ਨਹੀਂ", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/ru-ru.json b/src/lang/ru-ru.json index a6beb358a3..1628494470 100644 --- a/src/lang/ru-ru.json +++ b/src/lang/ru-ru.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "Отзыв должен содержать не более {max} символов.", "plugin-review:error-downvote-comment": "Расскажите разработчику, что можно улучшить.", "plugin-review:error-empty": "Добавьте комментарий или выберите «Нравится».", - "plugin-review:unrated": "Нет оценок" + "plugin-review:unrated": "Нет оценок", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/tl-ph.json b/src/lang/tl-ph.json index 62b428b457..3cfed0807a 100644 --- a/src/lang/tl-ph.json +++ b/src/lang/tl-ph.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "Dapat {max} character o mas kaunti ang review.", "plugin-review:error-downvote-comment": "Sabihin sa developer kung ano ang maaaring mapabuti.", "plugin-review:error-empty": "Magdagdag ng komento o piliin ang Gusto.", - "plugin-review:unrated": "Walang rating" + "plugin-review:unrated": "Walang rating", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/tr-tr.json b/src/lang/tr-tr.json index b894433d5a..39898581ad 100644 --- a/src/lang/tr-tr.json +++ b/src/lang/tr-tr.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "Değerlendirme en fazla {max} karakter olmalıdır.", "plugin-review:error-downvote-comment": "Geliştiriciye nelerin iyileştirilebileceğini söyleyin.", "plugin-review:error-empty": "Yorum ekleyin veya Beğendim’i seçin.", - "plugin-review:unrated": "Değerlendirilmedi" + "plugin-review:unrated": "Değerlendirilmedi", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/uk-ua.json b/src/lang/uk-ua.json index 3e3352008d..84f79fc2bb 100644 --- a/src/lang/uk-ua.json +++ b/src/lang/uk-ua.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "Відгук має містити не більше {max} символів.", "plugin-review:error-downvote-comment": "Розкажіть розробнику, що можна покращити.", "plugin-review:error-empty": "Додайте коментар або виберіть «Подобається».", - "plugin-review:unrated": "Без оцінки" + "plugin-review:unrated": "Без оцінки", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/uz-uz.json b/src/lang/uz-uz.json index e116cdc27a..04c6f91dc2 100644 --- a/src/lang/uz-uz.json +++ b/src/lang/uz-uz.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "Sharh {max} ta yoki undan kam belgidan iborat bo‘lishi kerak.", "plugin-review:error-downvote-comment": "Dasturchiga nimani yaxshilash mumkinligini ayting.", "plugin-review:error-empty": "Izoh qo‘shing yoki “Yoqdi”ni tanlang.", - "plugin-review:unrated": "Baholanmagan" + "plugin-review:unrated": "Baholanmagan", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/vi-vn.json b/src/lang/vi-vn.json index 0ddcf6a3f5..54f3850ff2 100644 --- a/src/lang/vi-vn.json +++ b/src/lang/vi-vn.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "Đánh giá phải có tối đa {max} ký tự.", "plugin-review:error-downvote-comment": "Hãy cho nhà phát triển biết điều gì có thể được cải thiện.", "plugin-review:error-empty": "Thêm bình luận hoặc chọn Thích.", - "plugin-review:unrated": "Chưa đánh giá" + "plugin-review:unrated": "Chưa đánh giá", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/zh-cn.json b/src/lang/zh-cn.json index 6d884b3699..c945e84c8c 100644 --- a/src/lang/zh-cn.json +++ b/src/lang/zh-cn.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "评价不能超过 {max} 个字符。", "plugin-review:error-downvote-comment": "告诉开发者哪些地方可以改进。", "plugin-review:error-empty": "添加评论或选择“赞”。", - "plugin-review:unrated": "暂无评分" + "plugin-review:unrated": "暂无评分", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/zh-hant.json b/src/lang/zh-hant.json index 74da96ad78..86399804bc 100644 --- a/src/lang/zh-hant.json +++ b/src/lang/zh-hant.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "評論不得超過 {max} 個字元。", "plugin-review:error-downvote-comment": "告訴開發者哪些地方可以改進。", "plugin-review:error-empty": "新增留言或選擇「讚」。", - "plugin-review:unrated": "尚未評分" + "plugin-review:unrated": "尚未評分", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } diff --git a/src/lang/zh-tw.json b/src/lang/zh-tw.json index 374cff0a61..01f2957f16 100644 --- a/src/lang/zh-tw.json +++ b/src/lang/zh-tw.json @@ -876,5 +876,10 @@ "plugin-review:error-max-length": "評論不得超過 {max} 個字元。", "plugin-review:error-downvote-comment": "告訴開發者哪些地方可以改進。", "plugin-review:error-empty": "新增留言或選擇「讚」。", - "plugin-review:unrated": "尚未評分" + "plugin-review:unrated": "尚未評分", + "wrapped line indent": "Word wrap indentation", + "wrap-indent-same": "Same", + "wrap-indent-indent": "Indent (+1 level)", + "wrap-indent-deep": "Deep indent (+2 levels)", + "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." } From 2fc72ca9fe2b15e1f493ea51462461fc6dba289a Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:44:10 +0530 Subject: [PATCH 3/3] fix --- src/cm/indentedLineWrapping.ts | 7 +++++-- tests/unit/indentedLineWrapping.test.ts | 22 +++++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/cm/indentedLineWrapping.ts b/src/cm/indentedLineWrapping.ts index cddc531441..bc7f3b8d2b 100644 --- a/src/cm/indentedLineWrapping.ts +++ b/src/cm/indentedLineWrapping.ts @@ -28,9 +28,12 @@ export function wrappedIndentColumns( ): number { const extra = mode === "indent" ? tabSize : mode === "deepIndent" ? 2 * tabSize : 0; - // Unindented/minified lines need no tab scan, even when they are megabytes long. if (mode === "none" || limit <= 0) return 0; - if (!extra && text[0] !== " " && text[0] !== "\t") return 0; + if (text[0] !== " " && text[0] !== "\t") { + // Extra levels are whole tab stops, so content tabs cannot affect them. + // Keep the width cap tab-aligned too, avoiding a scan even in narrow panes. + return Math.min(extra, Math.floor(limit / tabSize) * tabSize); + } // Include content tabs deliberately: negative text-indent changes the origin // of every tab stop on the first visual row. For " key\tvalue", using 2ch // shifts "value" left by two columns in Chromium; 4ch preserves its position. diff --git a/tests/unit/indentedLineWrapping.test.ts b/tests/unit/indentedLineWrapping.test.ts index 4dbff4f5d5..d5967ef77b 100644 --- a/tests/unit/indentedLineWrapping.test.ts +++ b/tests/unit/indentedLineWrapping.test.ts @@ -1,5 +1,5 @@ import { wrappedIndentColumns } from "cm/indentedLineWrapping"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; describe("wrapped line indentation", () => { it("preserves space indentation and stops at content", () => { @@ -37,4 +37,24 @@ describe("wrapped line indentation", () => { expect(wrappedIndentColumns(" key\tvalue", 4, 7, "deepIndent")).toBe(4); expect(wrappedIndentColumns(" code", 4, 7, "deepIndent")).toBe(7); }); + it("caps unindented lines at whole tab stops without depending on content tabs", () => { + for (const text of ["code", "key\tvalue"]) { + expect(wrappedIndentColumns(text, 4, 7, "deepIndent")).toBe(4); + expect(wrappedIndentColumns(text, 4, 3, "indent")).toBe(0); + } + }); + it("skips the full-line tab search for large unindented lines in extra-indent modes", () => { + const text = "x".repeat(1_000_000) + "\tvalue"; + const search = vi.spyOn(String.prototype, "includes"); + let calls: number; + try { + wrappedIndentColumns(text, 4, 40, "indent"); + wrappedIndentColumns(text, 4, 40, "deepIndent"); + wrappedIndentColumns(text, 4, 7, "deepIndent"); + calls = search.mock.calls.length; + } finally { + search.mockRestore(); + } + expect(calls).toBe(0); + }); });