mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-19 18:40:57 -07:00
[Extension Reloading]: Update custom commands, add enable/disable command (#12547)
This commit is contained in:
@@ -26,6 +26,7 @@ import {
|
||||
ToolConfirmationOutcome,
|
||||
makeFakeConfig,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { appEvents } from '../../utils/events.js';
|
||||
|
||||
const { logSlashCommand } = vi.hoisted(() => ({
|
||||
logSlashCommand: vi.fn(),
|
||||
@@ -1076,4 +1077,26 @@ describe('useSlashCommandProcessor', () => {
|
||||
expect(logSlashCommand).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should reload commands on extension events', async () => {
|
||||
const result = await setupProcessorHook();
|
||||
await waitFor(() => expect(result.current.slashCommands).toEqual([]));
|
||||
|
||||
// Create a new command and make that the result of the fileLoadCommands
|
||||
// (which is where extension commands come from)
|
||||
const newCommand = createTestCommand({
|
||||
name: 'someNewCommand',
|
||||
action: vi.fn(),
|
||||
});
|
||||
mockFileLoadCommands.mockResolvedValue([newCommand]);
|
||||
|
||||
// We should not see a change until we fire an event.
|
||||
await waitFor(() => expect(result.current.slashCommands).toEqual([]));
|
||||
await act(() => {
|
||||
appEvents.emit('extensionsStarting');
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(result.current.slashCommands).toEqual([newCommand]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,7 +8,11 @@ import { useCallback, useMemo, useEffect, useState } from 'react';
|
||||
import { type PartListUnion } from '@google/genai';
|
||||
import process from 'node:process';
|
||||
import type { UseHistoryManagerReturn } from './useHistoryManager.js';
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
import type {
|
||||
Config,
|
||||
ExtensionsStartingEvent,
|
||||
ExtensionsStoppingEvent,
|
||||
} from '@google/gemini-cli-core';
|
||||
import {
|
||||
GitService,
|
||||
Logger,
|
||||
@@ -39,6 +43,7 @@ import {
|
||||
type ExtensionUpdateAction,
|
||||
type ExtensionUpdateStatus,
|
||||
} from '../state/extensions.js';
|
||||
import { appEvents } from '../../utils/events.js';
|
||||
|
||||
interface SlashCommandProcessorActions {
|
||||
openAuthDialog: () => void;
|
||||
@@ -249,11 +254,27 @@ export const useSlashCommandProcessor = (
|
||||
ideClient.addStatusChangeListener(listener);
|
||||
})();
|
||||
|
||||
// TODO: Ideally this would happen more directly inside the ExtensionLoader,
|
||||
// but the CommandService today is not conducive to that since it isn't a
|
||||
// long lived service but instead gets fully re-created based on reload
|
||||
// events within this hook.
|
||||
const extensionEventListener = (
|
||||
_event: ExtensionsStartingEvent | ExtensionsStoppingEvent,
|
||||
) => {
|
||||
// We only care once at least one extension has completed
|
||||
// starting/stopping
|
||||
reloadCommands();
|
||||
};
|
||||
appEvents.on('extensionsStarting', extensionEventListener);
|
||||
appEvents.on('extensionsStopping', extensionEventListener);
|
||||
|
||||
return () => {
|
||||
(async () => {
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
ideClient.removeStatusChangeListener(listener);
|
||||
})();
|
||||
appEvents.off('extensionsStarting', extensionEventListener);
|
||||
appEvents.off('extensionsStopping', extensionEventListener);
|
||||
};
|
||||
}, [config, reloadCommands]);
|
||||
|
||||
|
||||
@@ -16,7 +16,10 @@ import {
|
||||
import { act } from 'react';
|
||||
import { render } from '../../test-utils/render.js';
|
||||
import { useEditorSettings } from './useEditorSettings.js';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
import type {
|
||||
LoadableSettingScope,
|
||||
LoadedSettings,
|
||||
} from '../../config/settings.js';
|
||||
import { SettingScope } from '../../config/settings.js';
|
||||
import { MessageType, type HistoryItem } from '../types.js';
|
||||
import {
|
||||
@@ -186,7 +189,10 @@ describe('useEditorSettings', () => {
|
||||
render(<TestComponent />);
|
||||
|
||||
const editorType: EditorType = 'vscode';
|
||||
const scopes = [SettingScope.User, SettingScope.Workspace];
|
||||
const scopes: LoadableSettingScope[] = [
|
||||
SettingScope.User,
|
||||
SettingScope.Workspace,
|
||||
];
|
||||
|
||||
scopes.forEach((scope) => {
|
||||
act(() => {
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
*/
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import type { LoadedSettings, SettingScope } from '../../config/settings.js';
|
||||
import type {
|
||||
LoadableSettingScope,
|
||||
LoadedSettings,
|
||||
} from '../../config/settings.js';
|
||||
import { type HistoryItem, MessageType } from '../types.js';
|
||||
import type { EditorType } from '@google/gemini-cli-core';
|
||||
import {
|
||||
@@ -18,7 +21,7 @@ interface UseEditorSettingsReturn {
|
||||
openEditorDialog: () => void;
|
||||
handleEditorSelect: (
|
||||
editorType: EditorType | undefined,
|
||||
scope: SettingScope,
|
||||
scope: LoadableSettingScope,
|
||||
) => void;
|
||||
exitEditorDialog: () => void;
|
||||
}
|
||||
@@ -35,7 +38,7 @@ export const useEditorSettings = (
|
||||
}, []);
|
||||
|
||||
const handleEditorSelect = useCallback(
|
||||
(editorType: EditorType | undefined, scope: SettingScope) => {
|
||||
(editorType: EditorType | undefined, scope: LoadableSettingScope) => {
|
||||
if (
|
||||
editorType &&
|
||||
(!checkHasEditorType(editorType) ||
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import { themeManager } from '../themes/theme-manager.js';
|
||||
import type { LoadedSettings, SettingScope } from '../../config/settings.js'; // Import LoadedSettings, AppSettings, MergedSetting
|
||||
import type {
|
||||
LoadableSettingScope,
|
||||
LoadedSettings,
|
||||
} from '../../config/settings.js'; // Import LoadedSettings, AppSettings, MergedSetting
|
||||
import { type HistoryItem, MessageType } from '../types.js';
|
||||
import process from 'node:process';
|
||||
|
||||
@@ -14,7 +17,7 @@ interface UseThemeCommandReturn {
|
||||
isThemeDialogOpen: boolean;
|
||||
openThemeDialog: () => void;
|
||||
closeThemeDialog: () => void;
|
||||
handleThemeSelect: (themeName: string, scope: SettingScope) => void;
|
||||
handleThemeSelect: (themeName: string, scope: LoadableSettingScope) => void;
|
||||
handleThemeHighlight: (themeName: string | undefined) => void;
|
||||
}
|
||||
|
||||
@@ -68,7 +71,7 @@ export const useThemeCommand = (
|
||||
}, [applyTheme, loadedSettings]);
|
||||
|
||||
const handleThemeSelect = useCallback(
|
||||
(themeName: string, scope: SettingScope) => {
|
||||
(themeName: string, scope: LoadableSettingScope) => {
|
||||
try {
|
||||
// Merge user and workspace custom themes (workspace takes precedence)
|
||||
const mergedCustomThemes = {
|
||||
|
||||
Reference in New Issue
Block a user