@fresh-editor/fresh-editor 0.2.5 → 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,354 @@
1
+ /// <reference path="./lib/fresh.d.ts" />
2
+ // Markdown Source Mode Plugin
3
+ // Provides smart editing features for Markdown files in source (non-compose) mode:
4
+ // - Enter: auto-continue list items (bullets, ordered, checkboxes) with matching
5
+ // indentation; on an empty list item, removes the marker instead
6
+ // - Tab: on a blank list item, indents and cycles the bullet (* -> - -> + -> *);
7
+ // otherwise inserts spaces (always spaces, never literal tabs)
8
+ // - Shift+Tab: on a blank list item, de-indents and cycles the bullet in reverse
9
+ // (+ -> - -> * -> +); otherwise falls through to built-in dedent_selection
10
+ //
11
+ // This plugin defines a "markdown-source" mode that auto-activates when a
12
+ // markdown file is opened in source view, or when the buffer language is set
13
+ // to markdown. It uses readOnly=false so that normal character insertion is
14
+ // unaffected.
15
+
16
+ const editor = getEditor();
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Configuration
20
+ // ---------------------------------------------------------------------------
21
+
22
+ const TAB_SIZE = 4;
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Helpers
26
+ // ---------------------------------------------------------------------------
27
+
28
+ function isMarkdownFile(path: string): boolean {
29
+ return path.endsWith(".md") || path.endsWith(".markdown") || path.endsWith(".mdx");
30
+ }
31
+
32
+ function isMarkdownLanguage(language: string): boolean {
33
+ return language === "markdown" || language === "multimarkdown";
34
+ }
35
+
36
+ // Check whether a buffer should use markdown-source mode based on its
37
+ // file path OR its language setting.
38
+ function isMarkdownBuffer(info: BufferInfo): boolean {
39
+ return isMarkdownFile(info.path) || isMarkdownLanguage(info.language);
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // List marker parsing
44
+ // ---------------------------------------------------------------------------
45
+
46
+ interface ListMarkerInfo {
47
+ type: "unordered" | "ordered" | "checkbox";
48
+ indent: string; // leading whitespace
49
+ bullet?: string; // -, *, + for unordered/checkbox
50
+ number?: string; // "1", "10" etc. for ordered
51
+ checked?: boolean; // for checkbox
52
+ content: string; // text after marker (may be empty)
53
+ }
54
+
55
+ // Parse the text of a line (from line start up to cursor) for a list marker.
56
+ // Returns null if the line doesn't start with a recognised list pattern.
57
+ function parseListMarker(lineText: string): ListMarkerInfo | null {
58
+ let indent = "";
59
+ let i = 0;
60
+ while (i < lineText.length && (lineText[i] === " " || lineText[i] === "\t")) {
61
+ indent += lineText[i];
62
+ i++;
63
+ }
64
+ const rest = lineText.substring(i);
65
+
66
+ // Checkbox: "- [ ] content" / "* [x] content" / "+ [X] "
67
+ const cbMatch = rest.match(/^([-*+]) \[([ xX])\] (.*)$/);
68
+ if (cbMatch) {
69
+ return {
70
+ type: "checkbox",
71
+ indent,
72
+ bullet: cbMatch[1],
73
+ checked: cbMatch[2] !== " ",
74
+ content: cbMatch[3],
75
+ };
76
+ }
77
+
78
+ // Ordered list: "1. content" / "10. "
79
+ const olMatch = rest.match(/^(\d+)\. (.*)$/);
80
+ if (olMatch) {
81
+ return {
82
+ type: "ordered",
83
+ indent,
84
+ number: olMatch[1],
85
+ content: olMatch[2],
86
+ };
87
+ }
88
+
89
+ // Unordered list: "- content" / "* content" / "+ content"
90
+ const ulMatch = rest.match(/^([-*+]) (.*)$/);
91
+ if (ulMatch) {
92
+ return {
93
+ type: "unordered",
94
+ indent,
95
+ bullet: ulMatch[1],
96
+ content: ulMatch[2],
97
+ };
98
+ }
99
+
100
+ return null;
101
+ }
102
+
103
+ // Build the marker text for the *next* list item (used by Enter handler).
104
+ function nextMarkerText(info: ListMarkerInfo): string {
105
+ if (info.type === "ordered") {
106
+ const num = parseInt(info.number!, 10) + 1;
107
+ return info.indent + num + ". ";
108
+ }
109
+ if (info.type === "checkbox") {
110
+ // New checkbox is always unchecked
111
+ return info.indent + info.bullet + " [ ] ";
112
+ }
113
+ // unordered — same bullet
114
+ return info.indent + info.bullet + " ";
115
+ }
116
+
117
+ // Cycle bullet character forward: * -> - -> + -> *
118
+ function cycleBullet(bullet: string): string {
119
+ switch (bullet) {
120
+ case "*": return "-";
121
+ case "-": return "+";
122
+ case "+": return "*";
123
+ default: return "-";
124
+ }
125
+ }
126
+
127
+ // Cycle bullet character in reverse: * -> + -> - -> *
128
+ function reverseCycleBullet(bullet: string): string {
129
+ switch (bullet) {
130
+ case "*": return "+";
131
+ case "+": return "-";
132
+ case "-": return "*";
133
+ default: return "-";
134
+ }
135
+ }
136
+
137
+ // Read the text on the current line after the cursor (up to the next newline).
138
+ async function readRestOfLine(bufferId: number, cursorPos: number): Promise<string> {
139
+ const bufLen = editor.getBufferLength(bufferId);
140
+ const afterLen = Math.min(1024, bufLen - cursorPos);
141
+ if (afterLen <= 0) return "";
142
+ const textAfter = await editor.getBufferText(bufferId, cursorPos, cursorPos + afterLen);
143
+ const nextNl = textAfter.indexOf("\n");
144
+ return nextNl >= 0 ? textAfter.substring(0, nextNl) : textAfter;
145
+ }
146
+
147
+ // ---------------------------------------------------------------------------
148
+ // Enter handler: auto-continue list items or match indentation
149
+ // ---------------------------------------------------------------------------
150
+
151
+ globalThis.md_src_enter = async function (): Promise<void> {
152
+ const bufferId = editor.getActiveBufferId();
153
+ if (!bufferId) {
154
+ editor.executeAction("insert_newline");
155
+ return;
156
+ }
157
+
158
+ // When multiple cursors are active, fall back to the built-in insert_newline
159
+ // action which correctly handles all cursors atomically. The markdown-specific
160
+ // list continuation logic below only operates on the primary cursor.
161
+ const allCursors = editor.getAllCursors();
162
+ if (allCursors.length > 1) {
163
+ editor.executeAction("insert_newline");
164
+ return;
165
+ }
166
+
167
+ const cursorPos = editor.getCursorPosition();
168
+
169
+ // Read a window of text before the cursor to find the current line.
170
+ const windowStart = Math.max(0, cursorPos - 1024);
171
+ const textWindow = await editor.getBufferText(bufferId, windowStart, cursorPos);
172
+
173
+ const lastNl = textWindow.lastIndexOf("\n");
174
+ const lineText = lastNl >= 0 ? textWindow.substring(lastNl + 1) : textWindow;
175
+
176
+ // Try to parse as a list item
177
+ const listMatch = parseListMarker(lineText);
178
+
179
+ if (listMatch) {
180
+ const restOfLine = await readRestOfLine(bufferId, cursorPos);
181
+
182
+ if (listMatch.content.trim() === "" && restOfLine.trim() === "") {
183
+ // Empty list item (just marker, no content) — remove the marker
184
+ const lineStartByte = cursorPos - editor.utf8ByteLength(lineText);
185
+ const lineEndByte = cursorPos + editor.utf8ByteLength(restOfLine);
186
+ editor.deleteRange(bufferId, lineStartByte, lineEndByte);
187
+ return;
188
+ }
189
+
190
+ // Non-empty list item — insert newline + next marker
191
+ editor.insertAtCursor("\n" + nextMarkerText(listMatch));
192
+ return;
193
+ }
194
+
195
+ // No list marker — just copy leading whitespace
196
+ let indent = "";
197
+ for (let i = 0; i < lineText.length; i++) {
198
+ const ch = lineText[i];
199
+ if (ch === " " || ch === "\t") {
200
+ indent += ch;
201
+ } else {
202
+ break;
203
+ }
204
+ }
205
+ editor.insertAtCursor("\n" + indent);
206
+ };
207
+
208
+ // ---------------------------------------------------------------------------
209
+ // Tab handler: indent + cycle bullet on blank list items, else insert spaces
210
+ // ---------------------------------------------------------------------------
211
+
212
+ globalThis.md_src_tab = async function (): Promise<void> {
213
+ const bufferId = editor.getActiveBufferId();
214
+ if (!bufferId) {
215
+ editor.insertAtCursor(" ".repeat(TAB_SIZE));
216
+ return;
217
+ }
218
+
219
+ const cursorPos = editor.getCursorPosition();
220
+ const windowStart = Math.max(0, cursorPos - 1024);
221
+ const textBefore = await editor.getBufferText(bufferId, windowStart, cursorPos);
222
+
223
+ const lastNl = textBefore.lastIndexOf("\n");
224
+ const lineText = lastNl >= 0 ? textBefore.substring(lastNl + 1) : textBefore;
225
+
226
+ const listMatch = parseListMarker(lineText);
227
+
228
+ if (listMatch && (listMatch.type === "unordered" || listMatch.type === "checkbox")) {
229
+ const restOfLine = await readRestOfLine(bufferId, cursorPos);
230
+
231
+ if (listMatch.content.trim() === "" && restOfLine.trim() === "") {
232
+ // Blank list item — indent + cycle bullet
233
+ const lineStartByte = cursorPos - editor.utf8ByteLength(lineText);
234
+ const lineEndByte = cursorPos + editor.utf8ByteLength(restOfLine);
235
+ const newBullet = cycleBullet(listMatch.bullet!);
236
+ let newLine: string;
237
+ if (listMatch.type === "checkbox") {
238
+ const check = listMatch.checked ? "x" : " ";
239
+ newLine = listMatch.indent + " ".repeat(TAB_SIZE) + newBullet + " [" + check + "] ";
240
+ } else {
241
+ newLine = listMatch.indent + " ".repeat(TAB_SIZE) + newBullet + " ";
242
+ }
243
+ editor.deleteRange(bufferId, lineStartByte, lineEndByte);
244
+ editor.insertText(bufferId, lineStartByte, newLine);
245
+ editor.setBufferCursor(bufferId, lineStartByte + editor.utf8ByteLength(newLine));
246
+ return;
247
+ }
248
+ }
249
+
250
+ // Default: insert spaces
251
+ editor.insertAtCursor(" ".repeat(TAB_SIZE));
252
+ };
253
+
254
+ // ---------------------------------------------------------------------------
255
+ // Shift+Tab handler: de-indent + reverse-cycle bullet on blank list items
256
+ // ---------------------------------------------------------------------------
257
+
258
+ globalThis.md_src_shift_tab = async function (): Promise<void> {
259
+ const bufferId = editor.getActiveBufferId();
260
+ if (!bufferId) {
261
+ editor.executeAction("dedent_selection");
262
+ return;
263
+ }
264
+
265
+ const cursorPos = editor.getCursorPosition();
266
+ const windowStart = Math.max(0, cursorPos - 1024);
267
+ const textBefore = await editor.getBufferText(bufferId, windowStart, cursorPos);
268
+
269
+ const lastNl = textBefore.lastIndexOf("\n");
270
+ const lineText = lastNl >= 0 ? textBefore.substring(lastNl + 1) : textBefore;
271
+
272
+ const listMatch = parseListMarker(lineText);
273
+
274
+ if (listMatch && (listMatch.type === "unordered" || listMatch.type === "checkbox")) {
275
+ const restOfLine = await readRestOfLine(bufferId, cursorPos);
276
+
277
+ if (listMatch.content.trim() === "" && restOfLine.trim() === "") {
278
+ // Blank list item — de-indent + reverse cycle bullet
279
+ const currentIndent = listMatch.indent;
280
+ // Only de-indent if there is indentation to remove
281
+ if (currentIndent.length >= TAB_SIZE) {
282
+ const lineStartByte = cursorPos - editor.utf8ByteLength(lineText);
283
+ const lineEndByte = cursorPos + editor.utf8ByteLength(restOfLine);
284
+ const newBullet = reverseCycleBullet(listMatch.bullet!);
285
+ const newIndent = currentIndent.substring(TAB_SIZE);
286
+ let newLine: string;
287
+ if (listMatch.type === "checkbox") {
288
+ const check = listMatch.checked ? "x" : " ";
289
+ newLine = newIndent + newBullet + " [" + check + "] ";
290
+ } else {
291
+ newLine = newIndent + newBullet + " ";
292
+ }
293
+ editor.deleteRange(bufferId, lineStartByte, lineEndByte);
294
+ editor.insertText(bufferId, lineStartByte, newLine);
295
+ editor.setBufferCursor(bufferId, lineStartByte + editor.utf8ByteLength(newLine));
296
+ return;
297
+ }
298
+ }
299
+ }
300
+
301
+ // Default: fall through to built-in dedent
302
+ editor.executeAction("dedent_selection");
303
+ };
304
+
305
+ // ---------------------------------------------------------------------------
306
+ // Mode definition
307
+ // ---------------------------------------------------------------------------
308
+
309
+ // Define a non-read-only mode so unmapped keys insert normally.
310
+ // Enter, Tab, and Shift+Tab are intercepted for smart list handling.
311
+ editor.defineMode("markdown-source", null, [
312
+ ["Enter", "md_src_enter"],
313
+ ["Tab", "md_src_tab"],
314
+ ["BackTab", "md_src_shift_tab"],
315
+ ], false);
316
+
317
+ // ---------------------------------------------------------------------------
318
+ // Auto-activation: switch mode when a markdown file is focused or language changes
319
+ // ---------------------------------------------------------------------------
320
+
321
+ function updateMarkdownMode(): void {
322
+ const bufferId = editor.getActiveBufferId();
323
+ if (!bufferId) return;
324
+
325
+ const info = editor.getBufferInfo(bufferId);
326
+ if (!info) return;
327
+
328
+ const currentMode = editor.getEditorMode();
329
+
330
+ if (isMarkdownBuffer(info) && info.view_mode === "source") {
331
+ // Only activate if no other mode is already set (e.g., vi-mode)
332
+ if (currentMode == null) {
333
+ editor.setEditorMode("markdown-source");
334
+ }
335
+ } else {
336
+ // Leaving a markdown file or switching to compose mode: deactivate
337
+ if (currentMode === "markdown-source") {
338
+ editor.setEditorMode(null);
339
+ }
340
+ }
341
+ }
342
+
343
+ globalThis.md_src_on_buffer_activated = function (): void {
344
+ updateMarkdownMode();
345
+ };
346
+
347
+ globalThis.md_src_on_language_changed = function (): void {
348
+ updateMarkdownMode();
349
+ };
350
+
351
+ editor.on("buffer_activated", "md_src_on_buffer_activated");
352
+ editor.on("language_changed", "md_src_on_language_changed");
353
+
354
+ editor.debug("markdown_source plugin loaded");
@@ -145,6 +145,15 @@
145
145
  50,
146
146
  50
147
147
  ]
