@google/gemini-cli 0.1.12-nightly.250714.b018e2d3 → 0.1.13-nightly.250726.fb751c54

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 (238) hide show
  1. package/README.md +30 -3
  2. package/dist/google-gemini-cli-0.1.13.tgz +0 -0
  3. package/dist/package.json +6 -4
  4. package/dist/src/acp/acp.d.ts +208 -0
  5. package/dist/src/acp/acp.js +193 -0
  6. package/dist/src/acp/acp.js.map +1 -0
  7. package/dist/src/acp/acpPeer.d.ts +8 -0
  8. package/dist/src/acp/acpPeer.js +537 -0
  9. package/dist/src/acp/acpPeer.js.map +1 -0
  10. package/dist/src/config/config.d.ts +6 -2
  11. package/dist/src/config/config.js +77 -13
  12. package/dist/src/config/config.js.map +1 -1
  13. package/dist/src/config/extension.d.ts +2 -2
  14. package/dist/src/config/extension.js +21 -16
  15. package/dist/src/config/extension.js.map +1 -1
  16. package/dist/src/config/settings.d.ts +15 -2
  17. package/dist/src/config/settings.js +25 -10
  18. package/dist/src/config/settings.js.map +1 -1
  19. package/dist/src/gemini.js +10 -20
  20. package/dist/src/gemini.js.map +1 -1
  21. package/dist/src/generated/git-commit.d.ts +1 -1
  22. package/dist/src/generated/git-commit.js +1 -1
  23. package/dist/src/patches/is-in-ci.d.ts +7 -0
  24. package/dist/src/patches/is-in-ci.js +15 -0
  25. package/dist/src/patches/is-in-ci.js.map +1 -0
  26. package/dist/src/services/BuiltinCommandLoader.d.ts +24 -0
  27. package/dist/src/services/BuiltinCommandLoader.js +72 -0
  28. package/dist/src/services/BuiltinCommandLoader.js.map +1 -0
  29. package/dist/src/services/CommandService.d.ts +43 -5
  30. package/dist/src/services/CommandService.js +61 -19
  31. package/dist/src/services/CommandService.js.map +1 -1
  32. package/dist/src/services/FileCommandLoader.d.ts +37 -0
  33. package/dist/src/services/FileCommandLoader.js +156 -0
  34. package/dist/src/services/FileCommandLoader.js.map +1 -0
  35. package/dist/src/services/McpPromptLoader.d.ts +25 -0
  36. package/dist/src/services/McpPromptLoader.js +192 -0
  37. package/dist/src/services/McpPromptLoader.js.map +1 -0
  38. package/dist/src/services/prompt-processors/argumentProcessor.d.ts +21 -0
  39. package/dist/src/services/prompt-processors/argumentProcessor.js +28 -0
  40. package/dist/src/services/prompt-processors/argumentProcessor.js.map +1 -0
  41. package/dist/src/services/prompt-processors/types.d.ts +34 -0
  42. package/dist/src/services/prompt-processors/types.js +10 -0
  43. package/dist/src/services/prompt-processors/types.js.map +1 -0
  44. package/dist/src/services/types.d.ts +22 -0
  45. package/dist/src/services/types.js +7 -0
  46. package/dist/src/services/types.js.map +1 -0
  47. package/dist/src/ui/App.js +103 -98
  48. package/dist/src/ui/App.js.map +1 -1
  49. package/dist/src/ui/colors.js +6 -0
  50. package/dist/src/ui/colors.js.map +1 -1
  51. package/dist/src/ui/commands/aboutCommand.d.ts +7 -0
  52. package/dist/src/ui/commands/aboutCommand.js +39 -0
  53. package/dist/src/ui/commands/aboutCommand.js.map +1 -0
  54. package/dist/src/ui/commands/authCommand.d.ts +7 -0
  55. package/dist/src/ui/commands/authCommand.js +16 -0
  56. package/dist/src/ui/commands/authCommand.js.map +1 -0
  57. package/dist/src/ui/commands/bugCommand.d.ts +7 -0
  58. package/dist/src/ui/commands/bugCommand.js +63 -0
  59. package/dist/src/ui/commands/bugCommand.js.map +1 -0
  60. package/dist/src/ui/commands/chatCommand.d.ts +7 -0
  61. package/dist/src/ui/commands/chatCommand.js +179 -0
  62. package/dist/src/ui/commands/chatCommand.js.map +1 -0
  63. package/dist/src/ui/commands/clearCommand.js +14 -2
  64. package/dist/src/ui/commands/clearCommand.js.map +1 -1
  65. package/dist/src/ui/commands/compressCommand.d.ts +7 -0
  66. package/dist/src/ui/commands/compressCommand.js +64 -0
  67. package/dist/src/ui/commands/compressCommand.js.map +1 -0
  68. package/dist/src/ui/commands/copyCommand.d.ts +7 -0
  69. package/dist/src/ui/commands/copyCommand.js +59 -0
  70. package/dist/src/ui/commands/copyCommand.js.map +1 -0
  71. package/dist/src/ui/commands/corgiCommand.d.ts +7 -0
  72. package/dist/src/ui/commands/corgiCommand.js +15 -0
  73. package/dist/src/ui/commands/corgiCommand.js.map +1 -0
  74. package/dist/src/ui/commands/docsCommand.d.ts +7 -0
  75. package/dist/src/ui/commands/docsCommand.js +31 -0
  76. package/dist/src/ui/commands/docsCommand.js.map +1 -0
  77. package/dist/src/ui/commands/editorCommand.d.ts +7 -0
  78. package/dist/src/ui/commands/editorCommand.js +16 -0
  79. package/dist/src/ui/commands/editorCommand.js.map +1 -0
  80. package/dist/src/ui/commands/extensionsCommand.d.ts +7 -0
  81. package/dist/src/ui/commands/extensionsCommand.js +31 -0
  82. package/dist/src/ui/commands/extensionsCommand.js.map +1 -0
  83. package/dist/src/ui/commands/helpCommand.js +3 -1
  84. package/dist/src/ui/commands/helpCommand.js.map +1 -1
  85. package/dist/src/ui/commands/ideCommand.d.ts +8 -0
  86. package/dist/src/ui/commands/ideCommand.js +121 -0
  87. package/dist/src/ui/commands/ideCommand.js.map +1 -0
  88. package/dist/src/ui/commands/mcpCommand.d.ts +7 -0
  89. package/dist/src/ui/commands/mcpCommand.js +425 -0
  90. package/dist/src/ui/commands/mcpCommand.js.map +1 -0
  91. package/dist/src/ui/commands/memoryCommand.js +11 -4
  92. package/dist/src/ui/commands/memoryCommand.js.map +1 -1
  93. package/dist/src/ui/commands/privacyCommand.d.ts +7 -0
  94. package/dist/src/ui/commands/privacyCommand.js +16 -0
  95. package/dist/src/ui/commands/privacyCommand.js.map +1 -0
  96. package/dist/src/ui/commands/quitCommand.d.ts +7 -0
  97. package/dist/src/ui/commands/quitCommand.js +34 -0
  98. package/dist/src/ui/commands/quitCommand.js.map +1 -0
  99. package/dist/src/ui/commands/restoreCommand.d.ts +8 -0
  100. package/dist/src/ui/commands/restoreCommand.js +128 -0
  101. package/dist/src/ui/commands/restoreCommand.js.map +1 -0
  102. package/dist/src/ui/commands/statsCommand.d.ts +7 -0
  103. package/dist/src/ui/commands/statsCommand.js +54 -0
  104. package/dist/src/ui/commands/statsCommand.js.map +1 -0
  105. package/dist/src/ui/commands/themeCommand.js +2 -0
  106. package/dist/src/ui/commands/themeCommand.js.map +1 -1
  107. package/dist/src/ui/commands/toolsCommand.d.ts +7 -0
  108. package/dist/src/ui/commands/toolsCommand.js +56 -0
  109. package/dist/src/ui/commands/toolsCommand.js.map +1 -0
  110. package/dist/src/ui/commands/types.d.ts +61 -4
  111. package/dist/src/ui/commands/types.js +6 -1
  112. package/dist/src/ui/commands/types.js.map +1 -1
  113. package/dist/src/ui/commands/vimCommand.d.ts +7 -0
  114. package/dist/src/ui/commands/vimCommand.js +23 -0
  115. package/dist/src/ui/commands/vimCommand.js.map +1 -0
  116. package/dist/src/ui/components/ContextSummaryDisplay.d.ts +6 -1
  117. package/dist/src/ui/components/ContextSummaryDisplay.js +46 -19
  118. package/dist/src/ui/components/ContextSummaryDisplay.js.map +1 -1
  119. package/dist/src/ui/components/Footer.d.ts +1 -0
  120. package/dist/src/ui/components/Footer.js +2 -2
  121. package/dist/src/ui/components/Footer.js.map +1 -1
  122. package/dist/src/ui/components/Help.d.ts +1 -1
  123. package/dist/src/ui/components/IDEContextDetailDisplay.d.ts +11 -0
  124. package/dist/src/ui/components/IDEContextDetailDisplay.js +19 -0
  125. package/dist/src/ui/components/IDEContextDetailDisplay.js.map +1 -0
  126. package/dist/src/ui/components/InputPrompt.d.ts +3 -1
  127. package/dist/src/ui/components/InputPrompt.js +85 -113
  128. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  129. package/dist/src/ui/components/ThemeDialog.js +51 -25
  130. package/dist/src/ui/components/ThemeDialog.js.map +1 -1
  131. package/dist/src/ui/components/messages/DiffRenderer.d.ts +1 -0
  132. package/dist/src/ui/components/messages/DiffRenderer.js +12 -11
  133. package/dist/src/ui/components/messages/DiffRenderer.js.map +1 -1
  134. package/dist/src/ui/components/messages/ToolConfirmationMessage.js +5 -4
  135. package/dist/src/ui/components/messages/ToolConfirmationMessage.js.map +1 -1
  136. package/dist/src/ui/components/messages/ToolGroupMessage.js +3 -1
  137. package/dist/src/ui/components/messages/ToolGroupMessage.js.map +1 -1
  138. package/dist/src/ui/components/shared/MaxSizedBox.js +69 -2
  139. package/dist/src/ui/components/shared/MaxSizedBox.js.map +1 -1
  140. package/dist/src/ui/components/shared/RadioButtonSelect.d.ts +3 -1
  141. package/dist/src/ui/components/shared/RadioButtonSelect.js +60 -10
  142. package/dist/src/ui/components/shared/RadioButtonSelect.js.map +1 -1
  143. package/dist/src/ui/components/shared/text-buffer.d.ts +273 -3
  144. package/dist/src/ui/components/shared/text-buffer.js +426 -80
  145. package/dist/src/ui/components/shared/text-buffer.js.map +1 -1
  146. package/dist/src/ui/components/shared/vim-buffer-actions.d.ts +72 -0
  147. package/dist/src/ui/components/shared/vim-buffer-actions.js +565 -0
  148. package/dist/src/ui/components/shared/vim-buffer-actions.js.map +1 -0
  149. package/dist/src/ui/constants.d.ts +1 -0
  150. package/dist/src/ui/constants.js +1 -0
  151. package/dist/src/ui/constants.js.map +1 -1
  152. package/dist/src/ui/contexts/VimModeContext.d.ts +19 -0
  153. package/dist/src/ui/contexts/VimModeContext.js +48 -0
  154. package/dist/src/ui/contexts/VimModeContext.js.map +1 -0
  155. package/dist/src/ui/hooks/atCommandProcessor.js +54 -15
  156. package/dist/src/ui/hooks/atCommandProcessor.js.map +1 -1
  157. package/dist/src/ui/hooks/shellCommandProcessor.d.ts +1 -1
  158. package/dist/src/ui/hooks/shellCommandProcessor.js +57 -19
  159. package/dist/src/ui/hooks/shellCommandProcessor.js.map +1 -1
  160. package/dist/src/ui/hooks/slashCommandProcessor.d.ts +3 -10
  161. package/dist/src/ui/hooks/slashCommandProcessor.js +121 -879
  162. package/dist/src/ui/hooks/slashCommandProcessor.js.map +1 -1
  163. package/dist/src/ui/hooks/useAuthCommand.js +2 -1
  164. package/dist/src/ui/hooks/useAuthCommand.js.map +1 -1
  165. package/dist/src/ui/hooks/useCompletion.d.ts +4 -1
  166. package/dist/src/ui/hooks/useCompletion.js +141 -25
  167. package/dist/src/ui/hooks/useCompletion.js.map +1 -1
  168. package/dist/src/ui/hooks/useFocus.d.ts +6 -0
  169. package/dist/src/ui/hooks/useFocus.js +41 -0
  170. package/dist/src/ui/hooks/useFocus.js.map +1 -0
  171. package/dist/src/ui/hooks/useGeminiStream.js +74 -11
  172. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  173. package/dist/src/ui/hooks/useInputHistory.d.ts +1 -1
  174. package/dist/src/ui/hooks/useKeypress.js +5 -2
  175. package/dist/src/ui/hooks/useKeypress.js.map +1 -1
  176. package/dist/src/ui/hooks/usePrivacySettings.js +5 -5
  177. package/dist/src/ui/hooks/usePrivacySettings.js.map +1 -1
  178. package/dist/src/ui/hooks/useShellHistory.d.ts +3 -2
  179. package/dist/src/ui/hooks/useShellHistory.js.map +1 -1
  180. package/dist/src/ui/hooks/useThemeCommand.js +24 -23
  181. package/dist/src/ui/hooks/useThemeCommand.js.map +1 -1
  182. package/dist/src/ui/hooks/vim.d.ts +28 -0
  183. package/dist/src/ui/hooks/vim.js +630 -0
  184. package/dist/src/ui/hooks/vim.js.map +1 -0
  185. package/dist/src/ui/themes/ansi-light.js +3 -1
  186. package/dist/src/ui/themes/ansi-light.js.map +1 -1
  187. package/dist/src/ui/themes/ansi.js +2 -0
  188. package/dist/src/ui/themes/ansi.js.map +1 -1
  189. package/dist/src/ui/themes/atom-one-dark.js +2 -0
  190. package/dist/src/ui/themes/atom-one-dark.js.map +1 -1
  191. package/dist/src/ui/themes/ayu-light.js +3 -1
  192. package/dist/src/ui/themes/ayu-light.js.map +1 -1
  193. package/dist/src/ui/themes/ayu.js +3 -1
  194. package/dist/src/ui/themes/ayu.js.map +1 -1
  195. package/dist/src/ui/themes/color-utils.d.ts +21 -0
  196. package/dist/src/ui/themes/color-utils.js +221 -0
  197. package/dist/src/ui/themes/color-utils.js.map +1 -0
  198. package/dist/src/ui/themes/dracula.js +2 -0
  199. package/dist/src/ui/themes/dracula.js.map +1 -1
  200. package/dist/src/ui/themes/github-dark.js +2 -0
  201. package/dist/src/ui/themes/github-dark.js.map +1 -1
  202. package/dist/src/ui/themes/github-light.js +2 -0
  203. package/dist/src/ui/themes/github-light.js.map +1 -1
  204. package/dist/src/ui/themes/googlecode.js +3 -1
  205. package/dist/src/ui/themes/googlecode.js.map +1 -1
  206. package/dist/src/ui/themes/no-color.js +3 -1
  207. package/dist/src/ui/themes/no-color.js.map +1 -1
  208. package/dist/src/ui/themes/shades-of-purple.d.ts +1 -1
  209. package/dist/src/ui/themes/shades-of-purple.js +3 -1
  210. package/dist/src/ui/themes/shades-of-purple.js.map +1 -1
  211. package/dist/src/ui/themes/theme-manager.d.ts +31 -6
  212. package/dist/src/ui/themes/theme-manager.js +104 -34
  213. package/dist/src/ui/themes/theme-manager.js.map +1 -1
  214. package/dist/src/ui/themes/theme.d.ts +22 -3
  215. package/dist/src/ui/themes/theme.js +229 -182
  216. package/dist/src/ui/themes/theme.js.map +1 -1
  217. package/dist/src/ui/themes/xcode.js +3 -1
  218. package/dist/src/ui/themes/xcode.js.map +1 -1
  219. package/dist/src/ui/types.d.ts +9 -1
  220. package/dist/src/ui/utils/CodeColorizer.d.ts +3 -1
  221. package/dist/src/ui/utils/CodeColorizer.js +23 -11
  222. package/dist/src/ui/utils/CodeColorizer.js.map +1 -1
  223. package/dist/src/ui/utils/commandUtils.d.ts +1 -0
  224. package/dist/src/ui/utils/commandUtils.js +47 -0
  225. package/dist/src/ui/utils/commandUtils.js.map +1 -1
  226. package/dist/src/ui/utils/markdownUtilities.js +1 -1
  227. package/dist/src/ui/utils/updateCheck.js +4 -0
  228. package/dist/src/ui/utils/updateCheck.js.map +1 -1
  229. package/dist/src/utils/sandbox.js +7 -3
  230. package/dist/src/utils/sandbox.js.map +1 -1
  231. package/dist/src/utils/userStartupWarnings.js +22 -1
  232. package/dist/src/utils/userStartupWarnings.js.map +1 -1
  233. package/dist/src/validateNonInterActiveAuth.d.ts +7 -0
  234. package/dist/src/validateNonInterActiveAuth.js +35 -0
  235. package/dist/src/validateNonInterActiveAuth.js.map +1 -0
  236. package/dist/tsconfig.tsbuildinfo +1 -1
  237. package/package.json +7 -5
  238. package/dist/google-gemini-cli-0.1.12.tgz +0 -0
