@fresh-editor/fresh-editor 0.2.20 → 0.2.21

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # Release Notes
2
2
 
3
+ ## 0.2.21
4
+
5
+ ### Features
6
+
7
+ * **Fast Completions without LSP**: New basic completions providers without language server — buffer-word candidates appear below LSP results in the popup. Also, a new setting (config) controls auto-trigger vs explicit Ctrl+Space (default: explicit). Enter dismisses the popup (Tab accepts). I plan to further improve it (make it more intelligent) in future releases.
8
+
9
+ * **Current Line Highlighting**: Highlights the cursor line. Enabled by default, togglable from the command palette and Settings UI (caveat: wrapped lines are currently highlighted in their entirety, this should probably be changed to visual lines).
10
+
11
+ * **LSP Code Actions**: Code action modal now actually works! Select an action by number or up/down arrows and enter (#1405). Supports resolve, execute command, and server-initiated workspace edits (previously dropped silently). File create/rename/delete operations handled. Actions from multiple servers unified into one popup. Default keybinding changed to Alt+. - because Ctrl+. is filtered by many terminals.
12
+
13
+ * **LSP Completion Resolve and Formatting**: Auto-imports applied on completion accept. Format Buffer falls back to LSP when no external formatter is configured. Also adds range formatting and pre-rename validation.
14
+
15
+ * **LSP Server Selection for Restart/Stop**: Popup to choose which server to restart/stop individually, or all at once.
16
+
17
+ * **Grammar Listing**: `fresh --cmd grammar list` and `editor.listGrammars()` plugin API show all available grammars with source and extensions. When specifying a grammar in a `languages` entry in the config, you must currently use a full name from this list - for example "Bourne Again Shell (bash)" rather than "bash". This will be improved once I add grammar aliases.
18
+
19
+ ### Improvements
20
+
21
+ * **Theme Contrast**: Replaced all named ANSI colors with explicit RGB in built-in themes for deterministic rendering. Improved contrast ratios across both high-contrast and light themes. Diagnostic and semantic overlays now re-apply correctly on theme change, including during live preview.
22
+
23
+ * **Git Status Marker Refresh**: File explorer markers update on terminal focus gain and by polling for git index changes (#1431).
24
+
25
+ * **Config-Only Languages**: Custom languages without a built-in grammar (e.g., "fish") appear in the Set Language popup and are detected correctly — no more fallthrough to wrong built-in grammars.
26
+
27
+ * **Theme Inspector**: Records exact theme keys during rendering instead of reverse-mapping via heuristics. Theme editor Save As improved for built-in themes.
28
+
29
+ * **LSP Reliability**: Diagnostics cleared on server stop/crash, buffers re-opened on server start, document version checking for workspace edits, LSP notified after undo/redo of bulk edits, pending requests drained on server death to prevent deadlocks, hover suppressed while popups are visible.
30
+
31
+ ### Vim Mode
32
+
33
+ 22 bug fixes: C/D/S/cc, e motion, nG, h/l line clamping, ^, $, J with space, f/t special chars, r replace, ~ toggle case, visual mode entry/switching, count display. Key motions moved from async plugin commands to native Rust actions, eliminating race conditions.
34
+
35
+ If you use the Vim plugin please drop a note at https://github.com/sinelaw/fresh/discussions/417 - I need feedback on this feature.
36
+
37
+ ### Bug Fixes
38
+
39
+ * Fixed Ctrl+W panic on accented/multi-byte characters (#1332).
40
+
41
+ * Fixed LSP diagnostics from stopped servers reappearing from queued messages.
42
+
3
43
  ## 0.2.20
4
44
 
5
45
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fresh-editor/fresh-editor",
3
- "version": "0.2.20",
3
+ "version": "0.2.21",
4
4
  "description": "A modern terminal-based text editor with plugin support",
5
5
  "repository": {
6
6
  "type": "git",
@@ -32,6 +32,7 @@
32
32
  "default": {
33
33
  "line_numbers": true,
34
34
  "relative_line_numbers": false,
35
+ "highlight_current_line": true,
35
36
  "line_wrap": true,
36
37
  "wrap_indent": true,
37
38
  "wrap_column": null,
@@ -66,10 +67,10 @@
66
67
  "ensure_final_newline_on_save": false,
67
68
  "highlight_matching_brackets": true,
68
69
  "rainbow_brackets": true,
70
+ "completion_popup_auto_show": false,
69
71
  "quick_suggestions": true,
70
72
  "quick_suggestions_delay_ms": 150,
71
73
  "suggest_on_trigger_characters": true,
72
- "accept_suggestion_on_enter": "on",
73
74
  "enable_inlay_hints": true,
74
75
  "enable_semantic_tokens_full": false,
75
76
  "diagnostics_inline_text": false,
@@ -251,6 +252,12 @@
251
252
  "default": false,
252
253
  "x-section": "Display"
253
254
  },
255
+ "highlight_current_line": {
256
+ "description": "Highlight the line containing the cursor",
257
+ "type": "boolean",
258
+ "default": true,
259
+ "x-section": "Display"
260
+ },
254
261
  "line_wrap": {
255
262
  "description": "Wrap long lines to fit the window width (default for new views)",
256
263
  "type": "boolean",
@@ -474,8 +481,14 @@
474
481
  "default": true,
475
482
  "x-section": "Bracket Matching"
476
483
  },
484
+ "completion_popup_auto_show": {
485
+ "description": "Automatically show the completion popup while typing.\nWhen false (default), the popup only appears when explicitly invoked\n(e.g. via Ctrl+Space). When true, it appears automatically after a\nshort delay while typing.\nDefault: false",
486
+ "type": "boolean",
487
+ "default": false,
488
+ "x-section": "Completion"
489
+ },
477
490
  "quick_suggestions": {
478
- "description": "Enable quick suggestions (VS Code-like behavior).\nWhen enabled, completion suggestions appear automatically while typing,\nnot just on trigger characters (like `.` or `::`).\nDefault: true",
491
+ "description": "Enable quick suggestions (VS Code-like behavior).\nWhen enabled, completion suggestions appear automatically while typing,\nnot just on trigger characters (like `.` or `::`).\nOnly takes effect when completion_popup_auto_show is true.\nDefault: true",
479
492
  "type": "boolean",
480
493
  "default": true,
481
494
  "x-section": "Completion"
@@ -494,12 +507,6 @@
494
507
  "default": true,
495
508
  "x-section": "Completion"
496
509
  },
497
- "accept_suggestion_on_enter": {
498
- "description": "Controls whether pressing Enter accepts the selected completion.\n - \"on\": Enter always accepts the completion\n - \"off\": Enter inserts a newline (use Tab to accept)\n - \"smart\": Enter accepts only if the completion text differs from typed text\n\nDefault: \"on\"",
499
- "$ref": "#/$defs/AcceptSuggestionOnEnter",
500
- "default": "on",
501
- "x-section": "Completion"
502
- },
503
510
  "enable_inlay_hints": {
504
511
  "description": "Whether to enable LSP inlay hints (type hints, parameter hints, etc.)",
505
512
  "type": "boolean",
@@ -687,16 +694,6 @@
687
694
  ],
688
695
  "default": "lf"
689
696
  },
690
- "AcceptSuggestionOnEnter": {
691
- "description": "Controls whether Enter accepts a completion suggestion",
692
- "type": "string",
693
- "enum": [
694
- "on",
695
- "off",
696
- "smart"
697
- ],
698
- "default": "on"
699
- },
700
697
  "FileExplorerConfig": {
701
698
  "description": "File explorer configuration",
702
699
  "type": "object",
@@ -1020,6 +1017,14 @@
1020
1017
  "$ref": "#/$defs/OnSaveAction"
1021
1018
  },
1022
1019
  "default": []
1020
+ },
1021
+ "word_characters": {
1022
+ "description": "Extra characters (beyond alphanumeric and `_`) considered part of\nidentifiers for this language. Used by dabbrev and buffer-word\ncompletion to correctly tokenise language-specific naming conventions.\n\nExamples:\n- Lisp/Clojure/CSS: `\"-\"` (kebab-case identifiers)\n- PHP/Bash: `\"$\"` (variable sigils)\n- Ruby: `\"?!\"` (predicate/bang methods)\n- Rust (default): `\"\"` (standard alphanumeric + underscore)",
1023
+ "type": [
1024
+ "string",
1025
+ "null"
1026
+ ],
1027
+ "default": null
1023
1028
  }
1024
1029
  },
1025
1030
  "x-display-field": "/grammar"
@@ -156,8 +156,14 @@ function onGitExplorerEditorInitialized() {
156
156
  }
157
157
  registerHandler("onGitExplorerEditorInitialized", onGitExplorerEditorInitialized);
158
158
 
159
+ function onGitExplorerFocusGained() {
160
+ refreshGitExplorerDecorations();
161
+ }
162
+ registerHandler("onGitExplorerFocusGained", onGitExplorerFocusGained);
163
+
159
164
  editor.on("after_file_open", "onGitExplorerAfterFileOpen");
160
165
  editor.on("after_file_save", "onGitExplorerAfterFileSave");
161
166
  editor.on("editor_initialized", "onGitExplorerEditorInitialized");
167
+ editor.on("focus_gained", "onGitExplorerFocusGained");
162
168
 
163
169
  refreshGitExplorerDecorations();
@@ -300,7 +300,9 @@
300
300
  "field.punctuation_bracket": "závorka",
301
301
  "field.punctuation_bracket_desc": "závorkas ({, }, (, ), [, ])",
302
302
  "field.punctuation_delimiter": "oddělovač",
303
- "field.punctuation_delimiter_desc": "oddělovačs (;, ,, .)"
303
+ "field.punctuation_delimiter_desc": "oddělovačs (;, ,, .)",
304
+ "suggestion.current_builtin": "(aktuální · vestavěný · pouze pro čtení)",
305
+ "prompt.save_as_builtin_error": "Nelze přepsat vestavěný motiv, zvolte jiný název: "
304
306
  },