148
+ },
149
+ "whitespace_indicator_fg": {
150
+ "description": "Whitespace indicator foreground color (for tab arrows and space dots)",
151
+ "$ref": "#/$defs/ColorDef",
152
+ "default": [
153
+ 70,
154
+ 70,
155
+ 70
156
+ ]
148
157
  }
149
158
  }
150
159
  },
@@ -293,7 +293,9 @@
293
293
  "field.settings_selected_fg": "Settings Selected Foreground",
294
294
  "field.settings_selected_fg_desc": "Text color for selected setting",
295
295
  "field.popup_selection_fg": "vyskakovací okno výběr popředí",
296
- "field.popup_selection_fg_desc": "vyskakovací okno selected item text barva"
296
+ "field.popup_selection_fg_desc": "vyskakovací okno selected item text barva",
297
+ "field.whitespace_indicator_fg": "Bílé znaky Indikátor popředí",
298
+ "field.whitespace_indicator_fg_desc": "Barva popředí indikátorů bílých znaků (šipky tabulátorů a tečky mezer)"
297
299
  },
298
300
  "de": {
299
301
  "cmd.edit_theme": "Theme bearbeiten",
@@ -589,7 +591,9 @@
589
591
  "field.settings_selected_fg": "Settings Selected Foreground",
590
592
  "field.settings_selected_fg_desc": "Text color for selected setting",
591
593
  "field.popup_selection_fg": "Popup Auswahl Vordergrund",
592
- "field.popup_selection_fg_desc": "Textfarbe des ausgewaehlten Popup-Elements"
594
+ "field.popup_selection_fg_desc": "Textfarbe des ausgewaehlten Popup-Elements",
595
+ "field.whitespace_indicator_fg": "Leerzeichen-Indikator Vordergrund",
596
+ "field.whitespace_indicator_fg_desc": "Vordergrundfarbe für Leerzeichen-Indikatoren (Tab-Pfeile und Leerzeichen-Punkte)"
593
597
  },