@@ -12,6 +12,7 @@ import { useState, useCallback, useEffect, useMemo, useReducer } from 'react';
12
12
  import stringWidth from 'string-width';
13
13
  import { unescapePath } from '@google/gemini-cli-core';
14
14
  import { toCodePoints, cpLen, cpSlice } from '../../utils/textUtils.js';
15
+ import { handleVimAction } from './vim-buffer-actions.js';
15
16
  // Simple helper for word‑wise ops.
16
17
  function isWordChar(ch) {
17
18
  if (ch === undefined) {
@@ -19,6 +20,209 @@ function isWordChar(ch) {
19
20
  }
20
21
  return !/[\s,.;!?]/.test(ch);
21
22
  }
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];
29
+ // 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])) {
33
+ i++;
34
+ }
35
+ }
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])) {
39
+ i++;
40
+ }
41
+ }
42
+ // Skip whitespace
43
+ while (i < text.length && /\s/.test(text[i])) {
44
+ i++;
45
+ }
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;
58
+ };
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
66
+ i--;
67
+ // Skip whitespace moving backwards
68
+ while (i >= 0 && (text[i] === ' ' || text[i] === '\t' || text[i] === '\n')) {
69
+ i--;
70
+ }
71
+ if (i < 0) {
72
+ return 0; // Reached beginning of text
73
+ }
74
+ const charAtI = text[i];
75
+ if (/\w/.test(charAtI)) {
76
+ // We're in a word, move to its beginning
77
+ while (i >= 0 && /\w/.test(text[i])) {
78
+ i--;
79
+ }
80
+ return i + 1; // Return first character of word
81
+ }
82
+ else {
83
+ // 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') {
89
+ i--;
90
+ }
91
+ return i + 1; // Return first character of punctuation sequence
92
+ }
93
+ };
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
101
+ i++;
102
+ // Skip whitespace/punctuation to find next word
103
+ while (i < text.length && !/\w/.test(text[i])) {
104
+ i++;
105
+ }
106
+ }
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])) {
110
+ i++;
111
+ }
112
+ }
113
+ // Move to end of current word
114
+ while (i < text.length && /\w/.test(text[i])) {
115
+ i++;
116
+ }
117
+ // Move back one to be on the last character of the word
118
+ return Math.max(currentOffset, i - 1);
119
+ };
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
125
+ }
126
+ offset += col;
127
+ return offset;
128
+ };
129
+ export const getPositionFromOffsets = (startOffset, endOffset, lines) => {
130
+ let offset = 0;
131
+ let startRow = 0;
132
+ let startCol = 0;
133
+ let endRow = 0;
134
+ let endCol = 0;
135
+ // Find start position
136
+ for (let i = 0; i < lines.length; i++) {
137
+ const lineLength = lines[i].length + 1; // +1 for newline
138
+ if (offset + lineLength > startOffset) {
139
+ startRow = i;
140
+ startCol = startOffset - offset;
141
+ break;
142
+ }
143
+ offset += lineLength;
144
+ }
145
+ // Find end position
146
+ offset = 0;
147
+ for (let i = 0; i < lines.length; i++) {
148
+ const lineLength = lines[i].length + (i < lines.length - 1 ? 1 : 0); // +1 for newline except last line
149
+ if (offset + lineLength >= endOffset) {
150
+ endRow = i;
151
+ endCol = endOffset - offset;
152
+ break;
153
+ }
154
+ offset += lineLength;
155
+ }
156
+ return { startRow, startCol, endRow, endCol };
157
+ };
158
+ export const getLineRangeOffsets = (startRow, lineCount, lines) => {
159
+ let startOffset = 0;
160
+ // Calculate start offset
161
+ for (let i = 0; i < startRow; i++) {
162
+ startOffset += lines[i].length + 1; // +1 for newline
163
+ }
164
+ // Calculate end offset
165
+ let endOffset = startOffset;
166
+ for (let i = 0; i < lineCount; i++) {
167
+ const lineIndex = startRow + i;
168
+ if (lineIndex < lines.length) {
169
+ endOffset += lines[lineIndex].length;
170
+ if (lineIndex < lines.length - 1) {
171
+ endOffset += 1; // +1 for newline
172
+ }
173
+ }
174
+ }
175
+ return { startOffset, endOffset };
176
+ };
177
+ export const replaceRangeInternal = (state, startRow, startCol, endRow, endCol, text) => {
178
+ const currentLine = (row) => state.lines[row] || '';
179
+ const currentLineLen = (row) => cpLen(currentLine(row));
180
+ const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
181
+ if (startRow > endRow ||
182
+ (startRow === endRow && startCol > endCol) ||
183
+ startRow < 0 ||
184
+ startCol < 0 ||
185
+ endRow >= state.lines.length ||
186
+ (endRow < state.lines.length && endCol > currentLineLen(endRow))) {
187
+ return state; // Invalid range
188
+ }
189
+ const newLines = [...state.lines];
190
+ const sCol = clamp(startCol, 0, currentLineLen(startRow));
191
+ const eCol = clamp(endCol, 0, currentLineLen(endRow));
192
+ const prefix = cpSlice(currentLine(startRow), 0, sCol);
193
+ const suffix = cpSlice(currentLine(endRow), eCol);
194
+ const normalisedReplacement = text
195
+ .replace(/\r\n/g, '\n')
196
+ .replace(/\r/g, '\n');
197
+ const replacementParts = normalisedReplacement.split('\n');
198
+ // Replace the content
199
+ if (startRow === endRow) {
200
+ newLines[startRow] = prefix + normalisedReplacement + suffix;
201
+ }
202
+ else {
203
+ const firstLine = prefix + replacementParts[0];
204
+ if (replacementParts.length === 1) {
205
+ // Single line of replacement text, but spanning multiple original lines
206
+ newLines.splice(startRow, endRow - startRow + 1, firstLine + suffix);
207
+ }
208
+ else {
209
+ // Multi-line replacement text
210
+ const lastLine = replacementParts[replacementParts.length - 1] + suffix;
211
+ const middleLines = replacementParts.slice(1, -1);
212
+ newLines.splice(startRow, endRow - startRow + 1, firstLine, ...middleLines, lastLine);
213
+ }
214
+ }
215
+ const finalCursorRow = startRow + replacementParts.length - 1;
216
+ const finalCursorCol = (replacementParts.length > 1 ? 0 : sCol) +
217
+ cpLen(replacementParts[replacementParts.length - 1]);
218
+ return {
219
+ ...state,
220
+ lines: newLines,
221
+ cursorRow: Math.min(Math.max(finalCursorRow, 0), newLines.length - 1),
222
+ cursorCol: Math.max(0, Math.min(finalCursorCol, cpLen(newLines[finalCursorRow] || ''))),
223
+ preferredCol: null,
224
+ };
225
+ };
22
226
  /**
23
227
  * Strip characters that can break terminal rendering.
24
228
  *
@@ -27,7 +231,7 @@ function isWordChar(ch) {
27
231
  */