305
307
  "de": {
306
308
  "cmd.edit_theme": "Theme bearbeiten",
@@ -603,7 +605,9 @@
603
605
  "field.punctuation_bracket": "Klammer",
604
606
  "field.punctuation_bracket_desc": "Klammern ({, }, (, ), [, ])",
605
607
  "field.punctuation_delimiter": "Trennzeichen",
606
- "field.punctuation_delimiter_desc": "Trennzeichen (;, ,, .)"
608
+ "field.punctuation_delimiter_desc": "Trennzeichen (;, ,, .)",
609
+ "suggestion.current_builtin": "(aktuell · eingebaut · schreibgeschützt)",
610
+ "prompt.save_as_builtin_error": "Eingebautes Theme kann nicht überschrieben werden, anderen Namen wählen: "
607
611
  },
608
612
  "en": {
609
613
  "cmd.edit_theme": "Edit Theme",
@@ -906,7 +910,9 @@
906
910
  "field.punctuation_bracket": "Punctuation Bracket",
907
911
  "field.punctuation_bracket_desc": "Brackets and parentheses ({, }, (, ), [, ])",
908
912
  "field.punctuation_delimiter": "Punctuation Delimiter",
909
- "field.punctuation_delimiter_desc": "Delimiters and separators (;, ,, .)"
913
+ "field.punctuation_delimiter_desc": "Delimiters and separators (;, ,, .)",
914
+ "suggestion.current_builtin": "(current · built-in · read-only)",
915
+ "prompt.save_as_builtin_error": "Can't override built-in theme, choose a different name: "
910
916
  },
911
917
  "es": {
912
918
  "cmd.edit_theme": "Editar tema",
@@ -1209,7 +1215,9 @@
1209
1215
  "field.punctuation_bracket": "Paréntesis",
1210
1216
  "field.punctuation_bracket_desc": "Paréntesis y corchetes ({, }, (, ), [, ])",
1211
1217
  "field.punctuation_delimiter": "Delimitador",
1212
- "field.punctuation_delimiter_desc": "Delimitadores y separadores (;, ,, .)"
1218
+ "field.punctuation_delimiter_desc": "Delimitadores y separadores (;, ,, .)",
1219
+ "suggestion.current_builtin": "(actual · predefinido · solo lectura)",
1220
+ "prompt.save_as_builtin_error": "No se puede sobrescribir un tema predefinido, elija otro nombre: "
1213
1221
  },
1214
1222
  "fr": {
1215
1223
  "cmd.edit_theme": "Modifier le theme",
@@ -1512,7 +1520,9 @@
1512
1520
  "field.punctuation_bracket": "Parenthese",
1513
1521
  "field.punctuation_bracket_desc": "Parentheses et crochets ({, }, (, ), [, ])",
1514
1522
  "field.punctuation_delimiter": "Delimiteur",
1515
- "field.punctuation_delimiter_desc": "Delimiteurs et separateurs (;, ,, .)"
1523
+ "field.punctuation_delimiter_desc": "Delimiteurs et separateurs (;, ,, .)",
1524
+ "suggestion.current_builtin": "(actuel · intégré · lecture seule)",
1525
+ "prompt.save_as_builtin_error": "Impossible de remplacer un thème intégré, choisissez un autre nom : "
1516
1526
  },
1517
1527
  "ja": {
1518
1528
  "cmd.edit_theme": "テーマを編集",
@@ -1815,7 +1825,9 @@
1815
1825
  "field.punctuation_bracket": "括弧",
1816
1826
  "field.punctuation_bracket_desc": "括弧 ({、}、(、)、[、])",
1817
1827
  "field.punctuation_delimiter": "区切り文字",
1818
- "field.punctuation_delimiter_desc": "区切り文字 (;、,、.)"
1828
+ "field.punctuation_delimiter_desc": "区切り文字 (;、,、.)",
1829
+ "suggestion.current_builtin": "(現在 · 組み込み · 読み取り専用)",
1830
+ "prompt.save_as_builtin_error": "組み込みテーマは上書きできません。別の名前を入力してください: "
1819
1831
  },
1820
1832
  "ko": {
1821
1833
  "cmd.edit_theme": "편집 Theme",
@@ -2118,7 +2130,9 @@
2118
2130
  "field.punctuation_bracket": "괄호",
2119
2131
  "field.punctuation_bracket_desc": "괄호s ({, }, (, ), [, ])",
2120
2132
  "field.punctuation_delimiter": "구분자",
2121
- "field.punctuation_delimiter_desc": "구분자s (;, ,, .)"
2133
+ "field.punctuation_delimiter_desc": "구분자s (;, ,, .)",
2134
+ "suggestion.current_builtin": "(현재 · 기본 제공 · 읽기 전용)",
2135
+ "prompt.save_as_builtin_error": "기본 제공 테마를 덮어쓸 수 없습니다. 다른 이름을 입력하세요: "
2122
2136
  },