594
598
  "en": {
595
599
  "cmd.edit_theme": "Edit Theme",
@@ -885,7 +889,9 @@
885
889
  "field.settings_selected_fg": "Settings Selected Foreground",
886
890
  "field.settings_selected_fg_desc": "Text color for selected setting",
887
891
  "field.popup_selection_fg": "Popup Selection Foreground",
888
- "field.popup_selection_fg_desc": "Popup selected item text color"
892
+ "field.popup_selection_fg_desc": "Popup selected item text color",
893
+ "field.whitespace_indicator_fg": "Whitespace Indicator Foreground",
894
+ "field.whitespace_indicator_fg_desc": "Foreground color for whitespace indicators (tab arrows and space dots)"
889
895
  },
890
896
  "es": {
891
897
  "cmd.edit_theme": "Editar tema",
@@ -1181,7 +1187,9 @@
1181
1187
  "field.settings_selected_fg": "Settings Selected Foreground",
1182
1188
  "field.settings_selected_fg_desc": "Text color for selected setting",
1183
1189
  "field.popup_selection_fg": "Fondo de seleccion de ventana emergente",
1184
- "field.popup_selection_fg_desc": "Fondo de elemento seleccionado en ventana emergente"
1190
+ "field.popup_selection_fg_desc": "Fondo de elemento seleccionado en ventana emergente",
1191
+ "field.whitespace_indicator_fg": "Indicador de espacios en blanco primer plano",
1192
+ "field.whitespace_indicator_fg_desc": "Color de primer plano para indicadores de espacios en blanco (flechas de tabulación y puntos de espacio)"
1185
1193
  },
