@google/gemini-cli 0.11.0-preview.0 → 0.12.0-nightly.20251023.c4c0c0d1

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 (140) hide show
  1. package/dist/google-gemini-cli-0.12.0-nightly.20251022.0542de95.tgz +0 -0
  2. package/dist/package.json +4 -3
  3. package/dist/src/commands/extensions/disable.js +13 -6
  4. package/dist/src/commands/extensions/disable.js.map +1 -1
  5. package/dist/src/commands/extensions/enable.js +13 -6
  6. package/dist/src/commands/extensions/enable.js.map +1 -1
  7. package/dist/src/commands/extensions/install.js +12 -2
  8. package/dist/src/commands/extensions/install.js.map +1 -1
  9. package/dist/src/commands/extensions/install.test.js +11 -3
  10. package/dist/src/commands/extensions/install.test.js.map +1 -1
  11. package/dist/src/commands/extensions/link.js +12 -2
  12. package/dist/src/commands/extensions/link.js.map +1 -1
  13. package/dist/src/commands/extensions/list.js +13 -4
  14. package/dist/src/commands/extensions/list.js.map +1 -1
  15. package/dist/src/commands/extensions/uninstall.js +12 -2
  16. package/dist/src/commands/extensions/uninstall.js.map +1 -1
  17. package/dist/src/commands/extensions/update.js +17 -13
  18. package/dist/src/commands/extensions/update.js.map +1 -1
  19. package/dist/src/commands/extensions.js +1 -0
  20. package/dist/src/commands/extensions.js.map +1 -1
  21. package/dist/src/commands/mcp/list.js +10 -3
  22. package/dist/src/commands/mcp/list.js.map +1 -1
  23. package/dist/src/commands/mcp/list.test.js +12 -6
  24. package/dist/src/commands/mcp/list.test.js.map +1 -1
  25. package/dist/src/config/config.js +12 -0
  26. package/dist/src/config/config.js.map +1 -1
  27. package/dist/src/config/config.test.js +11 -0
  28. package/dist/src/config/config.test.js.map +1 -1
  29. package/dist/src/config/extension-manager.d.ts +38 -0
  30. package/dist/src/config/extension-manager.js +412 -0
  31. package/dist/src/config/extension-manager.js.map +1 -0
  32. package/dist/src/config/extension.d.ts +4 -51
  33. package/dist/src/config/extension.js +1 -535
  34. package/dist/src/config/extension.js.map +1 -1
  35. package/dist/src/config/extension.test.js +316 -159
  36. package/dist/src/config/extension.test.js.map +1 -1
  37. package/dist/src/config/extensions/consent.d.ts +38 -0
  38. package/dist/src/config/extensions/consent.js +123 -0
  39. package/dist/src/config/extensions/consent.js.map +1 -0
  40. package/dist/src/config/extensions/extensionEnablement.js +1 -1
  41. package/dist/src/config/extensions/extensionEnablement.js.map +1 -1
  42. package/dist/src/config/extensions/extensionSettings.d.ts +15 -0
  43. package/dist/src/config/extensions/extensionSettings.js +63 -0
  44. package/dist/src/config/extensions/extensionSettings.js.map +1 -0
  45. package/dist/src/config/extensions/extensionSettings.test.d.ts +6 -0
  46. package/dist/src/config/extensions/extensionSettings.test.js +137 -0
  47. package/dist/src/config/extensions/extensionSettings.test.js.map +1 -0
  48. package/dist/src/config/extensions/github.d.ts +2 -2
  49. package/dist/src/config/extensions/github.js +3 -8
  50. package/dist/src/config/extensions/github.js.map +1 -1
  51. package/dist/src/config/extensions/github.test.js +25 -7
  52. package/dist/src/config/extensions/github.test.js.map +1 -1
  53. package/dist/src/config/extensions/storage.d.ts +14 -0
  54. package/dist/src/config/extensions/storage.js +32 -0
  55. package/dist/src/config/extensions/storage.js.map +1 -0
  56. package/dist/src/config/extensions/update.d.ts +4 -4
  57. package/dist/src/config/extensions/update.js +11 -18
  58. package/dist/src/config/extensions/update.js.map +1 -1
  59. package/dist/src/config/extensions/update.test.js +32 -58
  60. package/dist/src/config/extensions/update.test.js.map +1 -1
  61. package/dist/src/config/extensions/variableSchema.d.ts +0 -6
  62. package/dist/src/config/extensions/variableSchema.js.map +1 -1
  63. package/dist/src/config/extensions/variables.d.ts +4 -0
  64. package/dist/src/config/extensions/variables.js +6 -0
  65. package/dist/src/config/extensions/variables.js.map +1 -1
  66. package/dist/src/config/settings.d.ts +2 -1
  67. package/dist/src/config/settings.js +4 -7
  68. package/dist/src/config/settings.js.map +1 -1
  69. package/dist/src/config/settings.test.js +113 -14
  70. package/dist/src/config/settings.test.js.map +1 -1
  71. package/dist/src/config/settingsSchema.d.ts +9 -0
  72. package/dist/src/config/settingsSchema.js +9 -0
  73. package/dist/src/config/settingsSchema.js.map +1 -1
  74. package/dist/src/gemini.js +22 -5
  75. package/dist/src/gemini.js.map +1 -1
  76. package/dist/src/generated/git-commit.d.ts +2 -2
  77. package/dist/src/generated/git-commit.js +2 -2
  78. package/dist/src/generated/git-commit.js.map +1 -1
  79. package/dist/src/nonInteractiveCli.js +14 -1
  80. package/dist/src/nonInteractiveCli.js.map +1 -1
  81. package/dist/src/nonInteractiveCli.test.js +84 -2
  82. package/dist/src/nonInteractiveCli.test.js.map +1 -1
  83. package/dist/src/test-utils/createExtension.d.ts +3 -1
  84. package/dist/src/test-utils/createExtension.js +3 -3
  85. package/dist/src/test-utils/createExtension.js.map +1 -1
  86. package/dist/src/ui/AppContainer.js +101 -47
  87. package/dist/src/ui/AppContainer.js.map +1 -1
  88. package/dist/src/ui/AppContainer.test.js +138 -79
  89. package/dist/src/ui/AppContainer.test.js.map +1 -1
  90. package/dist/src/ui/commands/extensionsCommand.js +19 -10
  91. package/dist/src/ui/commands/extensionsCommand.js.map +1 -1
  92. package/dist/src/ui/commands/extensionsCommand.test.js +8 -0
  93. package/dist/src/ui/commands/extensionsCommand.test.js.map +1 -1
  94. package/dist/src/ui/components/HistoryItemDisplay.js +1 -1
  95. package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
  96. package/dist/src/ui/components/InputPrompt.js +5 -6
  97. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  98. package/dist/src/ui/components/InputPrompt.test.js +249 -393
  99. package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
  100. package/dist/src/ui/components/SettingsDialog.test.js +0 -29
  101. package/dist/src/ui/components/SettingsDialog.test.js.map +1 -1
  102. package/dist/src/ui/components/views/ExtensionsList.d.ts +7 -1
  103. package/dist/src/ui/components/views/ExtensionsList.js +4 -10
  104. package/dist/src/ui/components/views/ExtensionsList.js.map +1 -1
  105. package/dist/src/ui/components/views/ExtensionsList.test.js +34 -21
  106. package/dist/src/ui/components/views/ExtensionsList.test.js.map +1 -1
  107. package/dist/src/ui/contexts/KeypressContext.js +328 -335
  108. package/dist/src/ui/contexts/KeypressContext.js.map +1 -1
  109. package/dist/src/ui/hooks/useAutoAcceptIndicator.js +10 -0
  110. package/dist/src/ui/hooks/useAutoAcceptIndicator.js.map +1 -1
  111. package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js +30 -0
  112. package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js.map +1 -1
  113. package/dist/src/ui/hooks/useExtensionUpdates.d.ts +14 -4
  114. package/dist/src/ui/hooks/useExtensionUpdates.js +14 -17
  115. package/dist/src/ui/hooks/useExtensionUpdates.js.map +1 -1
  116. package/dist/src/ui/hooks/useExtensionUpdates.test.js +23 -30
  117. package/dist/src/ui/hooks/useExtensionUpdates.test.js.map +1 -1
  118. package/dist/src/ui/hooks/useGitBranchName.js +4 -0
  119. package/dist/src/ui/hooks/useGitBranchName.js.map +1 -1
  120. package/dist/src/ui/hooks/useGitBranchName.test.js +19 -21
  121. package/dist/src/ui/hooks/useGitBranchName.test.js.map +1 -1
  122. package/dist/src/ui/hooks/useReactToolScheduler.js +22 -8
  123. package/dist/src/ui/hooks/useReactToolScheduler.js.map +1 -1
  124. package/dist/src/ui/hooks/useReactToolScheduler.test.d.ts +6 -0
  125. package/dist/src/ui/hooks/useReactToolScheduler.test.js +65 -0
  126. package/dist/src/ui/hooks/useReactToolScheduler.test.js.map +1 -0
  127. package/dist/src/ui/hooks/useToolScheduler.test.js +30 -48
  128. package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
  129. package/dist/src/ui/types.d.ts +2 -1
  130. package/dist/src/ui/types.js.map +1 -1
  131. package/dist/src/ui/utils/CodeColorizer.js +2 -1
  132. package/dist/src/ui/utils/CodeColorizer.js.map +1 -1
  133. package/dist/src/utils/envVarResolver.d.ts +2 -2
  134. package/dist/src/utils/envVarResolver.js +10 -7
  135. package/dist/src/utils/envVarResolver.js.map +1 -1
  136. package/dist/src/zed-integration/schema.d.ts +4 -4
  137. package/dist/src/zed-integration/zedIntegration.js +3 -3
  138. package/dist/src/zed-integration/zedIntegration.js.map +1 -1
  139. package/dist/tsconfig.tsbuildinfo +1 -1
  140. package/package.json +4 -3