2123
2137
  "pt-BR": {
2124
2138
  "cmd.edit_theme": "editar Theme",
@@ -2421,7 +2435,9 @@
2421
2435
  "field.punctuation_bracket": "parêntese",
2422
2436
  "field.punctuation_bracket_desc": "parênteses ({, }, (, ), [, ])",
2423
2437
  "field.punctuation_delimiter": "delimitador",
2424
- "field.punctuation_delimiter_desc": "delimitadors (;, ,, .)"
2438
+ "field.punctuation_delimiter_desc": "delimitadors (;, ,, .)",
2439
+ "suggestion.current_builtin": "(atual · integrado · somente leitura)",
2440
+ "prompt.save_as_builtin_error": "Não é possível sobrescrever tema integrado, escolha outro nome: "
2425
2441
  },
2426
2442
  "ru": {
2427
2443
  "cmd.edit_theme": "редактировать Theme",
@@ -2724,7 +2740,9 @@
2724
2740
  "field.punctuation_bracket": "скобка",
2725
2741
  "field.punctuation_bracket_desc": "скобкаs ({, }, (, ), [, ])",
2726
2742
  "field.punctuation_delimiter": "разделитель",
2727
- "field.punctuation_delimiter_desc": "разделительs (;, ,, .)"
2743
+ "field.punctuation_delimiter_desc": "разделительs (;, ,, .)",
2744
+ "suggestion.current_builtin": "(текущий · встроенная · только чтение)",
2745
+ "prompt.save_as_builtin_error": "Невозможно перезаписать встроенную тему, выберите другое имя: "
2728
2746
  },
2729
2747
  "th": {
2730
2748
  "cmd.edit_theme": "แก้ไข Theme",
@@ -3027,7 +3045,9 @@
3027
3045
  "field.punctuation_bracket": "วงเล็บ",
3028
3046
  "field.punctuation_bracket_desc": "วงเล็บs ({, }, (, ), [, ])",
3029
3047
  "field.punctuation_delimiter": "ตัวคั่น",
3030
- "field.punctuation_delimiter_desc": "ตัวคั่นs (;, ,, .)"
3048
+ "field.punctuation_delimiter_desc": "ตัวคั่นs (;, ,, .)",
3049
+ "suggestion.current_builtin": "(ปัจจุบัน · ในตัว · อ่านอย่างเดียว)",
3050
+ "prompt.save_as_builtin_error": "ไม่สามารถเขียนทับธีมในตัวได้ กรุณาเลือกชื่ออื่น: "
3031
3051
  },
3032
3052
  "uk": {
3033
3053
  "cmd.edit_theme": "редагувати Theme",
@@ -3330,7 +3350,9 @@
3330
3350
  "field.punctuation_bracket": "дужка",
3331
3351
  "field.punctuation_bracket_desc": "дужкаs ({, }, (, ), [, ])",
3332
3352
  "field.punctuation_delimiter": "роздільник",
3333
- "field.punctuation_delimiter_desc": "роздільникs (;, ,, .)"
3353
+ "field.punctuation_delimiter_desc": "роздільникs (;, ,, .)",
3354
+ "suggestion.current_builtin": "(поточний · вбудована · лише читання)",
3355
+ "prompt.save_as_builtin_error": "Неможливо перезаписати вбудовану тему, виберіть іншу назву: "
3334
3356
  },
3335
3357
  "vi": {
3336
3358
  "cmd.edit_theme": "Chỉnh sửa giao diện",
@@ -3633,7 +3655,9 @@
3633
3655
  "field.punctuation_bracket": "Dấu ngoặc",
3634
3656
  "field.punctuation_bracket_desc": "Dấu ngoặc ({, }, (, ), [, ])",
3635
3657
  "field.punctuation_delimiter": "Dấu phân cách",
3636
- "field.punctuation_delimiter_desc": "Dấu phân cách (;, ,, .)"
3658
+ "field.punctuation_delimiter_desc": "Dấu phân cách (;, ,, .)",
3659
+ "suggestion.current_builtin": "(hiện tại · có sẵn · chỉ đọc)",
3660
+ "prompt.save_as_builtin_error": "Không thể ghi đè chủ đề có sẵn, chọn tên khác: "
3637
3661
  },
3638
3662
  "zh-CN": {
3639
3663
  "cmd.edit_theme": "编辑主题",
@@ -3936,7 +3960,9 @@
3936
3960
  "field.punctuation_bracket": "括号",
3937
3961
  "field.punctuation_bracket_desc": "括号 ({、}、(、)、[、])",
3938
3962
  "field.punctuation_delimiter": "分隔符",
3939
- "field.punctuation_delimiter_desc": "分隔符 (;、,、.)"
3963
+ "field.punctuation_delimiter_desc": "分隔符 (;、,、.)",
3964
+ "suggestion.current_builtin": "(当前 · 内置 · 只读)",
3965
+ "prompt.save_as_builtin_error": "无法覆盖内置主题,请选择其他名称: "
3940
3966
  },
3941
3967
  "it": {
3942
3968
  "cmd.edit_theme": "Modifica tema",
@@ -4239,6 +4265,8 @@
4239
4265
  "field.punctuation_bracket": "Parentesi",
4240
4266
  "field.punctuation_bracket_desc": "Parentesi e parentesi quadre ({, }, (, ), [, ])",
4241
4267
  "field.punctuation_delimiter": "Delimitatore",
4242
- "field.punctuation_delimiter_desc": "Delimitatori e separatori (;, ,, .)"
4268
+ "field.punctuation_delimiter_desc": "Delimitatori e separatori (;, ,, .)",
4269
+ "suggestion.current_builtin": "(corrente · integrato · sola lettura)",
4270
+ "prompt.save_as_builtin_error": "Impossibile sovrascrivere il tema integrato, scegli un altro nome: "
4243
4271
  }
4244
4272
  }