1186
1194
  "fr": {
1187
1195
  "cmd.edit_theme": "Modifier le theme",
@@ -1477,7 +1485,9 @@
1477
1485
  "field.settings_selected_fg": "Settings Selected Foreground",
1478
1486
  "field.settings_selected_fg_desc": "Text color for selected setting",
1479
1487
  "field.popup_selection_fg": "Premier plan selection popup",
1480
- "field.popup_selection_fg_desc": "Couleur du texte de l'element selectionne du popup"
1488
+ "field.popup_selection_fg_desc": "Couleur du texte de l'element selectionne du popup",
1489
+ "field.whitespace_indicator_fg": "Indicateur d'espaces premier plan",
1490
+ "field.whitespace_indicator_fg_desc": "Couleur de premier plan pour les indicateurs d'espaces (flèches de tabulation et points d'espace)"
1481
1491
  },
1482
1492
  "ja": {
1483
1493
  "cmd.edit_theme": "テーマを編集",
@@ -1773,7 +1783,9 @@
1773
1783
  "field.settings_selected_fg": "Settings Selected Foreground",
1774
1784
  "field.settings_selected_fg_desc": "Text color for selected setting",
1775
1785
  "field.popup_selection_fg": "ポップアップ選択前景",
1776
- "field.popup_selection_fg_desc": "ポップアップの選択項目の文字颜色"
1786
+ "field.popup_selection_fg_desc": "ポップアップの選択項目の文字颜色",
1787
+ "field.whitespace_indicator_fg": "空白インジケーター前景",
1788
+ "field.whitespace_indicator_fg_desc": "空白インジケーターの前景色(タブ矢印とスペースドット)"
1777
1789
  },