@@ -608,210 +608,95 @@ describe('InputPrompt', () => {
608
608
  unmount();
609
609
  });
610
610
  describe('cursor-based completion trigger', () => {
611
- it('should trigger completion when cursor is after @ without spaces', async () => {
612
- // Set up buffer state
613
- mockBuffer.text = '@src/components';
614
- mockBuffer.lines = ['@src/components'];
615
- mockBuffer.cursor = [0, 15];
616
- mockedUseCommandCompletion.mockReturnValue({
617
- ...mockCommandCompletion,
611
+ it.each([
612
+ {
613
+ name: 'should trigger completion when cursor is after @ without spaces',
614
+ text: '@src/components',
615
+ cursor: [0, 15],
618
616
  showSuggestions: true,
619
- suggestions: [{ label: 'Button.tsx', value: 'Button.tsx' }],
620
- });
621
- const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
622
- await wait();
623
- // Verify useCompletion was called with correct signature
624
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
625
- unmount();
626
- });
627
- it('should trigger completion when cursor is after / without spaces', async () => {
628
- mockBuffer.text = '/memory';
629
- mockBuffer.lines = ['/memory'];
630
- mockBuffer.cursor = [0, 7];
631
- mockedUseCommandCompletion.mockReturnValue({
632
- ...mockCommandCompletion,
617
+ },
618
+ {
619
+ name: 'should trigger completion when cursor is after / without spaces',
620
+ text: '/memory',
621
+ cursor: [0, 7],
633
622
  showSuggestions: true,
634
- suggestions: [{ label: 'show', value: 'show' }],
635
- });
636
- const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
637
- await wait();
638
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
639
- unmount();
640
- });
641
- it('should NOT trigger completion when cursor is after space following @', async () => {
642
- mockBuffer.text = '@src/file.ts hello';
643
- mockBuffer.lines = ['@src/file.ts hello'];
644
- mockBuffer.cursor = [0, 18];
645
- mockedUseCommandCompletion.mockReturnValue({
646
- ...mockCommandCompletion,
623
+ },
624
+ {
625
+ name: 'should NOT trigger completion when cursor is after space following @',
626
+ text: '@src/file.ts hello',
627
+ cursor: [0, 18],
647
628
  showSuggestions: false,
648
- suggestions: [],
649
- });
650
- const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
651
- await wait();
652
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
653
- unmount();
654
- });
655
- it('should NOT trigger completion when cursor is after space following /', async () => {
656
- mockBuffer.text = '/memory add';
657
- mockBuffer.lines = ['/memory add'];
658
- mockBuffer.cursor = [0, 11];
659
- mockedUseCommandCompletion.mockReturnValue({
660
- ...mockCommandCompletion,
629
+ },
630
+ {
631
+ name: 'should NOT trigger completion when cursor is after space following /',
632
+ text: '/memory add',
633
+ cursor: [0, 11],
661
634
  showSuggestions: false,
662
- suggestions: [],
663
- });
664
- const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
665
- await wait();
666
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
667
- unmount();
668
- });
669
- it('should NOT trigger completion when cursor is not after @ or /', async () => {
670
- mockBuffer.text = 'hello world';
671
- mockBuffer.lines = ['hello world'];
672
- mockBuffer.cursor = [0, 5];
673
- mockedUseCommandCompletion.mockReturnValue({
674
- ...mockCommandCompletion,
635
+ },
636
+ {
637
+ name: 'should NOT trigger completion when cursor is not after @ or /',
638
+ text: 'hello world',
639
+ cursor: [0, 5],
675
640
  showSuggestions: false,
676
- suggestions: [],
677
- });
678
- const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
679
- await wait();
680
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
681
- unmount();
682
- });
683
- it('should handle multiline text correctly', async () => {
684
- mockBuffer.text = 'first line\n/memory';
685
- mockBuffer.lines = ['first line', '/memory'];
686
- mockBuffer.cursor = [1, 7];
687
- mockedUseCommandCompletion.mockReturnValue({
688
- ...mockCommandCompletion,
641
+ },
642
+ {
643
+ name: 'should handle multiline text correctly',
644
+ text: 'first line\n/memory',
645
+ cursor: [1, 7],
689
646
  showSuggestions: false,
690
- suggestions: [],
691
- });
692
- const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
693
- await wait();
694
- // Verify useCompletion was called with the buffer
695
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
696
- unmount();
697
- });
698
- it('should handle single line slash command correctly', async () => {
699
- mockBuffer.text = '/memory';
700
- mockBuffer.lines = ['/memory'];
701
- mockBuffer.cursor = [0, 7];
702
- mockedUseCommandCompletion.mockReturnValue({
703
- ...mockCommandCompletion,
704
- showSuggestions: true,
705
- suggestions: [{ label: 'show', value: 'show' }],
706
- });
707
- const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
708
- await wait();
709
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
710
- unmount();
711
- });
712
- it('should handle Unicode characters (emojis) correctly in paths', async () => {
713
- // Test with emoji in path after @
714
- mockBuffer.text = '@src/file👍.txt';
715
- mockBuffer.lines = ['@src/file👍.txt'];
716
- mockBuffer.cursor = [0, 14]; // After the emoji character
717
- mockedUseCommandCompletion.mockReturnValue({
718
- ...mockCommandCompletion,
647
+ },
648
+ {
649
+ name: 'should handle Unicode characters (emojis) correctly in paths',
650
+ text: '@src/file👍.txt',
651
+ cursor: [0, 14],
719
652
  showSuggestions: true,
720
- suggestions: [{ label: 'file👍.txt', value: 'file👍.txt' }],
721
- });
722
- const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
723
- await wait();
724
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
725
- unmount();
726
- });
727
- it('should handle Unicode characters with spaces after them', async () => {
728
- // Test with emoji followed by space - should NOT trigger completion
729
- mockBuffer.text = '@src/file👍.txt hello';
730
- mockBuffer.lines = ['@src/file👍.txt hello'];
731
- mockBuffer.cursor = [0, 20]; // After the space
732
- mockedUseCommandCompletion.mockReturnValue({
733
- ...mockCommandCompletion,
653
+ },
654
+ {
655
+ name: 'should handle Unicode characters with spaces after them',
656
+ text: '@src/file👍.txt hello',
657
+ cursor: [0, 20],
734
658
  showSuggestions: false,
735
- suggestions: [],
736
- });
737
- const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
738
- await wait();
739
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
740
- unmount();
741
- });
742
- it('should handle escaped spaces in paths correctly', async () => {
743
- // Test with escaped space in path - should trigger completion
744
- mockBuffer.text = '@src/my\\ file.txt';
745
- mockBuffer.lines = ['@src/my\\ file.txt'];
746
- mockBuffer.cursor = [0, 16]; // After the escaped space and filename
747
- mockedUseCommandCompletion.mockReturnValue({
748
- ...mockCommandCompletion,
659
+ },
660
+ {
661
+ name: 'should handle escaped spaces in paths correctly',
662
+ text: '@src/my\\ file.txt',
663
+ cursor: [0, 16],
749
664
  showSuggestions: true,
750
- suggestions: [{ label: 'my file.txt', value: 'my file.txt' }],
751
- });
752
- const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
753
- await wait();
754
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
755
- unmount();
756
- });
757
- it('should NOT trigger completion after unescaped space following escaped space', async () => {
758
- // Test: @path/my\ file.txt hello (unescaped space after escaped space)
759
- mockBuffer.text = '@path/my\\ file.txt hello';
760
- mockBuffer.lines = ['@path/my\\ file.txt hello'];
761
- mockBuffer.cursor = [0, 24]; // After "hello"
762
- mockedUseCommandCompletion.mockReturnValue({
763
- ...mockCommandCompletion,
665
+ },
666
+ {
667
+ name: 'should NOT trigger completion after unescaped space following escaped space',
668
+ text: '@path/my\\ file.txt hello',
669
+ cursor: [0, 24],
764
670
  showSuggestions: false,
765
- suggestions: [],
766
- });
767
- const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
768
- await wait();
769
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
770
- unmount();
771
- });
772
- it('should handle multiple escaped spaces in paths', async () => {
773
- // Test with multiple escaped spaces
774
- mockBuffer.text = '@docs/my\\ long\\ file\\ name.md';
775
- mockBuffer.lines = ['@docs/my\\ long\\ file\\ name.md'];
776
- mockBuffer.cursor = [0, 29]; // At the end
777
- mockedUseCommandCompletion.mockReturnValue({
778
- ...mockCommandCompletion,
671
+ },
672
+ {
673
+ name: 'should handle multiple escaped spaces in paths',
674
+ text: '@docs/my\\ long\\ file\\ name.md',
675
+ cursor: [0, 29],
779
676
  showSuggestions: true,
780
- suggestions: [
781
- { label: 'my long file name.md', value: 'my long file name.md' },
782
- ],
783
- });
784
- const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
785
- await wait();
786
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
787
- unmount();
788
- });
789
- it('should handle escaped spaces in slash commands', async () => {
790
- // Test escaped spaces with slash commands (though less common)
791
- mockBuffer.text = '/memory\\ test';
792
- mockBuffer.lines = ['/memory\\ test'];
793
- mockBuffer.cursor = [0, 13]; // At the end
794
- mockedUseCommandCompletion.mockReturnValue({
795
- ...mockCommandCompletion,
677
+ },
678
+ {
679
+ name: 'should handle escaped spaces in slash commands',
680
+ text: '/memory\\ test',
681
+ cursor: [0, 13],
796
682
  showSuggestions: true,
797
- suggestions: [{ label: 'test-command', value: 'test-command' }],
798
- });
799
- const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
800
- await wait();
801
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
802
- unmount();
803
- });
804
- it('should handle Unicode characters with escaped spaces', async () => {
805
- // Test combining Unicode and escaped spaces
806
- mockBuffer.text = '@' + path.join('files', 'emoji\\ 👍\\ test.txt');
807
- mockBuffer.lines = ['@' + path.join('files', 'emoji\\ 👍\\ test.txt')];
808
- mockBuffer.cursor = [0, 25]; // After the escaped space and emoji
683
+ },
684
+ {
685
+ name: 'should handle Unicode characters with escaped spaces',
686
+ text: `@${path.join('files', 'emoji\\ 👍\\ test.txt')}`,
687
+ cursor: [0, 25],
688
+ showSuggestions: true,
689
+ },
690
+ ])('$name', async ({ text, cursor, showSuggestions }) => {
691
+ mockBuffer.text = text;
692
+ mockBuffer.lines = text.split('\n');
693
+ mockBuffer.cursor = cursor;
809
694
  mockedUseCommandCompletion.mockReturnValue({
810
695
  ...mockCommandCompletion,
811
- showSuggestions: true,
812
- suggestions: [
813
- { label: 'emoji 👍 test.txt', value: 'emoji 👍 test.txt' },
814
- ],
696
+ showSuggestions,
697
+ suggestions: showSuggestions
698
+ ? [{ label: 'suggestion', value: 'suggestion' }]
699
+ : [],
815
700
  });
816
701
  const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
817
702
  await wait();
@@ -876,165 +761,132 @@ describe('InputPrompt', () => {
876
761
  });
877
762
  });
878
763
  describe('Highlighting and Cursor Display', () => {
879
- it('should display cursor mid-word by highlighting the character', async () => {
880
- mockBuffer.text = 'hello world';
881
- mockBuffer.lines = ['hello world'];
882
- mockBuffer.viewportVisualLines = ['hello world'];
883
- mockBuffer.visualCursor = [0, 3]; // cursor on the second 'l'
884
- const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
885
- await wait();
886
- const frame = stdout.lastFrame();
887
- // The component will render the text with the character at the cursor inverted.
888
- expect(frame).toContain(`hel${chalk.inverse('l')}o world`);
889
- unmount();
890
- });
891
- it('should display cursor at the beginning of the line', async () => {
892
- mockBuffer.text = 'hello';
893
- mockBuffer.lines = ['hello'];
894
- mockBuffer.viewportVisualLines = ['hello'];
895
- mockBuffer.visualCursor = [0, 0]; // cursor on 'h'
896
- const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
897
- await wait();
898
- const frame = stdout.lastFrame();
899
- expect(frame).toContain(`${chalk.inverse('h')}ello`);
900
- unmount();
901
- });
902
- it('should display cursor at the end of the line as an inverted space', async () => {
903
- mockBuffer.text = 'hello';
904
- mockBuffer.lines = ['hello'];
905
- mockBuffer.viewportVisualLines = ['hello'];
906
- mockBuffer.visualCursor = [0, 5]; // cursor after 'o'
907
- const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
908
- await wait();
909
- const frame = stdout.lastFrame();
910
- expect(frame).toContain(`hello${chalk.inverse(' ')}`);
911
- unmount();
912
- });
913
- it('should display cursor correctly on a highlighted token', async () => {
914
- mockBuffer.text = 'run @path/to/file';
915
- mockBuffer.lines = ['run @path/to/file'];
916
- mockBuffer.viewportVisualLines = ['run @path/to/file'];
917
- mockBuffer.visualCursor = [0, 9]; // cursor on 't' in 'to'
918
- const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
919
- await wait();
920
- const frame = stdout.lastFrame();
921
- // The token '@path/to/file' is colored, and the cursor highlights one char inside it.
922
- expect(frame).toContain(`@path/${chalk.inverse('t')}o/file`);
923
- unmount();
924
- });
925
- it('should display cursor correctly for multi-byte unicode characters', async () => {
926
- const text = 'hello 👍 world';
927
- mockBuffer.text = text;
928
- mockBuffer.lines = [text];
929
- mockBuffer.viewportVisualLines = [text];
930
- mockBuffer.visualCursor = [0, 6]; // cursor on '👍'
931
- const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
932
- await wait();
933
- const frame = stdout.lastFrame();
934
- expect(frame).toContain(`hello ${chalk.inverse('👍')} world`);
935
- unmount();
936
- });
937
- it('should display cursor at the end of a line with unicode characters', async () => {
938
- const text = 'hello 👍';
939
- mockBuffer.text = text;
940
- mockBuffer.lines = [text];
941
- mockBuffer.viewportVisualLines = [text];
942
- mockBuffer.visualCursor = [0, 8]; // cursor after '👍' (length is 6 + 2 for emoji)
943
- const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
944
- await wait();
945
- const frame = stdout.lastFrame();
946
- expect(frame).toContain(`hello 👍${chalk.inverse(' ')}`);
947
- unmount();
948
- });
949
- it('should display cursor on an empty line', async () => {
950
- mockBuffer.text = '';
951
- mockBuffer.lines = [''];
952
- mockBuffer.viewportVisualLines = [''];
953
- mockBuffer.visualCursor = [0, 0];
954
- const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
955
- await wait();
956
- const frame = stdout.lastFrame();
957
- expect(frame).toContain(chalk.inverse(' '));
958
- unmount();
959
- });
960
- it('should display cursor on a space between words', async () => {
961
- mockBuffer.text = 'hello world';
962
- mockBuffer.lines = ['hello world'];
963
- mockBuffer.viewportVisualLines = ['hello world'];
964
- mockBuffer.visualCursor = [0, 5]; // cursor on the space
965
- const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
966
- await wait();
967
- const frame = stdout.lastFrame();
968
- expect(frame).toContain(`hello${chalk.inverse(' ')}world`);
969
- unmount();
970
- });
971
- it('should display cursor in the middle of a line in a multiline block', async () => {
972
- const text = 'first line\nsecond line\nthird line';
973
- mockBuffer.text = text;
974
- mockBuffer.lines = text.split('\n');
975
- mockBuffer.viewportVisualLines = text.split('\n');
976
- mockBuffer.visualCursor = [1, 3]; // cursor on 'o' in 'second'
977
- mockBuffer.visualToLogicalMap = [
978
- [0, 0],
979
- [1, 0],
980
- [2, 0],
981
- ];
982
- const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
983
- await wait();
984
- const frame = stdout.lastFrame();
985
- expect(frame).toContain(`sec${chalk.inverse('o')}nd line`);
986
- unmount();
987
- });
988
- it('should display cursor at the beginning of a line in a multiline block', async () => {
989
- const text = 'first line\nsecond line';
990
- mockBuffer.text = text;
991
- mockBuffer.lines = text.split('\n');
992
- mockBuffer.viewportVisualLines = text.split('\n');
993
- mockBuffer.visualCursor = [1, 0]; // cursor on 's' in 'second'
994
- mockBuffer.visualToLogicalMap = [
995
- [0, 0],
996
- [1, 0],
997
- ];
998
- const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
999
- await wait();
1000
- const frame = stdout.lastFrame();
1001
- expect(frame).toContain(`${chalk.inverse('s')}econd line`);
1002
- unmount();
1003
- });
1004
- it('should display cursor at the end of a line in a multiline block', async () => {
1005
- const text = 'first line\nsecond line';
1006
- mockBuffer.text = text;
1007
- mockBuffer.lines = text.split('\n');
1008
- mockBuffer.viewportVisualLines = text.split('\n');
1009
- mockBuffer.visualCursor = [0, 10]; // cursor after 'first line'
1010
- mockBuffer.visualToLogicalMap = [
1011
- [0, 0],
1012
- [1, 0],
1013
- ];
1014
- const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1015
- await wait();
1016
- const frame = stdout.lastFrame();
1017
- expect(frame).toContain(`first line${chalk.inverse(' ')}`);
1018
- unmount();
764
+ describe('single-line scenarios', () => {
765
+ it.each([
766
+ {
767
+ name: 'mid-word',
768
+ text: 'hello world',
769
+ visualCursor: [0, 3],
770
+ expected: `hel${chalk.inverse('l')}o world`,
771
+ },
772
+ {
773
+ name: 'at the beginning of the line',
774
+ text: 'hello',
775
+ visualCursor: [0, 0],
776
+ expected: `${chalk.inverse('h')}ello`,
777
+ },
778
+ {
779
+ name: 'at the end of the line',
780
+ text: 'hello',
781
+ visualCursor: [0, 5],
782
+ expected: `hello${chalk.inverse(' ')}`,
783
+ },
784
+ {
785
+ name: 'on a highlighted token',
786
+ text: 'run @path/to/file',
787
+ visualCursor: [0, 9],
788
+ expected: `@path/${chalk.inverse('t')}o/file`,
789
+ },
790
+ {
791
+ name: 'for multi-byte unicode characters',
792
+ text: 'hello 👍 world',
793
+ visualCursor: [0, 6],
794
+ expected: `hello ${chalk.inverse('👍')} world`,
795
+ },
796
+ {
797
+ name: 'at the end of a line with unicode characters',
798
+ text: 'hello 👍',
799
+ visualCursor: [0, 8],
800
+ expected: `hello 👍${chalk.inverse(' ')}`,
801
+ },
802
+ {
803
+ name: 'on an empty line',
804
+ text: '',
805
+ visualCursor: [0, 0],
806
+ expected: chalk.inverse(' '),
807
+ },
808
+ {
809
+ name: 'on a space between words',
810
+ text: 'hello world',
811
+ visualCursor: [0, 5],
812
+ expected: `hello${chalk.inverse(' ')}world`,
813
+ },
814
+ ])('should display cursor correctly $name', async ({ text, visualCursor, expected }) => {
815
+ mockBuffer.text = text;
816
+ mockBuffer.lines = [text];
817
+ mockBuffer.viewportVisualLines = [text];
818
+ mockBuffer.visualCursor = visualCursor;
819
+ const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
820
+ await wait();
821
+ const frame = stdout.lastFrame();
822
+ expect(frame).toContain(expected);
823
+ unmount();
824
+ });
1019
825
  });
1020
- it('should display cursor on a blank line in a multiline block', async () => {
1021
- const text = 'first line\n\nthird line';
1022
- mockBuffer.text = text;
1023
- mockBuffer.lines = text.split('\n');
1024
- mockBuffer.viewportVisualLines = text.split('\n');
1025
- mockBuffer.visualCursor = [1, 0]; // cursor on the blank line
1026
- mockBuffer.visualToLogicalMap = [
1027
- [0, 0],
1028
- [1, 0],
1029
- [2, 0],
1030
- ];
1031
- const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1032
- await wait();
1033
- const frame = stdout.lastFrame();
1034
- const lines = frame.split('\n');
1035
- // The line with the cursor should just be an inverted space inside the box border
1036
- expect(lines.find((l) => l.includes(chalk.inverse(' ')))).not.toBeUndefined();
1037
- unmount();
826
+ describe('multi-line scenarios', () => {
827
+ it.each([
828
+ {
829
+ name: 'in the middle of a line',
830
+ text: 'first line\nsecond line\nthird line',
831
+ visualCursor: [1, 3],
832
+ visualToLogicalMap: [
833
+ [0, 0],
834
+ [1, 0],
835
+ [2, 0],
836
+ ],
837
+ expected: `sec${chalk.inverse('o')}nd line`,
838
+ },
839
+ {
840
+ name: 'at the beginning of a line',
841
+ text: 'first line\nsecond line',
842
+ visualCursor: [1, 0],
843
+ visualToLogicalMap: [
844
+ [0, 0],
845
+ [1, 0],
846
+ ],
847
+ expected: `${chalk.inverse('s')}econd line`,
848
+ },
849
+ {
850
+ name: 'at the end of a line',
851
+ text: 'first line\nsecond line',
852
+ visualCursor: [0, 10],
853
+ visualToLogicalMap: [
854
+ [0, 0],
855
+ [1, 0],
856
+ ],
857
+ expected: `first line${chalk.inverse(' ')}`,
858
+ },
859
+ ])('should display cursor correctly $name in a multiline block', async ({ text, visualCursor, expected, visualToLogicalMap }) => {
860
+ mockBuffer.text = text;
861
+ mockBuffer.lines = text.split('\n');
862
+ mockBuffer.viewportVisualLines = text.split('\n');
863
+ mockBuffer.visualCursor = visualCursor;
864
+ mockBuffer.visualToLogicalMap = visualToLogicalMap;
865
+ const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
866
+ await wait();
867
+ const frame = stdout.lastFrame();
868
+ expect(frame).toContain(expected);
869
+ unmount();
870
+ });
871
+ it('should display cursor on a blank line in a multiline block', async () => {
872
+ const text = 'first line\n\nthird line';
873
+ mockBuffer.text = text;
874
+ mockBuffer.lines = text.split('\n');
875
+ mockBuffer.viewportVisualLines = text.split('\n');
876
+ mockBuffer.visualCursor = [1, 0]; // cursor on the blank line
877
+ mockBuffer.visualToLogicalMap = [
878
+ [0, 0],
879
+ [1, 0],
880
+ [2, 0],
881
+ ];
882
+ const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
883
+ await wait();
884
+ const frame = stdout.lastFrame();
885
+ const lines = frame.split('\n');
886
+ // The line with the cursor should just be an inverted space inside the box border
887
+ expect(lines.find((l) => l.includes(chalk.inverse(' ')))).not.toBeUndefined();
888
+ unmount();
889
+ });
1038
890
  });
1039
891
  });
1040
892
  describe('multiline rendering', () => {
@@ -1111,8 +963,9 @@ describe('InputPrompt', () => {
1111
963
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1112
964
  await vi.runAllTimersAsync();
1113
965
  // Simulate a paste operation (this should set the paste protection)
1114
- stdin.write(`\x1b[200~pasted content\x1b[201~`);
1115
- await vi.runAllTimersAsync();
966
+ act(() => {
967
+ stdin.write(`\x1b[200~pasted content\x1b[201~`);
968
+ });
1116
969
  // Simulate an Enter key press immediately after paste
1117
970
  stdin.write('\r');
1118
971
  await vi.runAllTimersAsync();
@@ -1456,7 +1309,7 @@ describe('InputPrompt', () => {
1456
1309
  expect(frame).toContain('git push');
1457
1310
  unmount();
1458
1311
  });
1459
- it.skip('expands and collapses long suggestion via Right/Left arrows', async () => {
1312
+ it('expands and collapses long suggestion via Right/Left arrows', async () => {
1460
1313
  props.shellModeActive = false;
1461
1314
  const longValue = 'l'.repeat(200);
1462
1315
  vi.mocked(useReverseSearchCompletion).mockReturnValue({
@@ -1684,48 +1537,51 @@ describe('InputPrompt', () => {
1684
1537
  expect(mockBuffer.handleInput).toHaveBeenCalled();
1685
1538
  unmount();
1686
1539
  });
1687
- it('should prevent slash commands from being queued while streaming', async () => {
1688
- props.onSubmit = vi.fn();
1689
- props.buffer.text = '/help';
1690
- props.setQueueErrorMessage = vi.fn();
1691
- props.streamingState = StreamingState.Responding;
1692
- const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1693
- await wait();
1694
- stdin.write('/help');
1695
- stdin.write('\r');
1696
- await wait();
1697
- expect(props.onSubmit).not.toHaveBeenCalled();
1698
- expect(props.setQueueErrorMessage).toHaveBeenCalledWith('Slash commands cannot be queued');
1699
- unmount();
1700
- });
1701
- it('should prevent shell commands from being queued while streaming', async () => {
1702
- props.onSubmit = vi.fn();
1703
- props.buffer.text = 'ls';
1704
- props.setQueueErrorMessage = vi.fn();
1705
- props.streamingState = StreamingState.Responding;
1706
- props.shellModeActive = true;
1707
- const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1708
- await wait();
1709
- stdin.write('ls');
1710
- stdin.write('\r');
1711
- await wait();
1712
- expect(props.onSubmit).not.toHaveBeenCalled();
1713
- expect(props.setQueueErrorMessage).toHaveBeenCalledWith('Shell commands cannot be queued');
1714
- unmount();
1715
- });
1716
- it('should allow regular messages to be queued while streaming', async () => {
1717
- props.onSubmit = vi.fn();
1718
- props.buffer.text = 'regular message';
1719
- props.setQueueErrorMessage = vi.fn();
1720
- props.streamingState = StreamingState.Responding;
1721
- const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1722
- await wait();
1723
- stdin.write('regular message');
1724
- stdin.write('\r');
1725
- await wait();
1726
- expect(props.onSubmit).toHaveBeenCalledWith('regular message');
1727
- expect(props.setQueueErrorMessage).not.toHaveBeenCalled();
1728
- unmount();
1540
+ describe('command queuing while streaming', () => {
1541
+ beforeEach(() => {
1542
+ props.streamingState = StreamingState.Responding;
1543
+ props.setQueueErrorMessage = vi.fn();
1544
+ props.onSubmit = vi.fn();
1545
+ });
1546
+ it.each([
1547
+ {
1548
+ name: 'should prevent slash commands',
1549
+ bufferText: '/help',
1550
+ shellMode: false,
1551
+ shouldSubmit: false,
1552
+ errorMessage: 'Slash commands cannot be queued',
1553
+ },
1554
+ {
1555
+ name: 'should prevent shell commands',
1556
+ bufferText: 'ls',
1557
+ shellMode: true,
1558
+ shouldSubmit: false,
1559
+ errorMessage: 'Shell commands cannot be queued',
1560
+ },
1561
+ {
1562
+ name: 'should allow regular messages',
1563
+ bufferText: 'regular message',
1564
+ shellMode: false,
1565
+ shouldSubmit: true,
1566
+ errorMessage: null,
1567
+ },
1568
+ ])('$name', async ({ bufferText, shellMode, shouldSubmit, errorMessage }) => {
1569
+ props.buffer.text = bufferText;
1570
+ props.shellModeActive = shellMode;
1571
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1572
+ await wait();
1573
+ stdin.write('\r');
1574
+ await wait();
1575
+ if (shouldSubmit) {
1576
+ expect(props.onSubmit).toHaveBeenCalledWith(bufferText);
1577
+ expect(props.setQueueErrorMessage).not.toHaveBeenCalled();
1578
+ }
1579
+ else {
1580
+ expect(props.onSubmit).not.toHaveBeenCalled();
1581
+ expect(props.setQueueErrorMessage).toHaveBeenCalledWith(errorMessage);
1582
+ }
1583
+ unmount();
1584
+ });
1729
1585
  });
1730
1586
  });
1731
1587
  function clean(str) {