From 8c2060a8f9f051155fd8ede3c4f401108463d838 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 19 Mar 2026 19:04:14 +0000 Subject: [PATCH 1/4] Add missing commands to CommandPalette and support Edit/Open synonyms Add 5 new commands to the command palette that were accessible from menus but not from the palette: Open preferences, Open about, Change language, Open version history, and Open debugger. Also make "Edit" and "Open" interchangeable in command search, so typing "Edit scene variables" matches "Open scene variables" and vice versa. Remove orphaned EDIT_NETWORK_PREVIEW from CommandName type. https://claude.ai/code/session_01YV81jKGWv7CV5QbrMJnSjZ --- .../CommandPalette/FilterOptions.js | 31 +++++++++++++++++++ newIDE/app/src/CommandPalette/CommandsList.js | 30 +++++++++++++++++- newIDE/app/src/MainFrame/MainFrameCommands.js | 25 +++++++++++++++ newIDE/app/src/MainFrame/index.js | 5 +++ 4 files changed, 90 insertions(+), 1 deletion(-) diff --git a/newIDE/app/src/CommandPalette/CommandPalette/FilterOptions.js b/newIDE/app/src/CommandPalette/CommandPalette/FilterOptions.js index b8ee60fbca74..eb050bf41f1e 100644 --- a/newIDE/app/src/CommandPalette/CommandPalette/FilterOptions.js +++ b/newIDE/app/src/CommandPalette/CommandPalette/FilterOptions.js @@ -1,6 +1,33 @@ // @flow import { fuzzyOrEmptyFilter } from '../../Utils/FuzzyOrEmptyFilter'; +/** + * Words that should be treated as synonyms when searching for commands. + * For example, "Edit scene variables" and "Open scene variables" should both match. + */ +const synonymGroups: Array> = [['open', 'edit']]; + +/** + * Returns an alternate version of the search text with synonyms swapped, + * or null if no synonym applies. + */ +const getSearchTextWithSynonym = (searchText: string): string | null => { + for (const group of synonymGroups) { + for (let i = 0; i < group.length; i++) { + const word = group[i]; + if (searchText.startsWith(word + ' ') || searchText === word) { + // Replace the first occurrence of the synonym with the other synonym. + for (let j = 0; j < group.length; j++) { + if (i !== j) { + return group[j] + searchText.slice(word.length); + } + } + } + } + } + return null; +}; + /** * Filters options both simply and fuzzy-ly, * prioritizing simple-matched options @@ -12,12 +39,16 @@ const filterOptions = ( const searchText = state.inputValue.toLowerCase(); if (searchText === '') return options; + const synonymSearchText = getSearchTextWithSynonym(searchText); + const directMatches = []; const fuzzyMatches = []; options.forEach(option => { if (option.hit) return directMatches.push(option); const optionText = state.getOptionLabel(option).toLowerCase(); if (optionText.includes(searchText)) return directMatches.push(option); + if (synonymSearchText && optionText.includes(synonymSearchText)) + return directMatches.push(option); if (fuzzyOrEmptyFilter(searchText, optionText)) return fuzzyMatches.push(option); }); diff --git a/newIDE/app/src/CommandPalette/CommandsList.js b/newIDE/app/src/CommandPalette/CommandsList.js index 50ba8a7f16cb..1c4619a54ff0 100644 --- a/newIDE/app/src/CommandPalette/CommandsList.js +++ b/newIDE/app/src/CommandPalette/CommandsList.js @@ -48,7 +48,6 @@ export type CommandName = | 'OPEN_SETUP_GRID' | 'EDIT_LAYER_EFFECTS' | 'EDIT_LAYER' - | 'EDIT_NETWORK_PREVIEW' | 'EDIT_OBJECT' | 'EDIT_OBJECT_BEHAVIORS' | 'EDIT_OBJECT_EFFECTS' @@ -68,6 +67,11 @@ export type CommandName = | 'SEARCH_EVENTS' | 'OPEN_EXTENSION_SETTINGS' | 'OPEN_PROFILE' + | 'OPEN_PREFERENCES' + | 'OPEN_ABOUT' + | 'OPEN_LANGUAGE' + | 'OPEN_VERSION_HISTORY' + | 'OPEN_DEBUGGER' | 'OPEN_MEMORY_TRACKER_REGISTRY'; export const commandAreas = { @@ -350,6 +354,30 @@ const commandsList: { [CommandName]: CommandMetadata } = { displayText: t`Open extension settings`, }, + // IDE commands + OPEN_PREFERENCES: { + area: 'IDE', + displayText: t`Open preferences`, + }, + OPEN_ABOUT: { + area: 'IDE', + displayText: t`Open about GDevelop`, + }, + OPEN_LANGUAGE: { + area: 'IDE', + displayText: t`Change language`, + }, + + // Project commands + OPEN_VERSION_HISTORY: { + area: 'PROJECT', + displayText: t`Open version history`, + }, + OPEN_DEBUGGER: { + area: 'PROJECT', + displayText: t`Open debugger`, + }, + // Debug commands OPEN_MEMORY_TRACKER_REGISTRY: { area: 'IDE', diff --git a/newIDE/app/src/MainFrame/MainFrameCommands.js b/newIDE/app/src/MainFrame/MainFrameCommands.js index 72cff6558801..efac1ecca022 100644 --- a/newIDE/app/src/MainFrame/MainFrameCommands.js +++ b/newIDE/app/src/MainFrame/MainFrameCommands.js @@ -69,6 +69,11 @@ type CommandHandlers = {| onRestartInGameEditor: (reason: string) => void, onOpenGlobalSearch: () => void, onOpenMemoryTrackerRegistry: () => void, + onOpenPreferences: () => void, + onOpenAbout: () => void, + onOpenLanguage: () => void, + onOpenVersionHistory: () => void, + onOpenDebugger: () => void, |}; const useMainFrameCommands = (handlers: CommandHandlers) => { @@ -174,6 +179,26 @@ const useMainFrameCommands = (handlers: CommandHandlers) => { ), }); + useCommand('OPEN_PREFERENCES', true, { + handler: handlers.onOpenPreferences, + }); + + useCommand('OPEN_ABOUT', true, { + handler: handlers.onOpenAbout, + }); + + useCommand('OPEN_LANGUAGE', true, { + handler: handlers.onOpenLanguage, + }); + + useCommand('OPEN_VERSION_HISTORY', !!handlers.project, { + handler: handlers.onOpenVersionHistory, + }); + + useCommand('OPEN_DEBUGGER', !!handlers.project, { + handler: handlers.onOpenDebugger, + }); + useCommand('OPEN_MEMORY_TRACKER_REGISTRY', true, { handler: handlers.onOpenMemoryTrackerRegistry, }); diff --git a/newIDE/app/src/MainFrame/index.js b/newIDE/app/src/MainFrame/index.js index 89fa784be712..d6a9aa8e0aff 100644 --- a/newIDE/app/src/MainFrame/index.js +++ b/newIDE/app/src/MainFrame/index.js @@ -4815,6 +4815,11 @@ const MainFrame = (props: Props): React.MixedElement => { onRestartInGameEditor, onOpenGlobalSearch: openGlobalSearch, onOpenMemoryTrackerRegistry: () => setMemoryTrackedRegistryDialogOpen(true), + onOpenPreferences: () => openPreferencesDialog(true), + onOpenAbout: () => openAboutDialog(true), + onOpenLanguage: () => openLanguageDialog(true), + onOpenVersionHistory: openVersionHistoryPanel, + onOpenDebugger: openDebugger, }); const resourceManagementProps: ResourceManagementProps = React.useMemo( From 302a297b720962672b2aad0ccc99995578b50e00 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 19 Mar 2026 19:07:58 +0000 Subject: [PATCH 2/4] Rename "Open about GDevelop" command to include version hint https://claude.ai/code/session_01YV81jKGWv7CV5QbrMJnSjZ --- newIDE/app/src/CommandPalette/CommandsList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/newIDE/app/src/CommandPalette/CommandsList.js b/newIDE/app/src/CommandPalette/CommandsList.js index 1c4619a54ff0..8251d30b38fd 100644 --- a/newIDE/app/src/CommandPalette/CommandsList.js +++ b/newIDE/app/src/CommandPalette/CommandsList.js @@ -361,7 +361,7 @@ const commandsList: { [CommandName]: CommandMetadata } = { }, OPEN_ABOUT: { area: 'IDE', - displayText: t`Open about GDevelop`, + displayText: t`Open "About GDevelop" (version)`, }, OPEN_LANGUAGE: { area: 'IDE', From 45721bdfa31257e83db2829a3f637d5d855c810a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 19 Mar 2026 19:18:09 +0000 Subject: [PATCH 3/4] Make command palette synonyms translatable with i18n The synonym groups (e.g. "open"/"edit") are now wrapped with lingui's t`` so they get translated per locale, enabling search in any language. https://claude.ai/code/session_01YV81jKGWv7CV5QbrMJnSjZ --- .../app/src/CommandPalette/CommandPalette/FilterOptions.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/newIDE/app/src/CommandPalette/CommandPalette/FilterOptions.js b/newIDE/app/src/CommandPalette/CommandPalette/FilterOptions.js index eb050bf41f1e..8372f3697b17 100644 --- a/newIDE/app/src/CommandPalette/CommandPalette/FilterOptions.js +++ b/newIDE/app/src/CommandPalette/CommandPalette/FilterOptions.js @@ -1,17 +1,22 @@ // @flow +import { t } from '@lingui/macro'; import { fuzzyOrEmptyFilter } from '../../Utils/FuzzyOrEmptyFilter'; /** * Words that should be treated as synonyms when searching for commands. * For example, "Edit scene variables" and "Open scene variables" should both match. + * Uses i18n so that synonyms work in all languages. */ -const synonymGroups: Array> = [['open', 'edit']]; +const getSynonymGroups = (): Array> => [ + [t`open`, t`edit`], +]; /** * Returns an alternate version of the search text with synonyms swapped, * or null if no synonym applies. */ const getSearchTextWithSynonym = (searchText: string): string | null => { + const synonymGroups = getSynonymGroups(); for (const group of synonymGroups) { for (let i = 0; i < group.length; i++) { const word = group[i]; From 4c754896663ce1cf14ba82dbc376a562198e2013 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 19 Mar 2026 19:28:02 +0000 Subject: [PATCH 4/4] Fix synonym i18n: use i18n._() to resolve message descriptors to strings The `t` macro returns a MessageDescriptor, not a string. Changed filterOptions to a factory (makeFilterOptions) that takes an i18n instance, so synonyms are properly resolved to translated strings. https://claude.ai/code/session_01YV81jKGWv7CV5QbrMJnSjZ --- .../CommandPalette/AutocompletePicker.js | 6 +- .../CommandPalette/FilterOptions.js | 60 +++++++++++-------- 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/newIDE/app/src/CommandPalette/CommandPalette/AutocompletePicker.js b/newIDE/app/src/CommandPalette/CommandPalette/AutocompletePicker.js index a478f1cceab0..abedec214aa1 100644 --- a/newIDE/app/src/CommandPalette/CommandPalette/AutocompletePicker.js +++ b/newIDE/app/src/CommandPalette/CommandPalette/AutocompletePicker.js @@ -13,7 +13,7 @@ import Chip from '../../UI/Chip'; import TextField from '@material-ui/core/TextField'; import ChevronRightIcon from '../../UI/CustomSvgIcons/ChevronArrowRight'; import Autocomplete from '@material-ui/lab/Autocomplete'; -import filterOptions from './FilterOptions'; +import makeFilterOptions from './FilterOptions'; import { type NamedCommand, type CommandOption, @@ -134,6 +134,10 @@ const AutocompletePicker = ( const [open, setOpen] = React.useState(true); const shortcutMap = useShortcutMap(); const classes = useStyles(); + const filterOptions = React.useMemo( + () => makeFilterOptions(props.i18n), + [props.i18n] + ); // $FlowFixMe[missing-local-annot] const handleClose = (_, reason) => { diff --git a/newIDE/app/src/CommandPalette/CommandPalette/FilterOptions.js b/newIDE/app/src/CommandPalette/CommandPalette/FilterOptions.js index 8372f3697b17..1716c84ec422 100644 --- a/newIDE/app/src/CommandPalette/CommandPalette/FilterOptions.js +++ b/newIDE/app/src/CommandPalette/CommandPalette/FilterOptions.js @@ -1,5 +1,6 @@ // @flow import { t } from '@lingui/macro'; +import { type I18n as I18nType } from '@lingui/core'; import { fuzzyOrEmptyFilter } from '../../Utils/FuzzyOrEmptyFilter'; /** @@ -7,16 +8,19 @@ import { fuzzyOrEmptyFilter } from '../../Utils/FuzzyOrEmptyFilter'; * For example, "Edit scene variables" and "Open scene variables" should both match. * Uses i18n so that synonyms work in all languages. */ -const getSynonymGroups = (): Array> => [ - [t`open`, t`edit`], +const getSynonymGroups = (i18n: I18nType): Array> => [ + [i18n._(t`open`), i18n._(t`edit`)], ]; /** * Returns an alternate version of the search text with synonyms swapped, * or null if no synonym applies. */ -const getSearchTextWithSynonym = (searchText: string): string | null => { - const synonymGroups = getSynonymGroups(); +const getSearchTextWithSynonym = ( + searchText: string, + i18n: I18nType +): string | null => { + const synonymGroups = getSynonymGroups(i18n); for (const group of synonymGroups) { for (let i = 0; i < group.length; i++) { const word = group[i]; @@ -34,31 +38,39 @@ const getSearchTextWithSynonym = (searchText: string): string | null => { }; /** - * Filters options both simply and fuzzy-ly, - * prioritizing simple-matched options + * Creates a filter function that filters options both simply and fuzzy-ly, + * prioritizing simple-matched options. + * Accepts an i18n instance so that synonym groups can be translated. */ -const filterOptions = ( +const makeFilterOptions = ( + i18n: I18nType +): (( options: Array, state: { getOptionLabel: T => string, inputValue: string } -): any => { - const searchText = state.inputValue.toLowerCase(); - if (searchText === '') return options; +) => any) => { + return ( + options: Array, + state: { getOptionLabel: T => string, inputValue: string } + ): any => { + const searchText = state.inputValue.toLowerCase(); + if (searchText === '') return options; - const synonymSearchText = getSearchTextWithSynonym(searchText); + const synonymSearchText = getSearchTextWithSynonym(searchText, i18n); - const directMatches = []; - const fuzzyMatches = []; - options.forEach(option => { - if (option.hit) return directMatches.push(option); - const optionText = state.getOptionLabel(option).toLowerCase(); - if (optionText.includes(searchText)) return directMatches.push(option); - if (synonymSearchText && optionText.includes(synonymSearchText)) - return directMatches.push(option); - if (fuzzyOrEmptyFilter(searchText, optionText)) - return fuzzyMatches.push(option); - }); + const directMatches = []; + const fuzzyMatches = []; + options.forEach(option => { + if (option.hit) return directMatches.push(option); + const optionText = state.getOptionLabel(option).toLowerCase(); + if (optionText.includes(searchText)) return directMatches.push(option); + if (synonymSearchText && optionText.includes(synonymSearchText)) + return directMatches.push(option); + if (fuzzyOrEmptyFilter(searchText, optionText)) + return fuzzyMatches.push(option); + }); - return [...directMatches, ...fuzzyMatches]; + return [...directMatches, ...fuzzyMatches]; + }; }; -export default filterOptions; +export default makeFilterOptions;