1778
1790
  "ko": {
1779
1791
  "cmd.edit_theme": "편집 Theme",
@@ -2069,7 +2081,9 @@
2069
2081
  "field.settings_selected_fg": "Settings Selected Foreground",
2070
2082
  "field.settings_selected_fg_desc": "Text color for selected setting",
2071
2083
  "field.popup_selection_fg": "팝업 선택 전경",
2072
- "field.popup_selection_fg_desc": "팝업 selected item 텍스트 색상"
2084
+ "field.popup_selection_fg_desc": "팝업 selected item 텍스트 색상",
2085
+ "field.whitespace_indicator_fg": "공백 표시기 전경",
2086
+ "field.whitespace_indicator_fg_desc": "공백 표시기의 전경색 (탭 화살표 및 공백 점)"
2073
2087
  },
2074
2088
  "pt-BR": {
2075
2089
  "cmd.edit_theme": "editar Theme",
@@ -2365,7 +2379,9 @@
2365
2379
  "field.settings_selected_fg": "Settings Selected Foreground",
2366
2380
  "field.settings_selected_fg_desc": "Text color for selected setting",
2367
2381
  "field.popup_selection_fg": "popup seleção primeiro plano",
2368
- "field.popup_selection_fg_desc": "popup selected item texto cor"
2382
+ "field.popup_selection_fg_desc": "popup selected item texto cor",
2383
+ "field.whitespace_indicator_fg": "Indicador de espaço em branco primeiro plano",
2384
+ "field.whitespace_indicator_fg_desc": "Cor de primeiro plano para indicadores de espaço em branco (setas de tabulação e pontos de espaço)"
2369
2385
  },
