From 1ef3e26e2d859b1fc41f14421a0566e6e72377b8 Mon Sep 17 00:00:00 2001 From: Dieter Holvoet Date: Mon, 28 Apr 2025 14:05:04 +0200 Subject: [PATCH 1/6] Use the search input for select lists with lots of options --- src-symfony-compatibility/v6/Style/DrushStyle.php | 14 ++++++++++---- src/Commands/core/CacheCommands.php | 4 ++-- src/Commands/core/ImageCommands.php | 2 +- src/Commands/core/SiteInstallCommands.php | 2 +- src/Commands/core/TopicCommands.php | 2 +- src/Commands/field/EntityTypeBundleAskTrait.php | 4 ++-- .../field/FieldBaseOverrideCreateCommands.php | 2 +- src/Commands/field/FieldCreateCommands.php | 6 +++--- src/Commands/field/FieldEntityReferenceHooks.php | 4 ++-- src/Commands/field/FieldTextHooks.php | 4 ++-- 10 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src-symfony-compatibility/v6/Style/DrushStyle.php b/src-symfony-compatibility/v6/Style/DrushStyle.php index 2efbabf814..3c7b6b8a44 100644 --- a/src-symfony-compatibility/v6/Style/DrushStyle.php +++ b/src-symfony-compatibility/v6/Style/DrushStyle.php @@ -108,22 +108,28 @@ public function suggest(string $label, array|\Closure $options, string $placehol /** * Allow the user to search for an option. * - * @param \Closure(string): array $options + * @param \Closure(string): array|array $options * @param true|string $required */ - public function search(string $label, \Closure $options, string $placeholder = '', int $scroll = 10, ?\Closure $validate = null, string $hint = '', bool|string $required = true): int|string + public function search(string $label, \Closure|array $options, string $placeholder = '', int $scroll = 10, ?\Closure $validate = null, string $hint = '', bool|string $required = true): int|string { + if (is_array($options)) { + $options = fn (string $query) => array_filter($options, fn ($option) => str_contains(strtolower($option), strtolower($query))); + } return (new SearchPrompt($label, $options, $placeholder, $scroll, $validate, $hint, $required))->prompt(); } /** * Allow the user to search for multiple option. * - * @param \Closure(string): array $options + * @param \Closure(string): array|array $options * @return array */ - public function multisearch(string $label, \Closure $options, string $placeholder = '', int $scroll = 10, bool|string $required = false, ?\Closure $validate = null, string $hint = 'Use the space bar to select options.'): array + public function multisearch(string $label, \Closure|array $options, string $placeholder = '', int $scroll = 10, bool|string $required = false, ?\Closure $validate = null, string $hint = 'Use the space bar to select options.'): array { + if (is_array($options)) { + $options = fn (string $query) => array_filter($options, fn ($option) => str_contains(strtolower($option), strtolower($query))); + } return (new MultiSearchPrompt($label, $options, $placeholder, $scroll, $required, $validate, $hint))->prompt(); } diff --git a/src/Commands/core/CacheCommands.php b/src/Commands/core/CacheCommands.php index 4c62ff5dba..418080c4a0 100644 --- a/src/Commands/core/CacheCommands.php +++ b/src/Commands/core/CacheCommands.php @@ -131,14 +131,14 @@ public function interact($input, $output): void if (empty($input->getArgument('type'))) { $types = $this->getTypes($this->bootstrapManager->hasBootstrapped(DrupalBootLevels::FULL)); $choices = array_combine(array_keys($types), array_keys($types)); - $type = $this->io()->select(dt("Choose a cache to clear"), $choices, 'render', scroll: 20); + $type = $this->io()->search(dt("Choose a cache to clear"), $choices, 'render', scroll: 20); $input->setArgument('type', $type); } if ($input->getArgument('type') == 'bin' && empty($input->getArgument('args'))) { $bins = Cache::getBins(); $choices = array_combine(array_keys($bins), array_keys($bins)); - $chosen = $this->io()->select(dt("Choose a cache to clear"), $choices, 'default', scroll: 20); + $chosen = $this->io()->search(dt("Choose a cache to clear"), $choices, 'default', scroll: 20); $input->setArgument('args', [$chosen]); } } diff --git a/src/Commands/core/ImageCommands.php b/src/Commands/core/ImageCommands.php index 87f7636594..b7499b8448 100644 --- a/src/Commands/core/ImageCommands.php +++ b/src/Commands/core/ImageCommands.php @@ -48,7 +48,7 @@ public function interactFlush(InputInterface $input, $output): void $styles_all = $styles; array_unshift($styles_all, 'all'); $choices = array_combine($styles_all, $styles_all); - $style_names = $this->io()->select(dt("Choose a style to flush"), $choices, 'all', scroll: 20); + $style_names = $this->io()->search(dt("Choose a style to flush"), $choices, 'all', scroll: 20); if ($style_names == 'all') { $style_names = implode(',', $styles); } diff --git a/src/Commands/core/SiteInstallCommands.php b/src/Commands/core/SiteInstallCommands.php index ef15138531..a3ad138468 100644 --- a/src/Commands/core/SiteInstallCommands.php +++ b/src/Commands/core/SiteInstallCommands.php @@ -366,7 +366,7 @@ public function validate(CommandData $commandData): void } // Ask questions to get our data. - $driverNamespace = $this->io()->select('Select the database driver', $driverSelectOptions); + $driverNamespace = $this->io()->search('Select the database driver', $driverSelectOptions); $formOptions = $driverList[$driverNamespace]->getInstallTasks()->getFormOptions([]); $databaseInfo = [ 'driver' => $driverList[$driverNamespace]->getDriverName(), diff --git a/src/Commands/core/TopicCommands.php b/src/Commands/core/TopicCommands.php index ad2de0278a..38bb9a2e56 100644 --- a/src/Commands/core/TopicCommands.php +++ b/src/Commands/core/TopicCommands.php @@ -60,7 +60,7 @@ public function interact(InputInterface $input, OutputInterface $output): void $choices[$key] = $topic->getDescription() . " ($key)"; } natcasesort($choices); - $topic_name = $this->io()->select(dt('Choose a topic'), $choices, scroll: 30); + $topic_name = $this->io()->search(dt('Choose a topic'), $choices, scroll: 30); $input->setArgument('topic_name', $topic_name); } } diff --git a/src/Commands/field/EntityTypeBundleAskTrait.php b/src/Commands/field/EntityTypeBundleAskTrait.php index ba973fa858..67a40034d8 100644 --- a/src/Commands/field/EntityTypeBundleAskTrait.php +++ b/src/Commands/field/EntityTypeBundleAskTrait.php @@ -40,7 +40,7 @@ protected function askEntityType(): ?string : $entityTypeDefinition->getLabel(); } - if (!$answer = $this->io()->select('Entity type', $choices, required: true)) { + if (!$answer = $this->io()->search('Entity type', $choices, required: true)) { throw new \InvalidArgumentException(dt('The entityType argument is required.')); } @@ -71,7 +71,7 @@ protected function askBundle(): ?string $choices[$bundle] = $label; } - if (!$answer = $this->io()->select('Bundle', $choices)) { + if (!$answer = $this->io()->search('Bundle', $choices)) { throw new \InvalidArgumentException(dt('The bundle argument is required.')); } diff --git a/src/Commands/field/FieldBaseOverrideCreateCommands.php b/src/Commands/field/FieldBaseOverrideCreateCommands.php index 3106d1198e..9cf33d9edb 100644 --- a/src/Commands/field/FieldBaseOverrideCreateCommands.php +++ b/src/Commands/field/FieldBaseOverrideCreateCommands.php @@ -146,7 +146,7 @@ protected function askFieldName(string $entityType): ?string $choices[$definition->getName()] = $label; } - return $this->io()->select('Field name', $choices); + return $this->io()->search('Field name', $choices); } protected function askFieldLabel(string $default): string diff --git a/src/Commands/field/FieldCreateCommands.php b/src/Commands/field/FieldCreateCommands.php index cfc98de37e..e14cb5164e 100644 --- a/src/Commands/field/FieldCreateCommands.php +++ b/src/Commands/field/FieldCreateCommands.php @@ -264,7 +264,7 @@ protected function askExistingFieldName(): ?string return null; } - return $this->io()->select('Choose an existing field', $choices); + return $this->io()->search('Choose an existing field', $choices); } protected function askFieldName(): string @@ -324,7 +324,7 @@ protected function askFieldType(): string $choices[$definition['id']] = $label; } - return $this->io()->select('Field type', $choices, scroll: 25); + return $this->io()->search('Field type', $choices, scroll: 25); } protected function askFieldWidget(): ?string @@ -355,7 +355,7 @@ protected function askFieldWidget(): ?string $default = $this->input->getOption('show-machine-names') ? key($choices) : current($choices); - return $this->io()->select('Field widget', $choices, $default); + return $this->io()->search('Field widget', $choices, $default); } protected function askRequired(): bool diff --git a/src/Commands/field/FieldEntityReferenceHooks.php b/src/Commands/field/FieldEntityReferenceHooks.php index 83c811804e..2aaef6d3c9 100644 --- a/src/Commands/field/FieldEntityReferenceHooks.php +++ b/src/Commands/field/FieldEntityReferenceHooks.php @@ -97,7 +97,7 @@ protected function askReferencedEntityType(): string $choices[$name] = $label; } - return $this->io()->select('Referenced entity type', $choices); + return $this->io()->search('Referenced entity type', $choices); } protected function askReferencedBundles(string $targetType): ?array @@ -114,6 +114,6 @@ protected function askReferencedBundles(string $targetType): ?array $choices[$bundle] = $label; } - return $this->io()->multiselect('Referenced bundles', $choices); + return $this->io()->multisearch('Referenced bundles', $choices); } } diff --git a/src/Commands/field/FieldTextHooks.php b/src/Commands/field/FieldTextHooks.php index 6352d5dd33..96b376338f 100644 --- a/src/Commands/field/FieldTextHooks.php +++ b/src/Commands/field/FieldTextHooks.php @@ -108,12 +108,12 @@ protected function hasAllowedFormats(?string $fieldType = null): bool protected function askAllowedFormats(): array { $formats = filter_formats(); - $choices = ['_none' => '- None -']; + $choices = []; foreach ($formats as $format) { $choices[$format->id()] = $format->label(); } - return $this->io()->multiselect('Allowed formats', $choices, ['_none']); + return $this->io()->multisearch('Allowed formats', $choices); } } From e1b961e21108659532bffc4098f557bd857f2107 Mon Sep 17 00:00:00 2001 From: Dieter Holvoet Date: Mon, 28 Apr 2025 14:22:45 +0200 Subject: [PATCH 2/6] Deal with null values --- src-symfony-compatibility/v6/Style/DrushStyle.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src-symfony-compatibility/v6/Style/DrushStyle.php b/src-symfony-compatibility/v6/Style/DrushStyle.php index 3c7b6b8a44..5dd7d6e9ae 100644 --- a/src-symfony-compatibility/v6/Style/DrushStyle.php +++ b/src-symfony-compatibility/v6/Style/DrushStyle.php @@ -114,7 +114,7 @@ public function suggest(string $label, array|\Closure $options, string $placehol public function search(string $label, \Closure|array $options, string $placeholder = '', int $scroll = 10, ?\Closure $validate = null, string $hint = '', bool|string $required = true): int|string { if (is_array($options)) { - $options = fn (string $query) => array_filter($options, fn ($option) => str_contains(strtolower($option), strtolower($query))); + $options = fn (?string $query) => array_filter($options, fn ($option) => str_contains(strtolower($option), strtolower((string) $query))); } return (new SearchPrompt($label, $options, $placeholder, $scroll, $validate, $hint, $required))->prompt(); } @@ -128,7 +128,7 @@ public function search(string $label, \Closure|array $options, string $placehold public function multisearch(string $label, \Closure|array $options, string $placeholder = '', int $scroll = 10, bool|string $required = false, ?\Closure $validate = null, string $hint = 'Use the space bar to select options.'): array { if (is_array($options)) { - $options = fn (string $query) => array_filter($options, fn ($option) => str_contains(strtolower($option), strtolower($query))); + $options = fn (?string $query) => array_filter($options, fn ($option) => str_contains(strtolower($option), strtolower((string) $query))); } return (new MultiSearchPrompt($label, $options, $placeholder, $scroll, $required, $validate, $hint))->prompt(); } From 4e79bc36775198974b323dd6eed63c240a8e368e Mon Sep 17 00:00:00 2001 From: Dieter Holvoet Date: Mon, 28 Apr 2025 14:38:52 +0200 Subject: [PATCH 3/6] Deal with null return type --- src-symfony-compatibility/v6/Style/DrushStyle.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-symfony-compatibility/v6/Style/DrushStyle.php b/src-symfony-compatibility/v6/Style/DrushStyle.php index 5dd7d6e9ae..8f51b6a90c 100644 --- a/src-symfony-compatibility/v6/Style/DrushStyle.php +++ b/src-symfony-compatibility/v6/Style/DrushStyle.php @@ -111,7 +111,7 @@ public function suggest(string $label, array|\Closure $options, string $placehol * @param \Closure(string): array|array $options * @param true|string $required */ - public function search(string $label, \Closure|array $options, string $placeholder = '', int $scroll = 10, ?\Closure $validate = null, string $hint = '', bool|string $required = true): int|string + public function search(string $label, \Closure|array $options, string $placeholder = '', int $scroll = 10, ?\Closure $validate = null, string $hint = '', bool|string $required = true): int|string|null { if (is_array($options)) { $options = fn (?string $query) => array_filter($options, fn ($option) => str_contains(strtolower($option), strtolower((string) $query))); From 331966fed47fb271ebb7ad4b187433370b53510c Mon Sep 17 00:00:00 2001 From: Dieter Holvoet Date: Mon, 28 Apr 2025 17:08:55 +0200 Subject: [PATCH 4/6] Fix 'Value "" is invalid' --- src/Commands/ConfiguresPrompts.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/ConfiguresPrompts.php b/src/Commands/ConfiguresPrompts.php index 0afbae202a..31059ae120 100644 --- a/src/Commands/ConfiguresPrompts.php +++ b/src/Commands/ConfiguresPrompts.php @@ -127,7 +127,7 @@ function () use ($prompt) { } return array_filter( - $style->choice($prompt->label, ['' => 'None', ...$options], '', true), + $style->choice($prompt->label, ['' => 'None', ...$options], null, true), fn ($option, $key) => $key !== '', ARRAY_FILTER_USE_BOTH, ); From 085e4900567dbb27aa1fe675300370d3acd5ca61 Mon Sep 17 00:00:00 2001 From: Dieter Holvoet Date: Wed, 30 Apr 2025 16:39:58 +0200 Subject: [PATCH 5/6] Revert for certain commands --- src/Commands/core/CacheCommands.php | 4 ++-- src/Commands/core/SiteInstallCommands.php | 2 +- src/Commands/core/TopicCommands.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Commands/core/CacheCommands.php b/src/Commands/core/CacheCommands.php index 418080c4a0..4c62ff5dba 100644 --- a/src/Commands/core/CacheCommands.php +++ b/src/Commands/core/CacheCommands.php @@ -131,14 +131,14 @@ public function interact($input, $output): void if (empty($input->getArgument('type'))) { $types = $this->getTypes($this->bootstrapManager->hasBootstrapped(DrupalBootLevels::FULL)); $choices = array_combine(array_keys($types), array_keys($types)); - $type = $this->io()->search(dt("Choose a cache to clear"), $choices, 'render', scroll: 20); + $type = $this->io()->select(dt("Choose a cache to clear"), $choices, 'render', scroll: 20); $input->setArgument('type', $type); } if ($input->getArgument('type') == 'bin' && empty($input->getArgument('args'))) { $bins = Cache::getBins(); $choices = array_combine(array_keys($bins), array_keys($bins)); - $chosen = $this->io()->search(dt("Choose a cache to clear"), $choices, 'default', scroll: 20); + $chosen = $this->io()->select(dt("Choose a cache to clear"), $choices, 'default', scroll: 20); $input->setArgument('args', [$chosen]); } } diff --git a/src/Commands/core/SiteInstallCommands.php b/src/Commands/core/SiteInstallCommands.php index a3ad138468..ef15138531 100644 --- a/src/Commands/core/SiteInstallCommands.php +++ b/src/Commands/core/SiteInstallCommands.php @@ -366,7 +366,7 @@ public function validate(CommandData $commandData): void } // Ask questions to get our data. - $driverNamespace = $this->io()->search('Select the database driver', $driverSelectOptions); + $driverNamespace = $this->io()->select('Select the database driver', $driverSelectOptions); $formOptions = $driverList[$driverNamespace]->getInstallTasks()->getFormOptions([]); $databaseInfo = [ 'driver' => $driverList[$driverNamespace]->getDriverName(), diff --git a/src/Commands/core/TopicCommands.php b/src/Commands/core/TopicCommands.php index 38bb9a2e56..ad2de0278a 100644 --- a/src/Commands/core/TopicCommands.php +++ b/src/Commands/core/TopicCommands.php @@ -60,7 +60,7 @@ public function interact(InputInterface $input, OutputInterface $output): void $choices[$key] = $topic->getDescription() . " ($key)"; } natcasesort($choices); - $topic_name = $this->io()->search(dt('Choose a topic'), $choices, scroll: 30); + $topic_name = $this->io()->select(dt('Choose a topic'), $choices, scroll: 30); $input->setArgument('topic_name', $topic_name); } } From 0636925105c989c0cfa680632123e0316f6ad284 Mon Sep 17 00:00:00 2001 From: Dieter Holvoet Date: Wed, 30 Apr 2025 16:41:08 +0200 Subject: [PATCH 6/6] Switch widget depending on the amount of options --- src/Commands/core/ImageCommands.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/core/ImageCommands.php b/src/Commands/core/ImageCommands.php index b7499b8448..f3b0a09ec4 100644 --- a/src/Commands/core/ImageCommands.php +++ b/src/Commands/core/ImageCommands.php @@ -48,7 +48,7 @@ public function interactFlush(InputInterface $input, $output): void $styles_all = $styles; array_unshift($styles_all, 'all'); $choices = array_combine($styles_all, $styles_all); - $style_names = $this->io()->search(dt("Choose a style to flush"), $choices, 'all', scroll: 20); + $style_names = $this->io()->{count($choices) > 20 ? 'search' : 'select'}(dt("Choose a style to flush"), $choices, 'all', scroll: 20); if ($style_names == 'all') { $style_names = implode(',', $styles); }