@vybestack/llxprt-code 0.1.19-gamma → 0.1.20-nightly.250816.90f427f5

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.
Files changed (168) hide show
  1. package/README.md +9 -0
  2. package/dist/package.json +5 -5
  3. package/dist/src/auth/oauth-manager.d.ts +1 -0
  4. package/dist/src/auth/oauth-manager.js +24 -13
  5. package/dist/src/auth/oauth-manager.js.map +1 -1
  6. package/dist/src/auth/oauth-manager.spec.js +4 -4
  7. package/dist/src/auth/oauth-manager.spec.js.map +1 -1
  8. package/dist/src/commands/mcp/list.js +1 -1
  9. package/dist/src/commands/mcp/list.js.map +1 -1
  10. package/dist/src/config/config.d.ts +2 -1
  11. package/dist/src/config/config.js +183 -25
  12. package/dist/src/config/config.js.map +1 -1
  13. package/dist/src/config/keyBindings.js +4 -0
  14. package/dist/src/config/keyBindings.js.map +1 -1
  15. package/dist/src/config/settingsSchema.d.ts +18 -9
  16. package/dist/src/config/settingsSchema.js +18 -9
  17. package/dist/src/config/settingsSchema.js.map +1 -1
  18. package/dist/src/config/trustedFolders.d.ts +36 -0
  19. package/dist/src/config/trustedFolders.js +112 -0
  20. package/dist/src/config/trustedFolders.js.map +1 -0
  21. package/dist/src/gemini.js +28 -18
  22. package/dist/src/gemini.js.map +1 -1
  23. package/dist/src/generated/git-commit.d.ts +1 -1
  24. package/dist/src/generated/git-commit.js +1 -1
  25. package/dist/src/nonInteractiveCli.js +3 -4
  26. package/dist/src/nonInteractiveCli.js.map +1 -1
  27. package/dist/src/providers/logging/git-stats.js +0 -1
  28. package/dist/src/providers/logging/git-stats.js.map +1 -1
  29. package/dist/src/providers/providerConfigUtils.js +1 -1
  30. package/dist/src/providers/providerConfigUtils.js.map +1 -1
  31. package/dist/src/services/BuiltinCommandLoader.js +2 -0
  32. package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
  33. package/dist/src/services/todo-continuation/todoContinuationService.d.ts +0 -1
  34. package/dist/src/services/todo-continuation/todoContinuationService.js +2 -1
  35. package/dist/src/services/todo-continuation/todoContinuationService.js.map +1 -1
  36. package/dist/src/ui/App.js +50 -34
  37. package/dist/src/ui/App.js.map +1 -1
  38. package/dist/src/ui/IdeIntegrationNudge.d.ts +7 -4
  39. package/dist/src/ui/IdeIntegrationNudge.js +31 -10
  40. package/dist/src/ui/IdeIntegrationNudge.js.map +1 -1
  41. package/dist/src/ui/commands/authCommand.js +16 -8
  42. package/dist/src/ui/commands/authCommand.js.map +1 -1
  43. package/dist/src/ui/commands/directoryCommand.js +2 -4
  44. package/dist/src/ui/commands/directoryCommand.js.map +1 -1
  45. package/dist/src/ui/commands/ideCommand.js +11 -8
  46. package/dist/src/ui/commands/ideCommand.js.map +1 -1
  47. package/dist/src/ui/commands/keyCommand.js +1 -1
  48. package/dist/src/ui/commands/keyCommand.js.map +1 -1
  49. package/dist/src/ui/commands/keyfileCommand.js +1 -1
  50. package/dist/src/ui/commands/keyfileCommand.js.map +1 -1
  51. package/dist/src/ui/commands/mcpCommand.js +10 -6
  52. package/dist/src/ui/commands/mcpCommand.js.map +1 -1
  53. package/dist/src/ui/commands/setCommand.js +43 -3
  54. package/dist/src/ui/commands/setCommand.js.map +1 -1
  55. package/dist/src/ui/commands/setupGithubCommand.js +5 -16
  56. package/dist/src/ui/commands/setupGithubCommand.js.map +1 -1
  57. package/dist/src/ui/commands/terminalSetupCommand.d.ts +13 -0
  58. package/dist/src/ui/commands/terminalSetupCommand.js +41 -0
  59. package/dist/src/ui/commands/terminalSetupCommand.js.map +1 -0
  60. package/dist/src/ui/commands/types.d.ts +1 -0
  61. package/dist/src/ui/commands/types.js.map +1 -1
  62. package/dist/src/ui/components/AuthDialog.js +56 -29
  63. package/dist/src/ui/components/AuthDialog.js.map +1 -1
  64. package/dist/src/ui/components/AuthInProgress.js +5 -4
  65. package/dist/src/ui/components/AuthInProgress.js.map +1 -1
  66. package/dist/src/ui/components/DebugProfiler.js +5 -4
  67. package/dist/src/ui/components/DebugProfiler.js.map +1 -1
  68. package/dist/src/ui/components/DetailedMessagesDisplay.js +4 -4
  69. package/dist/src/ui/components/DetailedMessagesDisplay.js.map +1 -1
  70. package/dist/src/ui/components/EditorSettingsDialog.js +6 -5
  71. package/dist/src/ui/components/EditorSettingsDialog.js.map +1 -1
  72. package/dist/src/ui/components/ErrorBoundary.js +2 -2
  73. package/dist/src/ui/components/ErrorBoundary.js.map +1 -1
  74. package/dist/src/ui/components/FolderTrustDialog.js +5 -4
  75. package/dist/src/ui/components/FolderTrustDialog.js.map +1 -1
  76. package/dist/src/ui/components/InputPrompt.js +7 -1
  77. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  78. package/dist/src/ui/components/LoggingDialog.js +5 -1
  79. package/dist/src/ui/components/LoggingDialog.js.map +1 -1
  80. package/dist/src/ui/components/OAuthCodeDialog.js +12 -14
  81. package/dist/src/ui/components/OAuthCodeDialog.js.map +1 -1
  82. package/dist/src/ui/components/SettingsDialog.js +12 -10
  83. package/dist/src/ui/components/SettingsDialog.js.map +1 -1
  84. package/dist/src/ui/components/ShellConfirmationDialog.js +5 -4
  85. package/dist/src/ui/components/ShellConfirmationDialog.js.map +1 -1
  86. package/dist/src/ui/components/ThemeDialog.js +6 -5
  87. package/dist/src/ui/components/ThemeDialog.js.map +1 -1
  88. package/dist/src/ui/components/TodoPanel.js +2 -2
  89. package/dist/src/ui/components/TodoPanel.js.map +1 -1
  90. package/dist/src/ui/components/messages/InfoMessage.js +1 -1
  91. package/dist/src/ui/components/messages/InfoMessage.js.map +1 -1
  92. package/dist/src/ui/components/messages/ToolConfirmationMessage.js +8 -7
  93. package/dist/src/ui/components/messages/ToolConfirmationMessage.js.map +1 -1
  94. package/dist/src/ui/components/shared/RadioButtonSelect.js +11 -9
  95. package/dist/src/ui/components/shared/RadioButtonSelect.js.map +1 -1
  96. package/dist/src/ui/components/shared/text-buffer.d.ts +17 -4
  97. package/dist/src/ui/components/shared/text-buffer.js +256 -80
  98. package/dist/src/ui/components/shared/text-buffer.js.map +1 -1
  99. package/dist/src/ui/components/shared/vim-buffer-actions.js +139 -152
  100. package/dist/src/ui/components/shared/vim-buffer-actions.js.map +1 -1
  101. package/dist/src/ui/containers/SessionController.js +23 -23
  102. package/dist/src/ui/containers/SessionController.js.map +1 -1
  103. package/dist/src/ui/hooks/atCommandProcessor.js +1 -1
  104. package/dist/src/ui/hooks/atCommandProcessor.js.map +1 -1
  105. package/dist/src/ui/hooks/slashCommandProcessor.js +7 -1
  106. package/dist/src/ui/hooks/slashCommandProcessor.js.map +1 -1
  107. package/dist/src/ui/hooks/useAuthCommand.js +8 -60
  108. package/dist/src/ui/hooks/useAuthCommand.js.map +1 -1
  109. package/dist/src/ui/hooks/useAutoAcceptIndicator.js +5 -5
  110. package/dist/src/ui/hooks/useAutoAcceptIndicator.js.map +1 -1
  111. package/dist/src/ui/hooks/useFocus.d.ts +4 -0
  112. package/dist/src/ui/hooks/useFocus.js +4 -4
  113. package/dist/src/ui/hooks/useFocus.js.map +1 -1
  114. package/dist/src/ui/hooks/useFolderTrust.d.ts +3 -2
  115. package/dist/src/ui/hooks/useFolderTrust.js +24 -9
  116. package/dist/src/ui/hooks/useFolderTrust.js.map +1 -1
  117. package/dist/src/ui/hooks/useGeminiStream.d.ts +1 -0
  118. package/dist/src/ui/hooks/useGeminiStream.js +126 -40
  119. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  120. package/dist/src/ui/hooks/useKeypress.d.ts +9 -1
  121. package/dist/src/ui/hooks/useKeypress.js +191 -8
  122. package/dist/src/ui/hooks/useKeypress.js.map +1 -1
  123. package/dist/src/ui/hooks/useKittyKeyboardProtocol.d.ts +15 -0
  124. package/dist/src/ui/hooks/useKittyKeyboardProtocol.js +20 -0
  125. package/dist/src/ui/hooks/useKittyKeyboardProtocol.js.map +1 -0
  126. package/dist/src/ui/privacy/CloudFreePrivacyNotice.js +5 -4
  127. package/dist/src/ui/privacy/CloudFreePrivacyNotice.js.map +1 -1
  128. package/dist/src/ui/privacy/CloudPaidPrivacyNotice.js +5 -4
  129. package/dist/src/ui/privacy/CloudPaidPrivacyNotice.js.map +1 -1
  130. package/dist/src/ui/privacy/GeminiPrivacyNotice.js +5 -4
  131. package/dist/src/ui/privacy/GeminiPrivacyNotice.js.map +1 -1
  132. package/dist/src/ui/reducers/sessionReducer.js +1 -0
  133. package/dist/src/ui/reducers/sessionReducer.js.map +1 -1
  134. package/dist/src/ui/utils/kittyProtocolDetector.d.ts +13 -0
  135. package/dist/src/ui/utils/kittyProtocolDetector.js +88 -0
  136. package/dist/src/ui/utils/kittyProtocolDetector.js.map +1 -0
  137. package/dist/src/ui/utils/platformConstants.d.ts +38 -0
  138. package/dist/src/ui/utils/platformConstants.js +39 -0
  139. package/dist/src/ui/utils/platformConstants.js.map +1 -0
  140. package/dist/src/ui/utils/renderLoopDetector.js +3 -3
  141. package/dist/src/ui/utils/renderLoopDetector.js.map +1 -1
  142. package/dist/src/ui/utils/terminalSetup.d.ts +30 -0
  143. package/dist/src/ui/utils/terminalSetup.js +281 -0
  144. package/dist/src/ui/utils/terminalSetup.js.map +1 -0
  145. package/dist/src/utils/checks.d.ts +19 -0
  146. package/dist/src/utils/checks.js +24 -0
  147. package/dist/src/utils/checks.js.map +1 -0
  148. package/dist/src/utils/privacy/ConversationDataRedactor.d.ts +0 -2
  149. package/dist/src/utils/privacy/ConversationDataRedactor.js +4 -37
  150. package/dist/src/utils/privacy/ConversationDataRedactor.js.map +1 -1
  151. package/dist/src/zed-integration/acp.d.ts +63 -0
  152. package/dist/src/{acp → zed-integration}/acp.js +76 -44
  153. package/dist/src/zed-integration/acp.js.map +1 -0
  154. package/dist/src/zed-integration/schema.d.ts +11679 -0
  155. package/dist/src/zed-integration/schema.js +305 -0
  156. package/dist/src/zed-integration/schema.js.map +1 -0
  157. package/dist/src/zed-integration/zedIntegration.d.ts +10 -0
  158. package/dist/src/{acp/acpPeer.js → zed-integration/zedIntegration.js} +333 -188
  159. package/dist/src/zed-integration/zedIntegration.js.map +1 -0
  160. package/dist/tsconfig.tsbuildinfo +1 -1
  161. package/package.json +5 -5
  162. package/dist/src/acp/acp.d.ts +0 -208
  163. package/dist/src/acp/acp.js.map +0 -1
  164. package/dist/src/acp/acpPeer.d.ts +0 -8
  165. package/dist/src/acp/acpPeer.js.map +0 -1
  166. package/dist/src/ui/utils/errorParsing.d.ts +0 -7
  167. package/dist/src/ui/utils/errorParsing.js +0 -106
  168. package/dist/src/ui/utils/errorParsing.js.map +0 -1