2370
2386
  "ru": {
2371
2387
  "cmd.edit_theme": "редактировать Theme",
@@ -2661,7 +2677,9 @@
2661
2677
  "field.settings_selected_fg": "Settings Selected Foreground",
2662
2678
  "field.settings_selected_fg_desc": "Text color for selected setting",
2663
2679
  "field.popup_selection_fg": "всплывающее окно выделение передний план",
2664
- "field.popup_selection_fg_desc": "всплывающее окно selected item текст цвет"
2680
+ "field.popup_selection_fg_desc": "всплывающее окно selected item текст цвет",
2681
+ "field.whitespace_indicator_fg": "Индикатор пробелов передний план",
2682
+ "field.whitespace_indicator_fg_desc": "Цвет переднего плана для индикаторов пробелов (стрелки табуляции и точки пробелов)"
2665
2683
  },
2666
2684
  "th": {
2667
2685
  "cmd.edit_theme": "แก้ไข Theme",
@@ -2957,7 +2975,9 @@
2957
2975
  "field.settings_selected_fg": "Settings Selected Foreground",
2958
2976
  "field.settings_selected_fg_desc": "Text color for selected setting",
2959
2977
  "field.popup_selection_fg": "ป๊อปอัป การเลือก พื้นหน้า",
2960
- "field.popup_selection_fg_desc": "ป๊อปอัป selected item ข้อความ สี"
2978
+ "field.popup_selection_fg_desc": "ป๊อปอัป selected item ข้อความ สี",
2979
+ "field.whitespace_indicator_fg": "ตัวบ่งชี้ช่องว่างพื้นหน้า",
2980
+ "field.whitespace_indicator_fg_desc": "สีพื้นหน้าสำหรับตัวบ่งชี้ช่องว่าง (ลูกศรแท็บและจุดเว้นวรรค)"
2961
2981
  },