@@ -355,6 +355,8 @@ interface ThemeEditorState {
355
355
  savedCursorPath: string | null;
356
356
  /** Whether to close the editor after a successful save */
357
357
  closeAfterSave: boolean;
358
+ /** Whether the Save As prompt has been pre-filled (to distinguish first vs second Enter) */
359
+ saveAsPreFilled: boolean;
358
360
  /** Which panel has focus */
359
361
  focusPanel: "tree" | "picker";
360
362
  /** Focus target within picker panel */
@@ -420,6 +422,7 @@ const state: ThemeEditorState = {
420
422
  isBuiltin: false,
421
423
  savedCursorPath: null,
422
424
  closeAfterSave: false,
425
+ saveAsPreFilled: false,
423
426
  focusPanel: "tree",
424
427
  pickerFocus: { type: "hex-input" },
425
428
  filterText: "",
@@ -1604,10 +1607,31 @@ async function onThemeSaveAsPromptConfirmed(args: {
1604
1607
 
1605
1608
  const name = args.input.trim();
1606
1609
  if (name) {
1610
+ // If user accepted a suggestion without typing, pre-fill the prompt so they can edit the name
1611
+ if (args.selected_index !== null && !state.saveAsPreFilled) {
1612
+ state.saveAsPreFilled = true;
1613
+ editor.startPromptWithInitial(editor.t("prompt.save_as"), "theme-save-as", name);
1614
+ editor.setPromptSuggestions([{
1615
+ text: state.themeName,
1616
+ description: state.isBuiltin
1617
+ ? editor.t("suggestion.current_builtin")
1618
+ : editor.t("suggestion.current"),
1619
+ value: state.themeName,
1620
+ }]);
1621
+ return true;
1622
+ }
1623
+ state.saveAsPreFilled = false;
1624
+
1607
1625
  // Reject names that match a built-in theme
1608
1626
  if (state.builtinThemes.includes(name)) {
1609
- editor.setStatus(editor.t("error.name_is_builtin", { name }));
1610
- theme_editor_save_as();
1627
+ editor.startPromptWithInitial(editor.t("prompt.save_as_builtin_error"), "theme-save-as", name);
1628
+ editor.setPromptSuggestions([{
1629
+ text: state.themeName,
1630
+ description: state.isBuiltin
1631
+ ? editor.t("suggestion.current_builtin")
1632
+ : editor.t("suggestion.current"),
1633
+ value: state.themeName,
1634
+ }]);
1611
1635
  return true;
1612
1636
  }
1613
1637
 
@@ -2757,11 +2781,14 @@ function theme_editor_save_as() : void {
2757
2781
  state.savedCursorPath = getCurrentFieldPath();
2758
2782
  }
2759
2783
 
2784
+ state.saveAsPreFilled = false;
2760
2785
  editor.startPrompt(editor.t("prompt.save_as"), "theme-save-as");
2761
2786
 
2762
2787
  editor.setPromptSuggestions([{
2763
2788
  text: state.themeName,
2764
- description: editor.t("suggestion.current"),
2789
+ description: state.isBuiltin
2790
+ ? editor.t("suggestion.current_builtin")
2791
+ : editor.t("suggestion.current"),
2765
2792
  value: state.themeName,
2766
2793
  }]);
2767
2794
  }
@@ -64,6 +64,14 @@ const state: ViState = {
64
64
  visualBlockAnchor: null,
65
65
  };
66
66
 
67
+ // Safe getBufferText that clamps end to buffer length
68
+ async function safeGetBufferText(bufferId: number, start: number, end: number): Promise<string | null> {
69
+ const bufLen = editor.getBufferLength(bufferId);
70
+ const clampedEnd = Math.min(end, bufLen);
71
+ if (clampedEnd <= start) return null;
72
+ return editor.getBufferText(bufferId, start, clampedEnd);
73
+ }
74
+
67
75
  // Mode indicator for status bar
68
76
  function getModeIndicator(mode: ViMode): string {
69
77
  const countPrefix = state.count !== null ? `${state.count} ` : "";
@@ -179,7 +187,11 @@ function getCount(): number {
179
187
  // Returns the count (defaults to 1)
180
188
  function consumeCount(): number {
181
189
  const count = state.count ?? 1;
182
- state.count = null;
190
+ if (state.count !== null) {
191
+ state.count = null;
192
+ // Update status to clear the count display
193
+ editor.setStatus(getModeIndicator(state.mode));
194
+ }
183
195
  return count;
184
196
  }
185
197
 
@@ -212,6 +224,7 @@ const motionToSelection: Record<string, string> = {
212
224
  move_down: "select_down",
213
225
  move_word_left: "select_word_left",
214
226
  move_word_right: "select_word_right",
227
+ vi_move_word_end: "vi_select_word_end",
215
228
  move_line_start: "select_line_start",
216
229
  move_line_end: "select_line_end",
217
230
  move_document_start: "select_document_start",
@@ -227,6 +240,7 @@ const atomicOperatorActions: OperatorMotionMap = {
227
240
  // Delete operators
228
241
  move_word_right: "delete_word_forward",
229
242
  move_word_left: "delete_word_backward",
243
+ vi_move_word_end: "delete_vi_word_end",
230
244
  move_line_end: "delete_to_line_end",
231
245
  move_line_start: "delete_to_line_start",
232
246
  },
@@ -234,6 +248,7 @@ const atomicOperatorActions: OperatorMotionMap = {
234
248
  // Yank operators
235
249
  move_word_right: "yank_word_forward",
236
250
  move_word_left: "yank_word_backward",
251
+ vi_move_word_end: "yank_vi_word_end",
237
252
  move_line_end: "yank_to_line_end",
238
253
  move_line_start: "yank_to_line_start",
239
254
  },
@@ -325,7 +340,8 @@ function handleMotionWithOperator(motionAction: string): void {
325
340
 
326
341
  // Navigation (all support count prefix, e.g., 5j moves down 5 lines)
327
342
  function vi_left() : void {
328
- executeWithCount("move_left");
343
+ // h — line-bounded move left (vim doesn't wrap across lines)
344
+ executeWithCount("move_left_in_line");
329
345
  }
330
346
  registerHandler("vi_left", vi_left);
331
347
 
@@ -340,7 +356,8 @@ function vi_up() : void {
340
356
  registerHandler("vi_up", vi_up);
341
357
 
342
358
  function vi_right() : void {
343
- executeWithCount("move_right");
359
+ // l — line-bounded move right (vim doesn't wrap across lines)
360
+ executeWithCount("move_right_in_line");
344
361
  }
345
362
  registerHandler("vi_right", vi_right);
346
363
 
@@ -355,12 +372,8 @@ function vi_word_back() : void {
355
372
  registerHandler("vi_word_back", vi_word_back);
356
373
 
357
374
  function vi_word_end() : void {
358
- // Move to end of word - for count, repeat the whole operation
359
- const count = consumeCount();
360
- for (let i = 0; i < count; i++) {
361
- editor.executeAction("move_word_right");
362
- editor.executeAction("move_left");
363
- }
375
+ // Vim 'e' motion uses native vi_move_word_end action
376
+ executeWithCount("vi_move_word_end");
364
377
  }
365
378
  registerHandler("vi_word_end", vi_word_end);
366
379
 
@@ -373,13 +386,36 @@ registerHandler("vi_line_start", vi_line_start);
373
386
  function vi_line_end() : void {
374
387
  consumeCount(); // Count doesn't apply to line end
375
388
  editor.executeAction("move_line_end");
389
+ // In vim normal mode, cursor should be ON the last char, not past it
390
+ // move_line_end goes past the last char; move_left corrects this
391
+ editor.executeAction("move_left");
376
392
  }
377
393
  registerHandler("vi_line_end", vi_line_end);
378
394
 
379
- function vi_first_non_blank() : void {
395
+ async function vi_first_non_blank() : Promise<void> {
380
396
  consumeCount(); // Count doesn't apply
381
- editor.executeAction("move_line_start");
382
- // TODO: skip whitespace
397
+ // Get line start position directly (avoids stale snapshot from executeAction)
398
+ const line = editor.getCursorLine();
399
+ const bufferId = editor.getActiveBufferId();
400
+ const lineStart = await editor.getLineStartPosition(line);
401
+ if (lineStart === null) {
402
+ editor.executeAction("move_line_start");
403
+ return;
404
+ }
405
+ const text = await safeGetBufferText(bufferId, lineStart, lineStart + 200);
406
+ if (text) {
407
+ let offset = 0;
408
+ while (offset < text.length && (text[offset] === ' ' || text[offset] === '\t')) {
409
+ offset++;
410
+ }
411
+ if (offset < text.length && text[offset] !== '\n' && text[offset] !== '\r') {
412
+ editor.setBufferCursor(bufferId, lineStart + offset);
413
+ } else {
414
+ editor.setBufferCursor(bufferId, lineStart);
415
+ }
416
+ } else {
417
+ editor.executeAction("move_line_start");
418
+ }
383
419
  }
384
420
  registerHandler("vi_first_non_blank", vi_first_non_blank);
385
421
 
@@ -390,8 +426,22 @@ function vi_doc_start() : void {
390
426
  registerHandler("vi_doc_start", vi_doc_start);
391
427
 
392
428
  function vi_doc_end() : void {
393
- consumeCount(); // Count doesn't apply
394
- editor.executeAction("move_document_end");
429
+ const count = state.count;
430
+ consumeCount();
431
+ if (count !== null) {
432
+ // nG = go to line n (1-indexed; goto_line expects 0-indexed internally)
433
+ // Use setBufferCursor to move to line start via getLineStartPosition
434
+ const line = count - 1; // Convert to 0-indexed
435
+ editor.getLineStartPosition(line).then((pos) => {
436
+ if (pos !== null) {
437
+ editor.setBufferCursor(editor.getActiveBufferId(), pos);
438
+ }
439
+ });
440
+ } else {
441
+ editor.executeAction("move_document_end");
442
+ }
443
+ // Update status to clear any count display
444
+ editor.setStatus(getModeIndicator(state.mode));
395
445
  }
396
446
  registerHandler("vi_doc_end", vi_doc_end);
397
447
 
@@ -489,13 +539,10 @@ registerHandler("vi_delete_line", vi_delete_line);
489
539
  function vi_change_line() : void {
490
540
  const count = consumeCount();
491
541
  state.lastChange = { type: "line-op", action: "change_line", count };
542
+ // Select from line start to line end, then cut (avoids stale snapshot issue)
492
543
  editor.executeAction("move_line_start");
493
- const start = editor.getCursorPosition();
494
- editor.executeAction("move_line_end");
495
- const end = editor.getCursorPosition();
496
- if (start !== null && end !== null) {
497
- editor.deleteRange(editor.getActiveBufferId(), start, end);
498
- }
544
+ editor.executeAction("select_line_end");
545
+ editor.executeAction("cut");
499
546
  switchMode("insert");
500
547
  }
501
548
  registerHandler("vi_change_line", vi_change_line);
@@ -535,11 +582,27 @@ function vi_delete_char_before() : void {
535
582
  registerHandler("vi_delete_char_before", vi_delete_char_before);
536
583
 
537
584
  function vi_replace_char() : void {
538
- // TODO: implement character replacement (need to read next char)
539
- editor.setStatus(editor.t("status.replace_not_implemented"));
585
+ // Enter replace-char mode to read the replacement character
586
+ state.mode = "find-char"; // Reuse find-char mode mechanism
587
+ editor.setEditorMode("vi-replace-char");
588
+ editor.setStatus("-- REPLACE CHAR --");
540
589
  }
541
590
  registerHandler("vi_replace_char", vi_replace_char);
542
591
 
592
+ // Handler for replacement character input
593
+ async function vi_replace_char_handler(char: string): Promise<void> {
594
+ const count = consumeCount();
595
+ // Replace character(s) under cursor without moving
596
+ for (let i = 0; i < count; i++) {
597
+ editor.executeAction("delete_forward");
598
+ editor.insertAtCursor(char);
599
+ }
600
+ // Move cursor back to stay on the replaced char (vim behavior)
601
+ editor.executeAction("move_left");
602
+ switchMode("normal");
603
+ }
604
+ registerHandler("vi_replace_char_handler", vi_replace_char_handler);
605
+
543
606
  // Substitute (delete char and enter insert mode)
544
607
  function vi_substitute() : void {
545
608
  const count = consumeCount();
@@ -553,27 +616,19 @@ function vi_substitute() : void {
553
616
  }
554
617
  registerHandler("vi_substitute", vi_substitute);
555
618
 
556
- // Delete to end of line
619
+ // Delete to end of line (D)
557
620
  function vi_delete_to_end() : void {
558
621
  state.lastChange = { type: "operator-motion", operator: "d", motion: "move_line_end" };
559
- const start = editor.getCursorPosition();
560
- editor.executeAction("move_line_end");
561
- const end = editor.getCursorPosition();
562
- if (start !== null && end !== null && end > start) {
563
- editor.deleteRange(editor.getActiveBufferId(), start, end);
564
- }
622
+ // Use the atomic delete_to_line_end action to avoid stale snapshot issues
623
+ editor.executeAction("delete_to_line_end");
565
624
  }
566
625
  registerHandler("vi_delete_to_end", vi_delete_to_end);
567
626
 
568
- // Change to end of line
627
+ // Change to end of line (C)
569
628
  function vi_change_to_end() : void {
570
629
  state.lastChange = { type: "operator-motion", operator: "c", motion: "move_line_end" };
571
- const start = editor.getCursorPosition();
572
- editor.executeAction("move_line_end");
573
- const end = editor.getCursorPosition();
574
- if (start !== null && end !== null && end > start) {
575
- editor.deleteRange(editor.getActiveBufferId(), start, end);
576
- }
630
+ // Use the atomic delete_to_line_end action to avoid stale snapshot issues
631
+ editor.executeAction("delete_to_line_end");
577
632
  switchMode("insert");
578
633
  }
579
634
  registerHandler("vi_change_to_end", vi_change_to_end);
@@ -665,14 +720,10 @@ async function vi_repeat() : Promise<void> {
665
720
  editor.executeAction("delete_line");
666
721
  }
667
722
  } else if (change.action === "change_line") {
668
- // Change line: delete line content and insert text
723
+ // Change line: select line content, cut, insert text
669
724
  editor.executeAction("move_line_start");
670
- const start = editor.getCursorPosition();
671
- editor.executeAction("move_line_end");
672
- const end = editor.getCursorPosition();
673
- if (start !== null && end !== null) {
674
- editor.deleteRange(editor.getActiveBufferId(), start, end);
675
- }
725
+ editor.executeAction("select_line_end");
726
+ editor.executeAction("cut");
676
727
  if (change.insertedText) {
677
728
  editor.insertAtCursor(change.insertedText);
678
729
  }
@@ -721,14 +772,22 @@ async function vi_repeat() : Promise<void> {
721
772
  }
722
773
  registerHandler("vi_repeat", vi_repeat);
723
774
 
724
- // Join lines
775
+ // Join lines — delete newline at end of current line and insert a space
725
776
  function vi_join() : void {
726
777
  editor.executeAction("move_line_end");
778
+ // Delete the newline character
727
779
  editor.executeAction("delete_forward");
728
- editor.executeAction("insert_text_at_cursor");
780
+ // Insert a space between the joined content
781
+ editor.insertAtCursor(" ");
729
782
  }
730
783
  registerHandler("vi_join", vi_join);
731
784
 
785
+ // Toggle case (~) — uses native toggle_case action
786
+ function vi_toggle_case() : void {
787
+ executeWithCount("toggle_case");
788
+ }
789
+ registerHandler("vi_toggle_case", vi_toggle_case);
790
+
732
791
  // Search
733
792
  function vi_search_forward() : void {
734
793
  editor.executeAction("search");
@@ -822,7 +881,9 @@ registerHandler("vi_op_digit_0_or_line_start", vi_op_digit_0_or_line_start);
822
881
  // Enter character-wise visual mode
823
882
  function vi_visual_char() : void {
824
883
  state.visualAnchor = editor.getCursorPosition();
825
- // Select current character to start visual selection
884
+ // Select the character under cursor to establish the anchor.
885
+ // This moves cursor one position right (the selection end), which is
886
+ // standard visual mode behavior — the first char is part of the selection.
826
887
  editor.executeAction("select_right");
827
888
  switchMode("visual");
828
889
  }
@@ -831,8 +892,7 @@ registerHandler("vi_visual_char", vi_visual_char);
831
892
  // Enter line-wise visual mode
832
893
  function vi_visual_line() : void {
833
894
  state.visualAnchor = editor.getCursorPosition();
834
- // Select current line
835
- editor.executeAction("move_line_start");
895
+ // Select full line including newline (select_line selects and moves to next line)
836
896
  editor.executeAction("select_line");
837
897
  switchMode("visual-line");
838
898
  }
@@ -989,6 +1049,7 @@ function vi_vis_word_back() : void {
989
1049
  registerHandler("vi_vis_word_back", vi_vis_word_back);
990
1050
 
991
1051
  function vi_vis_word_end() : void {
1052
+ // Extend selection to end of word
992
1053
  const count = consumeCount();
993
1054
  for (let i = 0; i < count; i++) {
994
1055
  editor.executeAction("select_word_right");
@@ -1557,6 +1618,13 @@ function vi_op_word_back(): void {
1557
1618
  }
1558
1619
  registerHandler("vi_op_word_back", vi_op_word_back);
1559
1620
 
1621
+ // Operator-pending e (word end) - select to word end, then apply operator
1622
+ // Operator-pending e (word end) — uses native vi_move_word_end motion
1623
+ function vi_op_word_end(): void {
1624
+ handleMotionWithOperator("vi_move_word_end");
1625
+ }
1626
+ registerHandler("vi_op_word_end", vi_op_word_end);
1627
+
1560
1628
  function vi_op_line_start(): void {
1561
1629
  handleMotionWithOperator("move_line_start");
1562
1630
  }
@@ -1680,6 +1748,7 @@ editor.defineMode("vi-normal", [
1680
1748
 
1681
1749
  // Other
1682
1750
  ["J", "vi_join"],
1751
+ ["~", "vi_toggle_case"],
1683
1752
 
1684
1753
  // Command mode
1685
1754
  [":", "vi_command_mode"],
@@ -1829,6 +1898,24 @@ registerHandler("vi_fc_9", vi_fc_9);
1829
1898
  async function vi_fc_space(): Promise<void> { return vi_find_char_handler(" "); }
1830
1899
  registerHandler("vi_fc_space", vi_fc_space);
1831
1900
 
1901
+ // Punctuation character handlers for find-char mode
1902
+ const punctuationChars: [string, string][] = [
1903
+ ["!", "excl"], ["@", "at"], ["#", "hash"], ["$", "dollar"], ["%", "percent"],
1904
+ ["^", "caret"], ["&", "amp"], ["*", "star"], ["(", "lparen"], [")", "rparen"],
1905
+ ["-", "minus"], ["_", "underscore"], ["=", "equals"], ["+", "plus"],
1906
+ ["[", "lbracket"], ["]", "rbracket"], ["{", "lbrace"], ["}", "rbrace"],
1907
+ ["\\", "backslash"], ["|", "pipe"], [";", "semi"], [":", "colon"],
1908
+ ["'", "squote"], ["\"", "dquote"], [",", "comma"], [".", "dot"],
1909
+ ["<", "lt"], [">", "gt"], ["/", "slash"], ["?", "question"], ["`", "backtick"],
1910
+ ["~", "tilde"],
1911
+ ];
1912
+
1913
+ for (const [char, name] of punctuationChars) {
1914
+ const handlerName = `vi_fc_p_${name}`;
1915
+ const handler = async (): Promise<void> => { return vi_find_char_handler(char); };
1916
+ registerHandler(handlerName, handler);
1917
+ }
1918
+
1832
1919
  // Define vi-find-char mode with all the character bindings
1833
1920
  editor.defineMode("vi-find-char", [
1834
1921
  ["Escape", "vi_find_char_cancel"],
@@ -1851,8 +1938,56 @@ editor.defineMode("vi-find-char", [
1851
1938
  ["0", "vi_fc_0"], ["1", "vi_fc_1"], ["2", "vi_fc_2"], ["3", "vi_fc_3"],
1852
1939
  ["4", "vi_fc_4"], ["5", "vi_fc_5"], ["6", "vi_fc_6"], ["7", "vi_fc_7"],
1853
1940
  ["8", "vi_fc_8"], ["9", "vi_fc_9"],
1854
- // Common punctuation
1941
+ // Space and punctuation
1855
1942
  ["Space", "vi_fc_space"],
1943
+ ["!", "vi_fc_p_excl"], ["@", "vi_fc_p_at"], ["#", "vi_fc_p_hash"],
1944
+ ["$", "vi_fc_p_dollar"], ["%", "vi_fc_p_percent"], ["^", "vi_fc_p_caret"],
1945
+ ["&", "vi_fc_p_amp"], ["*", "vi_fc_p_star"], ["(", "vi_fc_p_lparen"],
1946
+ [")", "vi_fc_p_rparen"], ["-", "vi_fc_p_minus"], ["_", "vi_fc_p_underscore"],
1947
+ ["=", "vi_fc_p_equals"], ["+", "vi_fc_p_plus"], ["[", "vi_fc_p_lbracket"],
1948
+ ["]", "vi_fc_p_rbracket"], ["{", "vi_fc_p_lbrace"], ["}", "vi_fc_p_rbrace"],
1949
+ ["\\", "vi_fc_p_backslash"], ["|", "vi_fc_p_pipe"], [";", "vi_fc_p_semi"],
1950
+ [":", "vi_fc_p_colon"], ["'", "vi_fc_p_squote"], ["\"", "vi_fc_p_dquote"],
1951
+ [",", "vi_fc_p_comma"], [".", "vi_fc_p_dot"], ["<", "vi_fc_p_lt"],
1952
+ [">", "vi_fc_p_gt"], ["/", "vi_fc_p_slash"], ["?", "vi_fc_p_question"],
1953
+ ["`", "vi_fc_p_backtick"], ["~", "vi_fc_p_tilde"],
1954
+ ], true);
1955
+
1956
+ // Define vi-replace-char mode — reuses find-char handlers but calls vi_replace_char_handler
1957
+ // We create replace-char specific handlers that delegate to the replace handler
1958
+ const rcHandlers: [string, string][] = [];
1959
+ for (const c of "abcdefghijklmnopqrstuvwxyz") {
1960
+ const name = `vi_rc_${c}`;
1961
+ const handler = async (): Promise<void> => { return vi_replace_char_handler(c); };
1962
+ registerHandler(name, handler);
1963
+ rcHandlers.push([c, name]);
1964
+ }
1965
+ for (const c of "ABCDEFGHIJKLMNOPQRSTUVWXYZ") {
1966
+ const name = `vi_rc_${c}`;
1967
+ const handler = async (): Promise<void> => { return vi_replace_char_handler(c); };
1968
+ registerHandler(name, handler);
1969
+ rcHandlers.push([c, name]);
1970
+ }
1971
+ for (const c of "0123456789") {
1972
+ const name = `vi_rc_d${c}`;
1973
+ const handler = async (): Promise<void> => { return vi_replace_char_handler(c); };
1974
+ registerHandler(name, handler);
1975
+ rcHandlers.push([c, name]);
1976
+ }
1977
+ // Space and common punctuation for replace-char mode
1978
+ const rcSpaceHandler = async (): Promise<void> => { return vi_replace_char_handler(" "); };
1979
+ registerHandler("vi_rc_space", rcSpaceHandler);
1980
+ for (const [char, pname] of punctuationChars) {
1981
+ const name = `vi_rc_p_${pname}`;
1982
+ const handler = async (): Promise<void> => { return vi_replace_char_handler(char); };
1983
+ registerHandler(name, handler);
1984
+ }
1985
+
1986
+ editor.defineMode("vi-replace-char", [
1987
+ ["Escape", "vi_find_char_cancel"],
1988
+ ...rcHandlers.map(([key, name]): [string, string] => [key, name]),
1989
+ ["Space", "vi_rc_space"],
1990
+ ...punctuationChars.map(([char, pname]): [string, string] => [char, `vi_rc_p_${pname}`]),
1856
1991
  ], true);
1857
1992
 
1858
1993
  // Define vi-operator-pending mode
@@ -1876,6 +2011,7 @@ editor.defineMode("vi-operator-pending", [
1876
2011
  ["l", "vi_op_right"],
1877
2012
  ["w", "vi_op_word"],
1878
2013
  ["b", "vi_op_word_back"],
2014
+ ["e", "vi_op_word_end"],
1879
2015
  ["$", "vi_op_line_end"],
1880
2016
  ["g g", "vi_op_doc_start"],
1881
2017
  ["G", "vi_op_doc_end"],
@@ -1948,8 +2084,9 @@ editor.defineMode("vi-visual", [
1948
2084
  ["g g", "vi_vis_doc_start"],
1949
2085
  ["G", "vi_vis_doc_end"],
1950
2086
 
1951
- // Switch to line mode
2087
+ // Switch visual sub-modes
1952
2088
  ["V", "vi_visual_toggle_line"],
2089
+ ["C-v", "vi_visual_block"], // Switch to block mode
1953
2090
 
1954
2091
  // Operators
1955
2092
  ["d", "vi_vis_delete"],
@@ -1986,8 +2123,9 @@ editor.defineMode("vi-visual-line", [
1986
2123
  ["g g", "vi_vis_doc_start"],
1987
2124
  ["G", "vi_vis_doc_end"],
1988
2125
 
1989
- // Switch to char mode
2126
+ // Switch visual sub-modes
1990
2127
  ["v", "vi_visual_toggle_line"],
2128
+ ["C-v", "vi_visual_block"], // Switch to block mode
1991
2129
 
1992
2130
  // Operators
1993
2131
  ["d", "vi_vis_delete"],
@@ -1,103 +1,103 @@
1
1
  {
2
2
  "name": "high-contrast",
3
3
  "editor": {
4
- "bg": "Black",
5
- "fg": "White",
6
- "cursor": "White",
7
- "inactive_cursor": "DarkGray",
4
+ "bg": [0, 0, 0],
5
+ "fg": [255, 255, 255],
6
+ "cursor": [255, 255, 255],
7
+ "inactive_cursor": [127, 127, 127],
8
8
  "selection_bg": [0, 100, 200],
9
9
  "current_line_bg": [20, 20, 20],
10
10
  "line_number_fg": [140, 140, 140],
11
- "line_number_bg": "Black",
12
- "diff_add_bg": [0, 80, 0],
13
- "diff_remove_bg": [100, 0, 0],
11
+ "line_number_bg": [0, 0, 0],
12
+ "diff_add_bg": [0, 35, 0],
13
+ "diff_remove_bg": [50, 0, 0],
14
14
  "diff_modify_bg": [25, 22, 0],
15
15
  "whitespace_indicator_fg": [80, 80, 80]
16
16
  },
17
17
  "ui": {
18
- "tab_active_fg": "Black",
19
- "tab_active_bg": "Yellow",
20
- "tab_inactive_fg": "White",
21
- "tab_inactive_bg": "Black",
18
+ "tab_active_fg": [0, 0, 0],
19
+ "tab_active_bg": [255, 255, 0],
20
+ "tab_inactive_fg": [255, 255, 255],
21
+ "tab_inactive_bg": [0, 0, 0],
22
22
  "tab_separator_bg": [30, 30, 35],
23
23
  "tab_close_hover_fg": [249, 38, 114],
24
24
  "tab_hover_bg": [50, 50, 55],
25
25
  "menu_bg": [50, 50, 55],
26
- "menu_fg": "White",
27
- "menu_active_bg": "Yellow",
28
- "menu_active_fg": "Black",
26
+ "menu_fg": [255, 255, 255],
27
+ "menu_active_bg": [255, 255, 0],
28
+ "menu_active_fg": [0, 0, 0],
29
29
  "menu_dropdown_bg": [20, 20, 20],
30
- "menu_dropdown_fg": "White",
30
+ "menu_dropdown_fg": [255, 255, 255],
31
31
  "menu_highlight_bg": [0, 100, 200],
32
- "menu_highlight_fg": "White",
33
- "menu_border_fg": "Yellow",
34
- "menu_separator_fg": "White",
32
+ "menu_highlight_fg": [255, 255, 255],
33
+ "menu_border_fg": [255, 255, 0],
34
+ "menu_separator_fg": [255, 255, 255],
35
35
  "menu_hover_bg": [50, 50, 50],
36
- "menu_hover_fg": "Yellow",
37
- "menu_disabled_fg": "DarkGray",
36
+ "menu_hover_fg": [255, 255, 0],
37
+ "menu_disabled_fg": [127, 127, 127],
38
38
  "menu_disabled_bg": [20, 20, 20],
39
- "status_bar_fg": "White",
39
+ "status_bar_fg": [255, 255, 255],
40
40
  "status_bar_bg": [20, 20, 20],
41
- "prompt_fg": "White",
41
+ "prompt_fg": [255, 255, 255],
42
42
  "prompt_bg": [10, 10, 10],
43
- "prompt_selection_fg": "White",
43
+ "prompt_selection_fg": [255, 255, 255],
44
44
  "prompt_selection_bg": [0, 100, 200],
45
- "popup_border_fg": "LightCyan",
46
- "popup_bg": "Black",
45
+ "popup_border_fg": [0, 255, 255],
46
+ "popup_bg": [0, 0, 0],
47
47
  "popup_selection_bg": [0, 100, 200],
48
- "popup_text_fg": "White",
49
- "suggestion_bg": "Black",
48
+ "popup_text_fg": [255, 255, 255],
49
+ "suggestion_bg": [0, 0, 0],
50
50
  "suggestion_selected_bg": [0, 100, 200],
51
- "help_bg": "Black",
52
- "help_fg": "White",
53
- "help_key_fg": "LightCyan",
54
- "help_separator_fg": "White",
55
- "help_indicator_fg": "Red",
56
- "help_indicator_bg": "Black",
51
+ "help_bg": [0, 0, 0],
52
+ "help_fg": [255, 255, 255],
53
+ "help_key_fg": [0, 255, 255],
54
+ "help_separator_fg": [255, 255, 255],
55
+ "help_indicator_fg": [255, 80, 80],
56
+ "help_indicator_bg": [0, 0, 0],
57
57
  "inline_code_bg": [40, 40, 40],
58
58
  "split_separator_fg": [140, 140, 140],
59
- "split_separator_hover_fg": "Yellow",
60
- "scrollbar_track_fg": "White",
61
- "scrollbar_thumb_fg": "Yellow",
62
- "scrollbar_track_hover_fg": "Yellow",
63
- "scrollbar_thumb_hover_fg": "Cyan",
59
+ "split_separator_hover_fg": [255, 255, 0],
60
+ "scrollbar_track_fg": [255, 255, 255],
61
+ "scrollbar_thumb_fg": [255, 255, 0],
62
+ "scrollbar_track_hover_fg": [255, 255, 0],
63
+ "scrollbar_thumb_hover_fg": [0, 255, 255],
64
64
  "compose_margin_bg": [10, 10, 10],
65
- "semantic_highlight_bg": [0, 60, 100],
65
+ "semantic_highlight_bg": [0, 25, 55],
66
66
  "terminal_bg": "Default",
67
67
  "terminal_fg": "Default",
68
- "status_warning_indicator_bg": "Yellow",
69
- "status_warning_indicator_fg": "Black",
70
- "status_error_indicator_bg": "Red",
71
- "status_error_indicator_fg": "White",
72
- "status_warning_indicator_hover_bg": "LightYellow",
73
- "status_warning_indicator_hover_fg": "Black",
74
- "status_error_indicator_hover_bg": "LightRed",
75
- "status_error_indicator_hover_fg": "White",
68
+ "status_warning_indicator_bg": [255, 255, 0],
69
+ "status_warning_indicator_fg": [0, 0, 0],
70
+ "status_error_indicator_bg": [255, 60, 60],
71
+ "status_error_indicator_fg": [255, 255, 255],
72
+ "status_warning_indicator_hover_bg": [255, 255, 100],
73
+ "status_warning_indicator_hover_fg": [0, 0, 0],
74
+ "status_error_indicator_hover_bg": [255, 100, 100],
75
+ "status_error_indicator_hover_fg": [255, 255, 255],
76
76
  "tab_drop_zone_bg": [0, 100, 200],
77
- "tab_drop_zone_border": "Yellow"
77
+ "tab_drop_zone_border": [255, 255, 0]
78
78
  },
79
79
  "search": {
80
- "match_bg": "Yellow",
81
- "match_fg": "Black"
80
+ "match_bg": [255, 255, 0],
81
+ "match_fg": [0, 0, 0]
82
82
  },
83
83
  "diagnostic": {
84
- "error_fg": "Red",
85
- "error_bg": [100, 0, 0],
86
- "warning_fg": "Yellow",
87
- "warning_bg": [100, 100, 0],
88
- "info_fg": "Cyan",
89
- "info_bg": [0, 50, 100],
90
- "hint_fg": "White",
91
- "hint_bg": [50, 50, 50]
84
+ "error_fg": [255, 80, 80],
85
+ "error_bg": [50, 0, 0],
86
+ "warning_fg": [255, 255, 0],
87
+ "warning_bg": [35, 30, 0],
88
+ "info_fg": [0, 255, 255],
89
+ "info_bg": [0, 25, 55],
90
+ "hint_fg": [255, 255, 255],
91
+ "hint_bg": [30, 30, 30]
92
92
  },
93
93
  "syntax": {
94
- "keyword": "Cyan",
95
- "string": "Green",
96
- "comment": "Gray",
97
- "function": "Yellow",
98
- "type": "Magenta",
99
- "variable": "White",
100
- "constant": "LightBlue",
101
- "operator": "White"
94
+ "keyword": [0, 255, 255],
95
+ "string": [0, 205, 0],
96
+ "comment": [229, 229, 229],
97
+ "function": [255, 255, 0],
98
+ "type": [255, 85, 255],
99
+ "variable": [255, 255, 255],
100
+ "constant": [120, 130, 255],
101
+ "operator": [255, 255, 255]
102
102
  }
103
103
  }
package/themes/light.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "inactive_cursor": [180, 180, 180],
8
8
  "selection_bg": [173, 214, 255],
9
9
  "current_line_bg": [245, 245, 245],
10
- "line_number_fg": [140, 140, 140],
10
+ "line_number_fg": [115, 115, 115],
11
11
  "line_number_bg": [255, 255, 255],
12
12
  "diff_add_bg": [200, 255, 200],
13
13
  "diff_remove_bg": [255, 200, 200],
@@ -36,11 +36,11 @@
36
36
  "menu_hover_fg": [0, 0, 0],
37
37
  "menu_disabled_fg": [160, 160, 160],
38
38
  "menu_disabled_bg": [248, 248, 248],
39
- "status_bar_fg": "Black",
39
+ "status_bar_fg": [0, 0, 0],
40
40
  "status_bar_bg": [220, 220, 220],
41
- "prompt_fg": "Black",
41
+ "prompt_fg": [0, 0, 0],
42
42
  "prompt_bg": [230, 240, 250],
43
- "prompt_selection_fg": "Black",
43
+ "prompt_selection_fg": [0, 0, 0],
44
44
  "prompt_selection_bg": [173, 214, 255],
45
45
  "popup_border_fg": [140, 140, 140],
46
46
  "popup_bg": [232, 238, 245],
@@ -48,12 +48,12 @@
48
48
  "popup_text_fg": [30, 30, 30],
49
49
  "suggestion_bg": [232, 238, 245],
50
50
  "suggestion_selected_bg": [209, 226, 243],
51
- "help_bg": "White",
52
- "help_fg": "Black",
53
- "help_key_fg": "Blue",
54
- "help_separator_fg": "Gray",
55
- "help_indicator_fg": "Red",
56
- "help_indicator_bg": "White",
51
+ "help_bg": [255, 255, 255],
52
+ "help_fg": [0, 0, 0],
53
+ "help_key_fg": [0, 0, 180],
54
+ "help_separator_fg": [170, 170, 170],
55
+ "help_indicator_fg": [200, 30, 30],
56
+ "help_indicator_bg": [255, 255, 255],
57
57
  "inline_code_bg": [230, 230, 235],
58
58
  "split_separator_fg": [140, 140, 140],
59
59
  "split_separator_hover_fg": [70, 130, 180],
@@ -81,14 +81,14 @@
81
81
  "match_fg": [0, 0, 0]
82
82
  },
83
83
  "diagnostic": {
84
- "error_fg": "Red",
85
- "error_bg": [255, 220, 220],
86
- "warning_fg": [128, 128, 0],
87
- "warning_bg": [255, 255, 200],
88
- "info_fg": "Blue",
89
- "info_bg": [220, 240, 255],
90
- "hint_fg": "DarkGray",
91
- "hint_bg": [240, 240, 240]
84
+ "error_fg": [180, 0, 0],
85
+ "error_bg": [255, 210, 210],
86
+ "warning_fg": [150, 110, 0],
87
+ "warning_bg": [255, 248, 170],
88
+ "info_fg": [0, 0, 180],
89
+ "info_bg": [210, 230, 255],
90
+ "hint_fg": [100, 100, 100],
91
+ "hint_bg": [235, 235, 240]
92
92
  },
93
93
  "syntax": {
94
94
  "keyword": [175, 0, 219],