28
232
  function stripUnsafeCharacters(str) {
29
233
  const stripped = stripAnsi(str);
30
- return toCodePoints(stripAnsi(stripped))
234
+ return toCodePoints(stripped)
31
235
  .filter((char) => {
32
236
  if (char.length > 1)
33
237
  return false;
@@ -106,6 +310,24 @@ export function offsetToLogicalPos(text, offset) {
106
310
  }
107
311
  return [row, col];
108
312
  }
313
+ /**
314
+ * Converts logical row/col position to absolute text offset
315
+ * Inverse operation of offsetToLogicalPos
316
+ */
317
+ export function logicalPosToOffset(lines, row, col) {
318
+ let offset = 0;
319
+ // Clamp row to valid range
320
+ const actualRow = Math.min(row, lines.length - 1);
321
+ // Add lengths of all lines before the target row
322
+ for (let i = 0; i < actualRow; i++) {
323
+ offset += cpLen(lines[i]) + 1; // +1 for newline
324
+ }
325
+ // Add column offset within the target row
326
+ if (actualRow >= 0 && actualRow < lines.length) {
327
+ offset += Math.min(col, cpLen(lines[actualRow]));
328
+ }
329
+ return offset;
330
+ }
109
331
  // Helper to calculate visual lines and map cursor positions
110
332
  function calculateVisualLayout(logicalLines, logicalCursor, viewportWidth) {
111
333
  const visualLines = [];
@@ -279,26 +501,27 @@ function calculateVisualLayout(logicalLines, logicalCursor, viewportWidth) {
279
501
  };
280
502
  }
281
503
  const historyLimit = 100;
282
- export function textBufferReducer(state, action) {
283
- const pushUndo = (currentState) => {
284
- const snapshot = {
285
- lines: [...currentState.lines],
286
- cursorRow: currentState.cursorRow,
287
- cursorCol: currentState.cursorCol,
288
- };
289
- const newStack = [...currentState.undoStack, snapshot];
290
- if (newStack.length > historyLimit) {
291
- newStack.shift();
292
- }
293
- return { ...currentState, undoStack: newStack, redoStack: [] };
504
+ export const pushUndo = (currentState) => {
505
+ const snapshot = {
506
+ lines: [...currentState.lines],
507
+ cursorRow: currentState.cursorRow,
508
+ cursorCol: currentState.cursorCol,
294
509
  };
510
+ const newStack = [...currentState.undoStack, snapshot];
511
+ if (newStack.length > historyLimit) {
512
+ newStack.shift();
513
+ }
514
+ return { ...currentState, undoStack: newStack, redoStack: [] };
515
+ };
516
+ export function textBufferReducer(state, action) {
517
+ const pushUndoLocal = pushUndo;
295
518
  const currentLine = (r) => state.lines[r] ?? '';
296
519
  const currentLineLen = (r) => cpLen(currentLine(r));
297
520
  switch (action.type) {
298
521
  case 'set_text': {
299
522
  let nextState = state;
300
523
  if (action.pushToUndo !== false) {
301
- nextState = pushUndo(state);
524
+ nextState = pushUndoLocal(state);
302
525
  }
303
526
  const newContentLines = action.payload
304
527
  .replace(/\r\n?/g, '\n')
@@ -314,7 +537,7 @@ export function textBufferReducer(state, action) {
314
537
  };
315
538
  }
316
539
  case 'insert': {
317
- const nextState = pushUndo(state);
540
+ const nextState = pushUndoLocal(state);
318
541
  const newLines = [...nextState.lines];
319
542
  let newCursorRow = nextState.cursorRow;
320
543
  let newCursorCol = nextState.cursorCol;
@@ -346,7 +569,7 @@ export function textBufferReducer(state, action) {
346
569
  };
347
570
  }
348
571
  case 'backspace': {
349
- const nextState = pushUndo(state);
572
+ const nextState = pushUndoLocal(state);
350
573
  const newLines = [...nextState.lines];
351
574
  let newCursorRow = nextState.cursorRow;
352
575
  let newCursorCol = nextState.cursorCol;
@@ -523,7 +746,7 @@ export function textBufferReducer(state, action) {
523
746
  const { cursorRow, cursorCol, lines } = state;
524
747
  const lineContent = currentLine(cursorRow);
525
748
  if (cursorCol < currentLineLen(cursorRow)) {
526
- const nextState = pushUndo(state);
749
+ const nextState = pushUndoLocal(state);
527
750
  const newLines = [...nextState.lines];
528
751
  newLines[cursorRow] =
529
752
  cpSlice(lineContent, 0, cursorCol) +
@@ -531,7 +754,7 @@ export function textBufferReducer(state, action) {
531
754
  return { ...nextState, lines: newLines, preferredCol: null };
532
755
  }
533
756
  else if (cursorRow < lines.length - 1) {
534
- const nextState = pushUndo(state);
757
+ const nextState = pushUndoLocal(state);
535
758
  const nextLineContent = currentLine(cursorRow + 1);
536
759
  const newLines = [...nextState.lines];
537
760
  newLines[cursorRow] = lineContent + nextLineContent;
@@ -546,7 +769,7 @@ export function textBufferReducer(state, action) {
546
769
  return state;
547
770
  if (cursorCol === 0) {
548
771
  // Act as a backspace
549
- const nextState = pushUndo(state);
772
+ const nextState = pushUndoLocal(state);
550
773
  const prevLineContent = currentLine(cursorRow - 1);
551
774
  const currentLineContentVal = currentLine(cursorRow);
552
775
  const newCol = cpLen(prevLineContent);
@@ -561,7 +784,7 @@ export function textBufferReducer(state, action) {
561
784
  preferredCol: null,
562
785
  };
563
786
  }
564
- const nextState = pushUndo(state);
787
+ const nextState = pushUndoLocal(state);
565
788
  const lineContent = currentLine(cursorRow);
566
789
  const arr = toCodePoints(lineContent);
567
790
  let start = cursorCol;
@@ -599,14 +822,14 @@ export function textBufferReducer(state, action) {
599
822
  return state;
600
823
  if (cursorCol >= arr.length) {
601
824
  // Act as a delete
602
- const nextState = pushUndo(state);
825
+ const nextState = pushUndoLocal(state);
603
826
  const nextLineContent = currentLine(cursorRow + 1);
604
827
  const newLines = [...nextState.lines];
605
828
  newLines[cursorRow] = lineContent + nextLineContent;
606
829
  newLines.splice(cursorRow + 1, 1);
607
830
  return { ...nextState, lines: newLines, preferredCol: null };
608
831
  }
609
- const nextState = pushUndo(state);
832
+ const nextState = pushUndoLocal(state);
610
833
  let end = cursorCol;
611
834
  while (end < arr.length && !isWordChar(arr[end]))
612
835
  end++;
@@ -621,14 +844,14 @@ export function textBufferReducer(state, action) {
621
844
  const { cursorRow, cursorCol, lines } = state;
622
845
  const lineContent = currentLine(cursorRow);
623
846
  if (cursorCol < currentLineLen(cursorRow)) {
624
- const nextState = pushUndo(state);
847
+ const nextState = pushUndoLocal(state);
625
848
  const newLines = [...nextState.lines];
626
849
  newLines[cursorRow] = cpSlice(lineContent, 0, cursorCol);
627
850
  return { ...nextState, lines: newLines };
628
851
  }
629
852
  else if (cursorRow < lines.length - 1) {
630
853
  // Act as a delete
631
- const nextState = pushUndo(state);
854
+ const nextState = pushUndoLocal(state);
632
855
  const nextLineContent = currentLine(cursorRow + 1);
633
856
  const newLines = [...nextState.lines];
634
857
  newLines[cursorRow] = lineContent + nextLineContent;
@@ -640,7 +863,7 @@ export function textBufferReducer(state, action) {
640
863
  case 'kill_line_left': {
641
864
  const { cursorRow, cursorCol } = state;
642
865
  if (cursorCol > 0) {
643
- const nextState = pushUndo(state);
866
+ const nextState = pushUndoLocal(state);
644
867
  const lineContent = currentLine(cursorRow);
645
868
  const newLines = [...nextState.lines];
646
869
  newLines[cursorRow] = cpSlice(lineContent, cursorCol);
@@ -687,51 +910,8 @@ export function textBufferReducer(state, action) {
687
910
  }
688
911
  case 'replace_range': {
689
912
  const { startRow, startCol, endRow, endCol, text } = action.payload;
690
- if (startRow > endRow ||
691
- (startRow === endRow && startCol > endCol) ||
692
- startRow < 0 ||
693
- startCol < 0 ||
694
- endRow >= state.lines.length ||
695
- (endRow < state.lines.length && endCol > currentLineLen(endRow))) {
696
- return state; // Invalid range
697
- }
698
- const nextState = pushUndo(state);
699
- const newLines = [...nextState.lines];
700
- const sCol = clamp(startCol, 0, currentLineLen(startRow));
701
- const eCol = clamp(endCol, 0, currentLineLen(endRow));
702
- const prefix = cpSlice(currentLine(startRow), 0, sCol);
703
- const suffix = cpSlice(currentLine(endRow), eCol);
704
- const normalisedReplacement = text
705
- .replace(/\r\n/g, '\n')
706
- .replace(/\r/g, '\n');
707
- const replacementParts = normalisedReplacement.split('\n');
708
- // Replace the content
709
- if (startRow === endRow) {
710
- newLines[startRow] = prefix + normalisedReplacement + suffix;
711
- }
712
- else {
713
- const firstLine = prefix + replacementParts[0];
714
- if (replacementParts.length === 1) {
715
- // Single line of replacement text, but spanning multiple original lines
716
- newLines.splice(startRow, endRow - startRow + 1, firstLine + suffix);
717
- }
718
- else {
719
- // Multi-line replacement text
720
- const lastLine = replacementParts[replacementParts.length - 1] + suffix;
721
- const middleLines = replacementParts.slice(1, -1);
722
- newLines.splice(startRow, endRow - startRow + 1, firstLine, ...middleLines, lastLine);
723
- }
724
- }
725
- const finalCursorRow = startRow + replacementParts.length - 1;
726
- const finalCursorCol = (replacementParts.length > 1 ? 0 : sCol) +
727
- cpLen(replacementParts[replacementParts.length - 1]);
728
- return {
729
- ...nextState,
730
- lines: newLines,
731
- cursorRow: finalCursorRow,
732
- cursorCol: finalCursorCol,
733
- preferredCol: null,
734
- };
913
+ const nextState = pushUndoLocal(state);
914
+ return replaceRangeInternal(nextState, startRow, startCol, endRow, endCol, text);
735
915
  }
736
916
  case 'move_to_offset': {
737
917
  const { offset } = action.payload;
@@ -744,8 +924,42 @@ export function textBufferReducer(state, action) {
744
924
  };
745
925
  }
746
926
  case 'create_undo_snapshot': {
747
- return pushUndo(state);
927
+ return pushUndoLocal(state);
748
928
  }
929
+ // Vim-specific operations
930
+ case 'vim_delete_word_forward':
931
+ case 'vim_delete_word_backward':
932
+ case 'vim_delete_word_end':
933
+ case 'vim_change_word_forward':
934
+ case 'vim_change_word_backward':
935
+ case 'vim_change_word_end':
936
+ case 'vim_delete_line':
937
+ case 'vim_change_line':
938
+ case 'vim_delete_to_end_of_line':
939
+ case 'vim_change_to_end_of_line':
940
+ case 'vim_change_movement':
941
+ case 'vim_move_left':
942
+ case 'vim_move_right':
943
+ case 'vim_move_up':
944
+ case 'vim_move_down':
945
+ case 'vim_move_word_forward':
946
+ case 'vim_move_word_backward':
947
+ case 'vim_move_word_end':
948
+ case 'vim_delete_char':
949
+ case 'vim_insert_at_cursor':
950
+ case 'vim_append_at_cursor':
951
+ case 'vim_open_line_below':
952
+ case 'vim_open_line_above':
953
+ case 'vim_append_at_line_end':
954
+ case 'vim_insert_at_line_start':
955
+ case 'vim_move_to_line_start':
956
+ case 'vim_move_to_line_end':
957
+ case 'vim_move_to_first_nonwhitespace':
958
+ case 'vim_move_to_first_line':
959
+ case 'vim_move_to_last_line':
960
+ case 'vim_move_to_line':
961
+ case 'vim_escape_insert_mode':
962
+ return handleVimAction(state, action);
749
963
  default: {
750
964
  const exhaustiveCheck = action;
751
965
  console.error(`Unknown action encountered: ${exhaustiveCheck}`);
@@ -798,22 +1012,23 @@ export function useTextBuffer({ initialText = '', initialCursorOffset = 0, viewp
798
1012
  setVisualScrollRow(newVisualScrollRow);
799
1013
  }
800
1014
  }, [visualCursor, visualScrollRow, viewport]);
801
- const insert = useCallback((ch) => {
1015
+ const insert = useCallback((ch, { paste = false } = {}) => {
802
1016
  if (/[\n\r]/.test(ch)) {
803
1017
  dispatch({ type: 'insert', payload: ch });
804
1018
  return;
805
1019
  }
806
1020
  const minLengthToInferAsDragDrop = 3;
807
- if (ch.length >= minLengthToInferAsDragDrop && !shellModeActive) {
808
- let potentialPath = ch;
809
- if (potentialPath.length > 2 &&
810
- potentialPath.startsWith("'") &&
811
- potentialPath.endsWith("'")) {
812
- potentialPath = ch.slice(1, -1);
1021
+ if (ch.length >= minLengthToInferAsDragDrop &&
1022
+ !shellModeActive &&
1023
+ paste) {
1024
+ let potentialPath = ch.trim();
1025
+ const quoteMatch = potentialPath.match(/^'(.*)'$/);
1026
+ if (quoteMatch) {
1027
+ potentialPath = quoteMatch[1];
813
1028
  }
814
1029
  potentialPath = potentialPath.trim();
815
1030
  if (isValidPath(unescapePath(potentialPath))) {
816
- ch = `@${potentialPath}`;
1031
+ ch = `@${potentialPath} `;
817
1032
  }
818
1033
  }
819
1034
  let currentText = '';
@@ -866,6 +1081,104 @@ export function useTextBuffer({ initialText = '', initialCursorOffset = 0, viewp
866
1081
  const killLineLeft = useCallback(() => {
867
1082
  dispatch({ type: 'kill_line_left' });
868
1083
  }, []);
1084
+ // Vim-specific operations
1085
+ const vimDeleteWordForward = useCallback((count) => {
1086
+ dispatch({ type: 'vim_delete_word_forward', payload: { count } });
1087
+ }, []);
1088
+ const vimDeleteWordBackward = useCallback((count) => {
1089
+ dispatch({ type: 'vim_delete_word_backward', payload: { count } });
1090
+ }, []);
1091
+ const vimDeleteWordEnd = useCallback((count) => {
1092
+ dispatch({ type: 'vim_delete_word_end', payload: { count } });
1093
+ }, []);
1094
+ const vimChangeWordForward = useCallback((count) => {
1095
+ dispatch({ type: 'vim_change_word_forward', payload: { count } });
1096
+ }, []);
1097
+ const vimChangeWordBackward = useCallback((count) => {
1098
+ dispatch({ type: 'vim_change_word_backward', payload: { count } });
1099
+ }, []);
1100
+ const vimChangeWordEnd = useCallback((count) => {
1101
+ dispatch({ type: 'vim_change_word_end', payload: { count } });
1102
+ }, []);
1103
+ const vimDeleteLine = useCallback((count) => {
1104
+ dispatch({ type: 'vim_delete_line', payload: { count } });
1105
+ }, []);
1106
+ const vimChangeLine = useCallback((count) => {
1107
+ dispatch({ type: 'vim_change_line', payload: { count } });
1108
+ }, []);
1109
+ const vimDeleteToEndOfLine = useCallback(() => {
1110
+ dispatch({ type: 'vim_delete_to_end_of_line' });
1111
+ }, []);
1112
+ const vimChangeToEndOfLine = useCallback(() => {
1113
+ dispatch({ type: 'vim_change_to_end_of_line' });
1114
+ }, []);
1115
+ const vimChangeMovement = useCallback((movement, count) => {
1116
+ dispatch({ type: 'vim_change_movement', payload: { movement, count } });
1117
+ }, []);
1118
+ // New vim navigation and operation methods
1119
+ const vimMoveLeft = useCallback((count) => {
1120
+ dispatch({ type: 'vim_move_left', payload: { count } });
1121
+ }, []);
1122
+ const vimMoveRight = useCallback((count) => {
1123
+ dispatch({ type: 'vim_move_right', payload: { count } });
1124
+ }, []);
1125
+ const vimMoveUp = useCallback((count) => {
1126
+ dispatch({ type: 'vim_move_up', payload: { count } });
1127
+ }, []);
1128
+ const vimMoveDown = useCallback((count) => {
1129
+ dispatch({ type: 'vim_move_down', payload: { count } });
1130
+ }, []);
1131
+ const vimMoveWordForward = useCallback((count) => {
1132
+ dispatch({ type: 'vim_move_word_forward', payload: { count } });
1133
+ }, []);
1134
+ const vimMoveWordBackward = useCallback((count) => {
1135
+ dispatch({ type: 'vim_move_word_backward', payload: { count } });
1136
+ }, []);
1137
+ const vimMoveWordEnd = useCallback((count) => {
1138
+ dispatch({ type: 'vim_move_word_end', payload: { count } });
1139
+ }, []);
1140
+ const vimDeleteChar = useCallback((count) => {
1141
+ dispatch({ type: 'vim_delete_char', payload: { count } });
1142
+ }, []);
1143
+ const vimInsertAtCursor = useCallback(() => {
1144
+ dispatch({ type: 'vim_insert_at_cursor' });
1145
+ }, []);
1146
+ const vimAppendAtCursor = useCallback(() => {
1147
+ dispatch({ type: 'vim_append_at_cursor' });
1148
+ }, []);
1149
+ const vimOpenLineBelow = useCallback(() => {
1150
+ dispatch({ type: 'vim_open_line_below' });
1151
+ }, []);
1152
+ const vimOpenLineAbove = useCallback(() => {
1153
+ dispatch({ type: 'vim_open_line_above' });
1154
+ }, []);
1155
+ const vimAppendAtLineEnd = useCallback(() => {
1156
+ dispatch({ type: 'vim_append_at_line_end' });
1157
+ }, []);
1158
+ const vimInsertAtLineStart = useCallback(() => {
1159
+ dispatch({ type: 'vim_insert_at_line_start' });
1160
+ }, []);
1161
+ const vimMoveToLineStart = useCallback(() => {
1162
+ dispatch({ type: 'vim_move_to_line_start' });
1163
+ }, []);
1164
+ const vimMoveToLineEnd = useCallback(() => {
1165
+ dispatch({ type: 'vim_move_to_line_end' });
1166
+ }, []);
1167
+ const vimMoveToFirstNonWhitespace = useCallback(() => {
1168
+ dispatch({ type: 'vim_move_to_first_nonwhitespace' });
1169
+ }, []);
1170
+ const vimMoveToFirstLine = useCallback(() => {
1171
+ dispatch({ type: 'vim_move_to_first_line' });
1172
+ }, []);
1173
+ const vimMoveToLastLine = useCallback(() => {
1174
+ dispatch({ type: 'vim_move_to_last_line' });
1175
+ }, []);
1176
+ const vimMoveToLine = useCallback((lineNumber) => {
1177
+ dispatch({ type: 'vim_move_to_line', payload: { lineNumber } });
1178
+ }, []);
1179
+ const vimEscapeInsertMode = useCallback(() => {
1180
+ dispatch({ type: 'vim_escape_insert_mode' });
1181
+ }, []);
869
1182
  const openInExternalEditor = useCallback(async (opts = {}) => {
870
1183
  const editor = opts.editor ??
871
1184
  process.env['VISUAL'] ??
@@ -959,7 +1272,7 @@ export function useTextBuffer({ initialText = '', initialCursorOffset = 0, viewp
959
1272
  else if (key.name === 'delete' || (key.ctrl && key.name === 'd'))
960
1273
  del();
961
1274
  else if (input && !key.ctrl && !key.meta) {
962
- insert(input);
1275
+ insert(input, { paste: key.paste });
963
1276
  }
964
1277
  }, [newline, move, deleteWordLeft, deleteWordRight, backspace, del, insert]);
965
1278
  const renderedVisualLines = useMemo(() => visualLines.slice(visualScrollRow, visualScrollRow + viewport.height), [visualLines, visualScrollRow, viewport.height]);
@@ -1004,6 +1317,39 @@ export function useTextBuffer({ initialText = '', initialCursorOffset = 0, viewp
1004
1317
  killLineLeft,
1005
1318
  handleInput,
1006
1319
  openInExternalEditor,
1320
+ // Vim-specific operations
1321
+ vimDeleteWordForward,
1322
+ vimDeleteWordBackward,
1323
+ vimDeleteWordEnd,
1324
+ vimChangeWordForward,
1325
+ vimChangeWordBackward,
1326
+ vimChangeWordEnd,
1327
+ vimDeleteLine,
1328
+ vimChangeLine,
1329
+ vimDeleteToEndOfLine,
1330
+ vimChangeToEndOfLine,
1331
+ vimChangeMovement,
1332
+ vimMoveLeft,
1333
+ vimMoveRight,
1334
+ vimMoveUp,
1335
+ vimMoveDown,
1336
+ vimMoveWordForward,
1337
+ vimMoveWordBackward,
1338
+ vimMoveWordEnd,
1339
+ vimDeleteChar,
1340
+ vimInsertAtCursor,
1341
+ vimAppendAtCursor,
1342
+ vimOpenLineBelow,
1343
+ vimOpenLineAbove,
1344
+ vimAppendAtLineEnd,
1345
+ vimInsertAtLineStart,
1346
+ vimMoveToLineStart,
1347
+ vimMoveToLineEnd,
1348
+ vimMoveToFirstNonWhitespace,
1349
+ vimMoveToFirstLine,
1350
+ vimMoveToLastLine,
1351
+ vimMoveToLine,
1352
+ vimEscapeInsertMode,
1007
1353
  };
1008
1354
  return returnValue;
1009
1355
  }