2962
2982
  "uk": {
2963
2983
  "cmd.edit_theme": "редагувати Theme",
@@ -3253,7 +3273,9 @@
3253
3273
  "field.settings_selected_fg": "Settings Selected Foreground",
3254
3274
  "field.settings_selected_fg_desc": "Text color for selected setting",
3255
3275
  "field.popup_selection_fg": "спливаюче вікно виділення передний план",
3256
- "field.popup_selection_fg_desc": "спливаюче вікно selected item текст цвет"
3276
+ "field.popup_selection_fg_desc": "спливаюче вікно selected item текст цвет",
3277
+ "field.whitespace_indicator_fg": "Індикатор пробілів передній план",
3278
+ "field.whitespace_indicator_fg_desc": "Колір переднього плану для індикаторів пробілів (стрілки табуляції та крапки пробілів)"
3257
3279
  },
3258
3280
  "vi": {
3259
3281
  "cmd.edit_theme": "Chỉnh sửa giao diện",
@@ -3549,7 +3571,9 @@
3549
3571
  "field.settings_selected_fg": "Tiền cảnh cài đặt đã chọn",
3550
3572
  "field.settings_selected_fg_desc": "Màu văn bản cho cài đặt đã chọn",
3551
3573
  "field.popup_selection_fg": "Tiền cảnh lựa chọn cửa sổ bật lên",
3552
- "field.popup_selection_fg_desc": "Màu văn bản mục đã chọn trong cửa sổ bật lên"
3574
+ "field.popup_selection_fg_desc": "Màu văn bản mục đã chọn trong cửa sổ bật lên",
3575
+ "field.whitespace_indicator_fg": "Chỉ báo khoảng trắng tiền cảnh",
3576
+ "field.whitespace_indicator_fg_desc": "Màu tiền cảnh cho chỉ báo khoảng trắng (mũi tên tab và dấu chấm khoảng trắng)"
3553
3577
  },
3554
3578
  "zh-CN": {
3555
3579
  "cmd.edit_theme": "编辑主题",
@@ -3845,7 +3869,9 @@
3845
3869
  "field.settings_selected_fg": "Settings Selected Foreground",
3846
3870
  "field.settings_selected_fg_desc": "Text color for selected setting",
3847
3871
  "field.popup_selection_fg": "弹出窗口选择前景",
3848
- "field.popup_selection_fg_desc": "弹出窗口选中项文字颜色"
3872
+ "field.popup_selection_fg_desc": "弹出窗口选中项文字颜色",
3873
+ "field.whitespace_indicator_fg": "空白指示器前景",
3874
+ "field.whitespace_indicator_fg_desc": "空白指示器的前景颜色(制表符箭头和空格点)"
3849
3875
  },
3850
3876
  "it": {
3851
3877
  "cmd.edit_theme": "Modifica tema",
@@ -4141,6 +4167,8 @@
4141
4167
  "field.settings_selected_fg": "Settings Selected Foreground",
4142
4168
  "field.settings_selected_fg_desc": "Text color for selected setting",
4143
4169
  "field.popup_selection_fg": "Primo piano selezione popup",
4144
- "field.popup_selection_fg_desc": "Colore del testo dell elemento selezionato nel popup"
4170
+ "field.popup_selection_fg_desc": "Colore del testo dell elemento selezionato nel popup",
4171
+ "field.whitespace_indicator_fg": "Indicatore spazi bianchi primo piano",
4172
+ "field.whitespace_indicator_fg_desc": "Colore primo piano per gli indicatori di spazi bianchi (frecce di tabulazione e punti di spazio)"
4145
4173
  }