@@ -4,6 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import stripAnsi from 'strip-ansi';
7
+ import { stripVTControlCharacters } from 'util';
7
8
  import { spawnSync } from 'child_process';
8
9
  import fs from 'fs';
9
10
  import os from 'os';
@@ -20,112 +21,266 @@ function isWordChar(ch) {
20
21
  }
21
22
  return !/[\s,.;!?]/.test(ch);
22
23
  }
23
- // Vim-specific word boundary functions
24
- export const findNextWordStart = (text, currentOffset) => {
25
- let i = currentOffset;
26
- if (i >= text.length)
27
- return i;
28
- const currentChar = text[i];
24
+ // Helper functions for line-based word navigation
25
+ export const isWordCharStrict = (char) => /[\w\p{L}\p{N}]/u.test(char); // Matches a single character that is any Unicode letter, any Unicode number, or an underscore
26
+ export const isWhitespace = (char) => /\s/.test(char);
27
+ // Check if a character is a combining mark (only diacritics for now)
28
+ export const isCombiningMark = (char) => /\p{M}/u.test(char);
29
+ // Check if a character should be considered part of a word (including combining marks)
30
+ export const isWordCharWithCombining = (char) => isWordCharStrict(char) || isCombiningMark(char);
31
+ // Get the script of a character (simplified for common scripts)
32
+ export const getCharScript = (char) => {
33
+ if (/[\p{Script=Latin}]/u.test(char))
34
+ return 'latin'; // All Latin script chars including diacritics
35
+ if (/[\p{Script=Han}]/u.test(char))
36
+ return 'han'; // Chinese
37
+ if (/[\p{Script=Arabic}]/u.test(char))
38
+ return 'arabic';
39
+ if (/[\p{Script=Hiragana}]/u.test(char))
40
+ return 'hiragana';
41
+ if (/[\p{Script=Katakana}]/u.test(char))
42
+ return 'katakana';
43
+ if (/[\p{Script=Cyrillic}]/u.test(char))
44
+ return 'cyrillic';
45
+ return 'other';
46
+ };
47
+ // Check if two characters are from different scripts (indicating word boundary)
48
+ export const isDifferentScript = (char1, char2) => {
49
+ if (!isWordCharStrict(char1) || !isWordCharStrict(char2))
50
+ return false;
51
+ return getCharScript(char1) !== getCharScript(char2);
52
+ };
53
+ // Find next word start within a line, starting from col
54
+ export const findNextWordStartInLine = (line, col) => {
55
+ const chars = toCodePoints(line);
56
+ let i = col;
57
+ if (i >= chars.length)
58
+ return null;
59
+ const currentChar = chars[i];
29
60
  // Skip current word/sequence based on character type
30
- if (/\w/.test(currentChar)) {
31
- // Skip current word characters
32
- while (i < text.length && /\w/.test(text[i])) {
61
+ if (isWordCharStrict(currentChar)) {
62
+ while (i < chars.length && isWordCharWithCombining(chars[i])) {
63
+ // Check for script boundary - if next character is from different script, stop here
64
+ if (i + 1 < chars.length &&
65
+ isWordCharStrict(chars[i + 1]) &&
66
+ isDifferentScript(chars[i], chars[i + 1])) {
67
+ i++; // Include current character
68
+ break; // Stop at script boundary
69
+ }
33
70
  i++;
34
71
  }
35
72
  }
36
- else if (!/\s/.test(currentChar)) {
37
- // Skip current non-word, non-whitespace characters (like "/", ".", etc.)
38
- while (i < text.length && !/\w/.test(text[i]) && !/\s/.test(text[i])) {
73
+ else if (!isWhitespace(currentChar)) {
74
+ while (i < chars.length &&
75
+ !isWordCharStrict(chars[i]) &&
76
+ !isWhitespace(chars[i])) {
39
77
  i++;
40
78
  }
41
79
  }
42
80
  // Skip whitespace
43
- while (i < text.length && /\s/.test(text[i])) {
81
+ while (i < chars.length && isWhitespace(chars[i])) {
44
82
  i++;
45
83
  }
46
- // If we reached the end of text and there's no next word,
47
- // vim behavior for dw is to delete to the end of the current word
48
- if (i >= text.length) {
49
- // Go back to find the end of the last word
50
- let endOfLastWord = text.length - 1;
51
- while (endOfLastWord >= 0 && /\s/.test(text[endOfLastWord])) {
52
- endOfLastWord--;
53
- }
54
- // For dw on last word, return position AFTER the last character to delete entire word
55
- return Math.max(currentOffset + 1, endOfLastWord + 1);
56
- }
57
- return i;
84
+ return i < chars.length ? i : null;
58
85
  };
59
- export const findPrevWordStart = (text, currentOffset) => {
60
- let i = currentOffset;
61
- // If at beginning of text, return current position
62
- if (i <= 0) {
63
- return currentOffset;
64
- }
65
- // Move back one character to start searching
86
+ // Find previous word start within a line
87
+ export const findPrevWordStartInLine = (line, col) => {
88
+ const chars = toCodePoints(line);
89
+ let i = col;
90
+ if (i <= 0)
91
+ return null;
66
92
  i--;
67
93
  // Skip whitespace moving backwards
68
- while (i >= 0 && (text[i] === ' ' || text[i] === '\t' || text[i] === '\n')) {
94
+ while (i >= 0 && isWhitespace(chars[i])) {
69
95
  i--;
70
96
  }
71
- if (i < 0) {
72
- return 0; // Reached beginning of text
73
- }
74
- const charAtI = text[i];
75
- if (/\w/.test(charAtI)) {
97
+ if (i < 0)
98
+ return null;
99
+ if (isWordCharStrict(chars[i])) {
76
100
  // We're in a word, move to its beginning
77
- while (i >= 0 && /\w/.test(text[i])) {
101
+ while (i >= 0 && isWordCharStrict(chars[i])) {
102
+ // Check for script boundary - if previous character is from different script, stop here
103
+ if (i - 1 >= 0 &&
104
+ isWordCharStrict(chars[i - 1]) &&
105
+ isDifferentScript(chars[i], chars[i - 1])) {
106
+ return i; // Return current position at script boundary
107
+ }
78
108
  i--;
79
109
  }
80
- return i + 1; // Return first character of word
110
+ return i + 1;
81
111
  }
82
112
  else {
83
113
  // We're in punctuation, move to its beginning
84
- while (i >= 0 &&
85
- !/\w/.test(text[i]) &&
86
- text[i] !== ' ' &&
87
- text[i] !== '\t' &&
88
- text[i] !== '\n') {
114
+ while (i >= 0 && !isWordCharStrict(chars[i]) && !isWhitespace(chars[i])) {
89
115
  i--;
90
116
  }
91
- return i + 1; // Return first character of punctuation sequence
117
+ return i + 1;
92
118
  }
93
119
  };
94
- export const findWordEnd = (text, currentOffset) => {
95
- let i = currentOffset;
96
- // If we're already at the end of a word, advance to next word
97
- if (i < text.length &&
98
- /\w/.test(text[i]) &&
99
- (i + 1 >= text.length || !/\w/.test(text[i + 1]))) {
100
- // We're at the end of a word, move forward to find next word
120
+ // Find word end within a line
121
+ export const findWordEndInLine = (line, col) => {
122
+ const chars = toCodePoints(line);
123
+ let i = col;
124
+ // If we're already at the end of a word (including punctuation sequences), advance to next word
125
+ // This includes both regular word endings and script boundaries
126
+ const atEndOfWordChar = i < chars.length &&
127
+ isWordCharWithCombining(chars[i]) &&
128
+ (i + 1 >= chars.length ||
129
+ !isWordCharWithCombining(chars[i + 1]) ||
130
+ (isWordCharStrict(chars[i]) &&
131
+ i + 1 < chars.length &&
132
+ isWordCharStrict(chars[i + 1]) &&
133
+ isDifferentScript(chars[i], chars[i + 1])));
134
+ const atEndOfPunctuation = i < chars.length &&
135
+ !isWordCharWithCombining(chars[i]) &&
136
+ !isWhitespace(chars[i]) &&
137
+ (i + 1 >= chars.length ||
138
+ isWhitespace(chars[i + 1]) ||
139
+ isWordCharWithCombining(chars[i + 1]));
140
+ if (atEndOfWordChar || atEndOfPunctuation) {
141
+ // We're at the end of a word or punctuation sequence, move forward to find next word
101
142
  i++;
102
- // Skip whitespace/punctuation to find next word
103
- while (i < text.length && !/\w/.test(text[i])) {
143
+ // Skip whitespace to find next word or punctuation
144
+ while (i < chars.length && isWhitespace(chars[i])) {
104
145
  i++;
105
146
  }
106
147
  }
107
- // If we're not on a word character, find the next word
108
- if (i < text.length && !/\w/.test(text[i])) {
109
- while (i < text.length && !/\w/.test(text[i])) {
148
+ // If we're not on a word character, find the next word or punctuation sequence
149
+ if (i < chars.length && !isWordCharWithCombining(chars[i])) {
150
+ // Skip whitespace to find next word or punctuation
151
+ while (i < chars.length && isWhitespace(chars[i])) {
110
152
  i++;
111
153
  }
112
154
  }
113
- // Move to end of current word
114
- while (i < text.length && /\w/.test(text[i])) {
115
- i++;
155
+ // Move to end of current word (including combining marks, but stop at script boundaries)
156
+ let foundWord = false;
157
+ let lastBaseCharPos = -1;
158
+ if (i < chars.length && isWordCharWithCombining(chars[i])) {
159
+ // Handle word characters
160
+ while (i < chars.length && isWordCharWithCombining(chars[i])) {
161
+ foundWord = true;
162
+ // Track the position of the last base character (not combining mark)
163
+ if (isWordCharStrict(chars[i])) {
164
+ lastBaseCharPos = i;
165
+ }
166
+ // Check if next character is from a different script (word boundary)
167
+ if (i + 1 < chars.length &&
168
+ isWordCharStrict(chars[i + 1]) &&
169
+ isDifferentScript(chars[i], chars[i + 1])) {
170
+ i++; // Include current character
171
+ if (isWordCharStrict(chars[i - 1])) {
172
+ lastBaseCharPos = i - 1;
173
+ }
174
+ break; // Stop at script boundary
175
+ }
176
+ i++;
177
+ }
178
+ }
179
+ else if (i < chars.length && !isWhitespace(chars[i])) {
180
+ // Handle punctuation sequences (like ████)
181
+ while (i < chars.length &&
182
+ !isWordCharStrict(chars[i]) &&
183
+ !isWhitespace(chars[i])) {
184
+ foundWord = true;
185
+ lastBaseCharPos = i;
186
+ i++;
187
+ }
116
188
  }
117
- // Move back one to be on the last character of the word
118
- return Math.max(currentOffset, i - 1);
189
+ // Only return a position if we actually found a word
190
+ // Return the position of the last base character, not combining marks
191
+ if (foundWord && lastBaseCharPos >= col) {
192
+ return lastBaseCharPos;
193
+ }
194
+ return null;
119
195
  };
120
- // Helper functions for vim operations
121
- export const getOffsetFromPosition = (row, col, lines) => {
122
- let offset = 0;
123
- for (let i = 0; i < row; i++) {
124
- offset += lines[i].length + 1; // +1 for newline
196
+ // Find next word across lines
197
+ export const findNextWordAcrossLines = (lines, cursorRow, cursorCol, searchForWordStart) => {
198
+ // First try current line
199
+ const currentLine = lines[cursorRow] || '';
200
+ const colInCurrentLine = searchForWordStart
201
+ ? findNextWordStartInLine(currentLine, cursorCol)
202
+ : findWordEndInLine(currentLine, cursorCol);
203
+ if (colInCurrentLine !== null) {
204
+ return { row: cursorRow, col: colInCurrentLine };
125
205
  }
126
- offset += col;
127
- return offset;
206
+ // Search subsequent lines
207
+ for (let row = cursorRow + 1; row < lines.length; row++) {
208
+ const line = lines[row] || '';
209
+ const chars = toCodePoints(line);
210
+ // For empty lines, if we haven't found any words yet, return the empty line
211
+ if (chars.length === 0) {
212
+ // Check if there are any words in remaining lines
213
+ let hasWordsInLaterLines = false;
214
+ for (let laterRow = row + 1; laterRow < lines.length; laterRow++) {
215
+ const laterLine = lines[laterRow] || '';
216
+ const laterChars = toCodePoints(laterLine);
217
+ let firstNonWhitespace = 0;
218
+ while (firstNonWhitespace < laterChars.length &&
219
+ isWhitespace(laterChars[firstNonWhitespace])) {
220
+ firstNonWhitespace++;
221
+ }
222
+ if (firstNonWhitespace < laterChars.length) {
223
+ hasWordsInLaterLines = true;
224
+ break;
225
+ }
226
+ }
227
+ // If no words in later lines, return the empty line
228
+ if (!hasWordsInLaterLines) {
229
+ return { row, col: 0 };
230
+ }
231
+ continue;
232
+ }
233
+ // Find first non-whitespace
234
+ let firstNonWhitespace = 0;
235
+ while (firstNonWhitespace < chars.length &&
236
+ isWhitespace(chars[firstNonWhitespace])) {
237
+ firstNonWhitespace++;
238
+ }
239
+ if (firstNonWhitespace < chars.length) {
240
+ if (searchForWordStart) {
241
+ return { row, col: firstNonWhitespace };
242
+ }
243
+ else {
244
+ // For word end, find the end of the first word
245
+ const endCol = findWordEndInLine(line, firstNonWhitespace);
246
+ if (endCol !== null) {
247
+ return { row, col: endCol };
248
+ }
249
+ }
250
+ }
251
+ }
252
+ return null;
128
253
  };
254
+ // Find previous word across lines
255
+ export const findPrevWordAcrossLines = (lines, cursorRow, cursorCol) => {
256
+ // First try current line
257
+ const currentLine = lines[cursorRow] || '';
258
+ const colInCurrentLine = findPrevWordStartInLine(currentLine, cursorCol);
259
+ if (colInCurrentLine !== null) {
260
+ return { row: cursorRow, col: colInCurrentLine };
261
+ }
262
+ // Search previous lines
263
+ for (let row = cursorRow - 1; row >= 0; row--) {
264
+ const line = lines[row] || '';
265
+ const chars = toCodePoints(line);
266
+ if (chars.length === 0)
267
+ continue;
268
+ // Find last word start
269
+ let lastWordStart = chars.length;
270
+ while (lastWordStart > 0 && isWhitespace(chars[lastWordStart - 1])) {
271
+ lastWordStart--;
272
+ }
273
+ if (lastWordStart > 0) {
274
+ // Find start of this word
275
+ const wordStart = findPrevWordStartInLine(line, lastWordStart);
276
+ if (wordStart !== null) {
277
+ return { row, col: wordStart };
278
+ }
279
+ }
280
+ }
281
+ return null;
282
+ };
283
+ // Helper functions for vim line operations
129
284
  export const getPositionFromOffsets = (startOffset, endOffset, lines) => {
130
285
  let offset = 0;
131
286
  let startRow = 0;
@@ -221,21 +376,42 @@ export const replaceRangeInternal = (state, startRow, startCol, endRow, endCol,
221
376
  /**
222
377
  * Strip characters that can break terminal rendering.
223
378
  *
224
- * Strip ANSI escape codes and control characters except for line breaks.
225
- * Control characters such as delete break terminal UI rendering.
379
+ * Uses Node.js built-in stripVTControlCharacters to handle VT sequences,
380
+ * then filters remaining control characters that can disrupt display.
381
+ *
382
+ * Characters stripped:
383
+ * - ANSI escape sequences (via strip-ansi)
384
+ * - VT control sequences (via Node.js util.stripVTControlCharacters)
385
+ * - C0 control chars (0x00-0x1F) except CR/LF which are handled elsewhere
386
+ * - C1 control chars (0x80-0x9F) that can cause display issues
387
+ *
388
+ * Characters preserved:
389
+ * - All printable Unicode including emojis
390
+ * - DEL (0x7F) - handled functionally by applyOperations, not a display issue
391
+ * - CR/LF (0x0D/0x0A) - needed for line breaks
226
392
  */
227
393
  function stripUnsafeCharacters(str) {
228
- const stripped = stripAnsi(str);
229
- return toCodePoints(stripped)
394
+ const strippedAnsi = stripAnsi(str);
395
+ const strippedVT = stripVTControlCharacters(strippedAnsi);
396
+ return toCodePoints(strippedVT)
230
397
  .filter((char) => {
231
- if (char.length > 1)
232
- return false;
233
398
  const code = char.codePointAt(0);
234
- if (code === undefined) {
399
+ if (code === undefined)
235
400
  return false;
236
- }
237
- const isUnsafe = code === 127 || (code <= 31 && code !== 13 && code !== 10);
238
- return !isUnsafe;
401
+ // Preserve CR/LF for line handling
402
+ if (code === 0x0a || code === 0x0d)
403
+ return true;
404
+ // Remove C0 control chars (except CR/LF) that can break display
405
+ // Examples: BELL(0x07) makes noise, BS(0x08) moves cursor, VT(0x0B), FF(0x0C)
406
+ if (code >= 0x00 && code <= 0x1f)
407
+ return false;
408
+ // Remove C1 control chars (0x80-0x9F) - legacy 8-bit control codes
409
+ if (code >= 0x80 && code <= 0x9f)
410
+ return false;
411
+ // Preserve DEL (0x7F) - it's handled functionally by applyOperations as backspace
412
+ // and doesn't cause rendering issues when displayed
413
+ // Preserve all other characters including Unicode/emojis
414
+ return true;
239
415
  })
240
416
  .join('');
241
417
  }