4146
- }
4174
+ }
@@ -163,7 +163,7 @@ function loadThemeSections(): ThemeSection[] {
163
163
  });
164
164
  }
165
165
 
166
- // Sort fields alphabetically (use simple comparison to avoid ICU issues in Deno)
166
+ // Sort fields alphabetically (use simple comparison to avoid ICU issues in QuickJS)
167
167
  fields.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
168
168
 
169
169
  // Generate i18n keys for section
package/themes/dark.json CHANGED
@@ -11,7 +11,8 @@
11
11
  "line_number_bg": [30, 30, 30],
12
12
  "diff_add_bg": [35, 60, 35],
13
13
  "diff_remove_bg": [70, 35, 35],
14
- "diff_modify_bg": [40, 38, 30]
14
+ "diff_modify_bg": [40, 38, 30],
15
+ "whitespace_indicator_fg": [70, 70, 70]
15
16
  },
16
17
  "ui": {
17
18
  "tab_active_fg": "Yellow",
@@ -7,7 +7,8 @@
7
7
  "selection_bg": [68, 71, 90],
8
8
  "current_line_bg": [50, 52, 66],
9
9
  "line_number_fg": [98, 114, 164],
10
- "line_number_bg": [40, 42, 54]
10
+ "line_number_bg": [40, 42, 54],
11
+ "whitespace_indicator_fg": [68, 71, 90]
11
12
  },
12
13
  "ui": {
13
14
  "tab_active_fg": [248, 248, 242],
@@ -11,7 +11,8 @@
11
11
  "line_number_bg": "Black",
12
12
  "diff_add_bg": [0, 80, 0],
13
13
  "diff_remove_bg": [100, 0, 0],
14
- "diff_modify_bg": [25, 22, 0]
14
+ "diff_modify_bg": [25, 22, 0],
15
+ "whitespace_indicator_fg": [80, 80, 80]
15
16
  },
16
17
  "ui": {
17
18
  "tab_active_fg": "Black",
package/themes/light.json CHANGED
@@ -11,7 +11,8 @@
11
11
  "line_number_bg": [255, 255, 255],
12
12
  "diff_add_bg": [200, 255, 200],
13
13
  "diff_remove_bg": [255, 200, 200],
14
- "diff_modify_bg": [255, 252, 240]
14
+ "diff_modify_bg": [255, 252, 240],
15
+ "whitespace_indicator_fg": [200, 200, 200]
15
16
  },
16
17
  "ui": {
17
18
  "tab_active_fg": [40, 40, 40],
package/themes/nord.json CHANGED
@@ -7,7 +7,8 @@
7
7
  "selection_bg": [67, 76, 94],
8
8
  "current_line_bg": [59, 66, 82],
9
9
  "line_number_fg": [76, 86, 106],
10
- "line_number_bg": [46, 52, 64]
10
+ "line_number_bg": [46, 52, 64],
11
+ "whitespace_indicator_fg": [67, 76, 94]
11
12
  },
12
13
  "ui": {
13
14
  "tab_active_fg": [236, 239, 244],
@@ -11,7 +11,8 @@
11
11
  "line_number_bg": [0, 0, 170],
12
12
  "diff_add_bg": [0, 100, 0],
13
13
  "diff_remove_bg": [170, 0, 0],
14
- "diff_modify_bg": [20, 20, 140]
14
+ "diff_modify_bg": [20, 20, 140],
15
+ "whitespace_indicator_fg": [0, 0, 100]
15
16
  },
16
17
  "ui": {
17
18
  "tab_active_fg": [0, 0, 0],
@@ -7,7 +7,8 @@
7
7
  "selection_bg": [7, 54, 66],
8
8
  "current_line_bg": [7, 54, 66],
9
9
  "line_number_fg": [88, 110, 117],
10
- "line_number_bg": [0, 43, 54]
10
+ "line_number_bg": [0, 43, 54],
11
+ "whitespace_indicator_fg": [0, 60, 75]
11
12
  },
12
13
  "ui": {
13
14
  "tab_active_fg": [253, 246, 227],