From 24d19c59bb060635e500a299a144b0c223ca1686 Mon Sep 17 00:00:00 2001 From: Nikita Fedkin Date: Thu, 23 Apr 2026 10:57:33 +0200 Subject: [PATCH 01/11] feat: built-in completions command with shell script generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлена команда `completions` на уровне cli-библиотеки, которая формирует скрипты автодополнения для bash, zsh и PowerShell. Новые возможности ПараметрКоманды: - ПоставщикДополнения(Объект, ИмяМетода) — регистрация поставщика вариантов автодополнения значений параметра. - ПолучитьЗначенияДополнения() — вызов поставщика и безопасное получение списка вариантов (с обработкой исключений). - ПолучитьИмя/ПолучитьСинонимы/ПолучитьТипПараметра — публичные аксессоры для расширенной работы с параметрами. Новые API КомандаПриложения: - ПолучитьОпцииПараметрами/ПолучитьАргументыПараметрами — доступ к объектам ПараметрКоманды (в отличие от ПолучитьТаблицуОпций, которая отдаёт только их свойства). КонсольноеПриложение автоматически регистрирует подкоманду `completions --shell bash|zsh|pwsh`. Команда обходит дерево подкоманд, опрашивает поставщиков дополнения у каждого параметра и запекает полученные списки в итоговый скрипт — никакого runtime обращения к приложению из скрипта автодополнения. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- packagedef | 2 +- ...75\320\265\320\275\320\270\321\217Bash.os" | 200 ++++++++++++++++++ ...75\320\265\320\275\320\270\321\217Pwsh.os" | 159 ++++++++++++++ ...275\320\265\320\275\320\270\321\217Zsh.os" | 145 +++++++++++++ ...20\275\320\265\320\275\320\270\321\217.os" | 124 +++++++++++ ...20\266\320\265\320\275\320\270\321\217.os" | 32 +++ ...20\266\320\265\320\275\320\270\320\265.os" | 8 + ...20\274\320\260\320\275\320\264\321\213.os" | 104 +++++++++ 8 files changed, 773 insertions(+), 1 deletion(-) create mode 100644 "src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Bash.os" create mode 100644 "src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Pwsh.os" create mode 100644 "src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Zsh.os" create mode 100644 "src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" diff --git a/packagedef b/packagedef index 597ddd9..cb3c51b 100644 --- a/packagedef +++ b/packagedef @@ -4,7 +4,7 @@ // Описание.Имя("cli") - .Версия("0.11.0") + .Версия("0.12.0") .Автор("Khorev Aleksey") .АдресАвтора("khorevaa@gmail.com") .Описание("Данный пакет облегчает создание консольных приложений на Oscript") diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Bash.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Bash.os" new file mode 100644 index 0000000..1766645 --- /dev/null +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Bash.os" @@ -0,0 +1,200 @@ +// Генератор скрипта автодополнения для GNU Bash. + +Функция Оболочка() Экспорт + Возврат "bash"; +КонецФункции + +Функция СформироватьСкрипт(ИмяПриложения, Модель) Экспорт + + ИмяФункции = "_" + ИмяФункцииИзИмени(ИмяПриложения) + "_completions"; + + СтрокиСкрипта = Новый Массив(); + СтрокиСкрипта.Добавить("# " + ИмяПриложения + " bash completions"); + СтрокиСкрипта.Добавить("# Добавьте строку ниже в ~/.bashrc для активации:"); + СтрокиСкрипта.Добавить("# source <(" + ИмяПриложения + " completions --shell bash)"); + СтрокиСкрипта.Добавить(""); + СтрокиСкрипта.Добавить(ИмяФункции + "() {"); + СтрокиСкрипта.Добавить(" local cur prev words cword"); + СтрокиСкрипта.Добавить(" _init_completion 2>/dev/null || {"); + СтрокиСкрипта.Добавить(" COMPREPLY=()"); + СтрокиСкрипта.Добавить(" cur=""${COMP_WORDS[COMP_CWORD]}"""); + СтрокиСкрипта.Добавить(" prev=""${COMP_WORDS[COMP_CWORD-1]}"""); + СтрокиСкрипта.Добавить(" words=(""${COMP_WORDS[@]}"")"); + СтрокиСкрипта.Добавить(" cword=$COMP_CWORD"); + СтрокиСкрипта.Добавить(" }"); + СтрокиСкрипта.Добавить(""); + + ВсеИменаВерхнегоУровня = Новый Массив(); + Для Каждого Описание Из Модель Цикл + Для Каждого Имя Из Описание.Имена Цикл + ВсеИменаВерхнегоУровня.Добавить(Имя); + КонецЦикла; + КонецЦикла; + + СтрокиСкрипта.Добавить(" local commands=""" + СтрСоединить(ВсеИменаВерхнегоУровня, " ") + """"); + СтрокиСкрипта.Добавить(""); + СтрокиСкрипта.Добавить(" if [ $cword -eq 1 ]; then"); + СтрокиСкрипта.Добавить(" COMPREPLY=($(compgen -W ""$commands"" -- ""$cur""))"); + СтрокиСкрипта.Добавить(" return 0"); + СтрокиСкрипта.Добавить(" fi"); + СтрокиСкрипта.Добавить(""); + СтрокиСкрипта.Добавить(" local command=""${words[1]}"""); + СтрокиСкрипта.Добавить(" case ""$command"" in"); + + Для Каждого Описание Из Модель Цикл + ДобавитьВеткуКоманды(СтрокиСкрипта, Описание, " "); + КонецЦикла; + + СтрокиСкрипта.Добавить(" *)"); + СтрокиСкрипта.Добавить(" ;;"); + СтрокиСкрипта.Добавить(" esac"); + СтрокиСкрипта.Добавить(""); + СтрокиСкрипта.Добавить(" return 0"); + СтрокиСкрипта.Добавить("}"); + СтрокиСкрипта.Добавить(""); + СтрокиСкрипта.Добавить("complete -F " + ИмяФункции + " " + ИмяПриложения); + + Возврат СтрСоединить(СтрокиСкрипта, Символы.ПС); + +КонецФункции + +Процедура ДобавитьВеткуКоманды(СтрокиСкрипта, Описание, Отступ) + + ПаттернCase = СтрСоединить(Описание.Имена, "|"); + СтрокиСкрипта.Добавить(Отступ + ПаттернCase + ")"); + + ИменаОпций = ИменаПараметровДляКомплита(Описание.Опции); + + Для Каждого Опция Из Описание.Опции Цикл + Если Опция.Значения.Количество() = 0 Тогда + Продолжить; + КонецЕсли; + + Для Каждого ИмяОпции Из Опция.Имена Цикл + СтрокиСкрипта.Добавить(Отступ + " if [ ""$prev"" = """ + ИмяОпции + """ ]; then"); + СтрокиСкрипта.Добавить(Отступ + " COMPREPLY=($(compgen -W """ + СтрСоединить(Опция.Значения, " ") + """ -- ""$cur""))"); + СтрокиСкрипта.Добавить(Отступ + " return 0"); + СтрокиСкрипта.Добавить(Отступ + " fi"); + КонецЦикла; + КонецЦикла; + + СтрокиСкрипта.Добавить(Отступ + " if [[ ""$cur"" == -* ]]; then"); + СтрокиСкрипта.Добавить(Отступ + " COMPREPLY=($(compgen -W """ + СтрСоединить(ИменаОпций, " ") + """ -- ""$cur""))"); + СтрокиСкрипта.Добавить(Отступ + " return 0"); + СтрокиСкрипта.Добавить(Отступ + " fi"); + + Если Описание.Подкоманды.Количество() > 0 Тогда + + ИменаПодкоманд = Новый Массив(); + Для Каждого Под Из Описание.Подкоманды Цикл + Для Каждого Имя Из Под.Имена Цикл + ИменаПодкоманд.Добавить(Имя); + КонецЦикла; + КонецЦикла; + + СтрокиСкрипта.Добавить(Отступ + " if [ $cword -eq 2 ]; then"); + СтрокиСкрипта.Добавить(Отступ + " COMPREPLY=($(compgen -W """ + СтрСоединить(ИменаПодкоманд, " ") + """ -- ""$cur""))"); + СтрокиСкрипта.Добавить(Отступ + " return 0"); + СтрокиСкрипта.Добавить(Отступ + " fi"); + + СтрокиСкрипта.Добавить(Отступ + " local subcommand=""${words[2]}"""); + СтрокиСкрипта.Добавить(Отступ + " case ""$subcommand"" in"); + Для Каждого Под Из Описание.Подкоманды Цикл + ДобавитьВеткуПодкоманды(СтрокиСкрипта, Под, Отступ + " "); + КонецЦикла; + СтрокиСкрипта.Добавить(Отступ + " *)"); + СтрокиСкрипта.Добавить(Отступ + " ;;"); + СтрокиСкрипта.Добавить(Отступ + " esac"); + + Иначе + + ЗначенияАргументов = СобратьЗначенияАргументов(Описание); + Если ЗначенияАргументов.Количество() > 0 Тогда + СтрокиСкрипта.Добавить(Отступ + " COMPREPLY=($(compgen -W """ + СтрСоединить(ЗначенияАргументов, " ") + """ -- ""$cur""))"); + КонецЕсли; + + КонецЕсли; + + СтрокиСкрипта.Добавить(Отступ + " ;;"); + +КонецПроцедуры + +Процедура ДобавитьВеткуПодкоманды(СтрокиСкрипта, Описание, Отступ) + + ПаттернCase = СтрСоединить(Описание.Имена, "|"); + СтрокиСкрипта.Добавить(Отступ + ПаттернCase + ")"); + + ИменаОпций = ИменаПараметровДляКомплита(Описание.Опции); + + Для Каждого Опция Из Описание.Опции Цикл + Если Опция.Значения.Количество() = 0 Тогда + Продолжить; + КонецЕсли; + Для Каждого ИмяОпции Из Опция.Имена Цикл + СтрокиСкрипта.Добавить(Отступ + " if [ ""$prev"" = """ + ИмяОпции + """ ]; then"); + СтрокиСкрипта.Добавить(Отступ + " COMPREPLY=($(compgen -W """ + СтрСоединить(Опция.Значения, " ") + """ -- ""$cur""))"); + СтрокиСкрипта.Добавить(Отступ + " return 0"); + СтрокиСкрипта.Добавить(Отступ + " fi"); + КонецЦикла; + КонецЦикла; + + СтрокиСкрипта.Добавить(Отступ + " if [[ ""$cur"" == -* ]]; then"); + СтрокиСкрипта.Добавить(Отступ + " COMPREPLY=($(compgen -W """ + СтрСоединить(ИменаОпций, " ") + """ -- ""$cur""))"); + СтрокиСкрипта.Добавить(Отступ + " return 0"); + СтрокиСкрипта.Добавить(Отступ + " fi"); + + ЗначенияАргументов = СобратьЗначенияАргументов(Описание); + Если ЗначенияАргументов.Количество() > 0 Тогда + СтрокиСкрипта.Добавить(Отступ + " COMPREPLY=($(compgen -W """ + СтрСоединить(ЗначенияАргументов, " ") + """ -- ""$cur""))"); + КонецЕсли; + + СтрокиСкрипта.Добавить(Отступ + " ;;"); + +КонецПроцедуры + +Функция ИменаПараметровДляКомплита(Опции) + + Результат = Новый Массив(); + Результат.Добавить("--help"); + + Для Каждого Опция Из Опции Цикл + Для Каждого Имя Из Опция.Имена Цикл + Результат.Добавить(Имя); + КонецЦикла; + КонецЦикла; + + Возврат Результат; + +КонецФункции + +Функция СобратьЗначенияАргументов(Описание) + + Результат = Новый Массив(); + Для Каждого Аргумент Из Описание.Аргументы Цикл + Для Каждого Значение Из Аргумент.Значения Цикл + Результат.Добавить(Значение); + КонецЦикла; + КонецЦикла; + + Возврат Результат; + +КонецФункции + +Функция ИмяФункцииИзИмени(ИмяПриложения) + + Результат = ""; + Для Инд = 1 По СтрДлина(ИмяПриложения) Цикл + Символ = Сред(ИмяПриложения, Инд, 1); + Если (Символ >= "a" И Символ <= "z") + Или (Символ >= "A" И Символ <= "Z") + Или (Символ >= "0" И Символ <= "9") + Или Символ = "_" Тогда + Результат = Результат + Символ; + Иначе + Результат = Результат + "_"; + КонецЕсли; + КонецЦикла; + + Возврат Результат; + +КонецФункции diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Pwsh.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Pwsh.os" new file mode 100644 index 0000000..9531bbf --- /dev/null +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Pwsh.os" @@ -0,0 +1,159 @@ +// Генератор скрипта автодополнения для PowerShell. + +Функция Оболочка() Экспорт + Возврат "pwsh"; +КонецФункции + +Функция СформироватьСкрипт(ИмяПриложения, Модель) Экспорт + + Строки = Новый Массив(); + Строки.Добавить("# " + ИмяПриложения + " PowerShell completions"); + Строки.Добавить("# Активируйте строкой в профиле PowerShell ($PROFILE):"); + Строки.Добавить("# " + ИмяПриложения + " completions --shell pwsh | Out-String | Invoke-Expression"); + Строки.Добавить(""); + Строки.Добавить("Register-ArgumentCompleter -Native -CommandName '" + ИмяПриложения + "' -ScriptBlock {"); + Строки.Добавить(" param($wordToComplete, $commandAst, $cursorPosition)"); + Строки.Добавить(""); + Строки.Добавить(" $tokens = $commandAst.CommandElements | ForEach-Object { $_.Extent.Text }"); + Строки.Добавить(" $command = if ($tokens.Count -ge 2) { $tokens[1] } else { '' }"); + Строки.Добавить(" $subcommand = if ($tokens.Count -ge 3) { $tokens[2] } else { '' }"); + Строки.Добавить(" $prev = if ($tokens.Count -ge 2) { $tokens[$tokens.Count - 2] } else { '' }"); + Строки.Добавить(" $position = $tokens.Count - 1"); + Строки.Добавить(" if (-not [string]::IsNullOrEmpty($wordToComplete)) { $position = $position - 1 }"); + Строки.Добавить(""); + + СтрокаКоманд = " $commands = @("; + ПерваяКоманда = Истина; + Для Каждого Описание Из Модель Цикл + Для Каждого Имя Из Описание.Имена Цикл + Если Не ПерваяКоманда Тогда + СтрокаКоманд = СтрокаКоманд + ", "; + КонецЕсли; + СтрокаКоманд = СтрокаКоманд + "'" + Имя + "'"; + ПерваяКоманда = Ложь; + КонецЦикла; + КонецЦикла; + СтрокаКоманд = СтрокаКоманд + ")"; + Строки.Добавить(СтрокаКоманд); + + Строки.Добавить(""); + Строки.Добавить(" if ($position -le 1) {"); + Строки.Добавить(" return $commands | Where-Object { $_ -like ""$wordToComplete*"" } | ForEach-Object {"); + Строки.Добавить(" [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)"); + Строки.Добавить(" }"); + Строки.Добавить(" }"); + Строки.Добавить(""); + Строки.Добавить(" switch ($command) {"); + + Для Каждого Описание Из Модель Цикл + ДобавитьВеткуКоманды(Строки, Описание, " "); + КонецЦикла; + + Строки.Добавить(" default { }"); + Строки.Добавить(" }"); + Строки.Добавить("}"); + + Возврат СтрСоединить(Строки, Символы.ПС); + +КонецФункции + +Процедура ДобавитьВеткуКоманды(Строки, Описание, Отступ) + + Для Каждого Имя Из Описание.Имена Цикл + Строки.Добавить(Отступ + "'" + Имя + "' {"); + + Если Описание.Подкоманды.Количество() > 0 Тогда + ПодкомандыМассив = "@("; + ПерваяП = Истина; + Для Каждого Под Из Описание.Подкоманды Цикл + Для Каждого ИмяП Из Под.Имена Цикл + Если Не ПерваяП Тогда + ПодкомандыМассив = ПодкомандыМассив + ", "; + КонецЕсли; + ПодкомандыМассив = ПодкомандыМассив + "'" + ИмяП + "'"; + ПерваяП = Ложь; + КонецЦикла; + КонецЦикла; + ПодкомандыМассив = ПодкомандыМассив + ")"; + + Строки.Добавить(Отступ + " $subs = " + ПодкомандыМассив); + Строки.Добавить(Отступ + " if ($position -le 2) {"); + Строки.Добавить(Отступ + " return $subs | Where-Object { $_ -like ""$wordToComplete*"" } | ForEach-Object {"); + Строки.Добавить(Отступ + " [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)"); + Строки.Добавить(Отступ + " }"); + Строки.Добавить(Отступ + " }"); + + Строки.Добавить(Отступ + " switch ($subcommand) {"); + Для Каждого Под Из Описание.Подкоманды Цикл + ДобавитьВеткуПодкоманды(Строки, Под, Отступ + " "); + КонецЦикла; + Строки.Добавить(Отступ + " default { }"); + Строки.Добавить(Отступ + " }"); + Иначе + ДобавитьТелоПараметров(Строки, Описание, Отступ + " "); + КонецЕсли; + + Строки.Добавить(Отступ + " return"); + Строки.Добавить(Отступ + "}"); + КонецЦикла; + +КонецПроцедуры + +Процедура ДобавитьВеткуПодкоманды(Строки, Описание, Отступ) + + Для Каждого Имя Из Описание.Имена Цикл + Строки.Добавить(Отступ + "'" + Имя + "' {"); + ДобавитьТелоПараметров(Строки, Описание, Отступ + " "); + Строки.Добавить(Отступ + " return"); + Строки.Добавить(Отступ + "}"); + КонецЦикла; + +КонецПроцедуры + +Процедура ДобавитьТелоПараметров(Строки, Описание, Отступ) + + Для Каждого Опция Из Описание.Опции Цикл + Если Опция.Значения.Количество() = 0 Тогда + Продолжить; + КонецЕсли; + Для Каждого ИмяОпции Из Опция.Имена Цикл + МассивЗначений = "@("; + ПерваяЗ = Истина; + Для Каждого Значение Из Опция.Значения Цикл + Если Не ПерваяЗ Тогда + МассивЗначений = МассивЗначений + ", "; + КонецЕсли; + МассивЗначений = МассивЗначений + "'" + Значение + "'"; + ПерваяЗ = Ложь; + КонецЦикла; + МассивЗначений = МассивЗначений + ")"; + Строки.Добавить(Отступ + "if ($prev -eq '" + ИмяОпции + "') {"); + Строки.Добавить(Отступ + " return " + МассивЗначений + " | Where-Object { $_ -like ""$wordToComplete*"" } | ForEach-Object {"); + Строки.Добавить(Отступ + " [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)"); + Строки.Добавить(Отступ + " }"); + Строки.Добавить(Отступ + "}"); + КонецЦикла; + КонецЦикла; + + ЕстьЗначенияАргументов = Ложь; + МассивАргументов = "@("; + ПерваяА = Истина; + Для Каждого Аргумент Из Описание.Аргументы Цикл + Для Каждого Значение Из Аргумент.Значения Цикл + Если Не ПерваяА Тогда + МассивАргументов = МассивАргументов + ", "; + КонецЕсли; + МассивАргументов = МассивАргументов + "'" + Значение + "'"; + ПерваяА = Ложь; + ЕстьЗначенияАргументов = Истина; + КонецЦикла; + КонецЦикла; + МассивАргументов = МассивАргументов + ")"; + + Если ЕстьЗначенияАргументов Тогда + Строки.Добавить(Отступ + "return " + МассивАргументов + " | Where-Object { $_ -like ""$wordToComplete*"" } | ForEach-Object {"); + Строки.Добавить(Отступ + " [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)"); + Строки.Добавить(Отступ + "}"); + КонецЕсли; + +КонецПроцедуры diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Zsh.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Zsh.os" new file mode 100644 index 0000000..71fe0fd --- /dev/null +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Zsh.os" @@ -0,0 +1,145 @@ +// Генератор скрипта автодополнения для Zsh. + +Функция Оболочка() Экспорт + Возврат "zsh"; +КонецФункции + +Функция СформироватьСкрипт(ИмяПриложения, Модель) Экспорт + + БезопасноеИмя = БезопасноеИмяФункции(ИмяПриложения); + ИмяФункции = "_" + БезопасноеИмя; + + Строки = Новый Массив(); + Строки.Добавить("#compdef " + ИмяПриложения); + Строки.Добавить("# " + ИмяПриложения + " zsh completions"); + Строки.Добавить("# Поместите файл в путь $fpath, например ~/.zsh/completions/_" + ИмяПриложения + ","); + Строки.Добавить("# либо активируйте через: source <(" + ИмяПриложения + " completions --shell zsh)"); + Строки.Добавить(""); + Строки.Добавить(ИмяФункции + "() {"); + Строки.Добавить(" local -a commands"); + Строки.Добавить(" local context curcontext=""$curcontext"" state line"); + + СтрокаКоманд = " commands=("; + Для Каждого Описание Из Модель Цикл + Для Каждого Имя Из Описание.Имена Цикл + СтрокаКоманд = СтрокаКоманд + " " + Имя; + КонецЦикла; + КонецЦикла; + СтрокаКоманд = СтрокаКоманд + " )"; + Строки.Добавить(СтрокаКоманд); + + Строки.Добавить(" _arguments -C \"); + Строки.Добавить(" ""1: :->command"" \"); + Строки.Добавить(" ""*:: :->args"""); + Строки.Добавить(""); + Строки.Добавить(" case $state in"); + Строки.Добавить(" command)"); + Строки.Добавить(" _values ""command"" $commands"); + Строки.Добавить(" ;;"); + Строки.Добавить(" args)"); + Строки.Добавить(" case $words[1] in"); + + Для Каждого Описание Из Модель Цикл + ДобавитьВеткуКоманды(Строки, Описание, " "); + КонецЦикла; + + Строки.Добавить(" *) ;;"); + Строки.Добавить(" esac"); + Строки.Добавить(" ;;"); + Строки.Добавить(" esac"); + Строки.Добавить("}"); + Строки.Добавить(""); + Строки.Добавить("compdef " + ИмяФункции + " " + ИмяПриложения); + + Возврат СтрСоединить(Строки, Символы.ПС); + +КонецФункции + +Процедура ДобавитьВеткуКоманды(Строки, Описание, Отступ) + + ПаттернCase = СтрСоединить(Описание.Имена, "|"); + Строки.Добавить(Отступ + ПаттернCase + ")"); + + Если Описание.Подкоманды.Количество() > 0 Тогда + + ПодкомандыСтр = ""; + Для Каждого Под Из Описание.Подкоманды Цикл + Для Каждого Имя Из Под.Имена Цикл + ПодкомандыСтр = ПодкомандыСтр + " " + Имя; + КонецЦикла; + КонецЦикла; + Строки.Добавить(Отступ + " if (( CURRENT == 2 )); then"); + Строки.Добавить(Отступ + " _values ""subcommand""" + ПодкомандыСтр); + Строки.Добавить(Отступ + " else"); + Строки.Добавить(Отступ + " case $words[2] in"); + Для Каждого Под Из Описание.Подкоманды Цикл + ДобавитьВеткуПараметров(Строки, Под, Отступ + " "); + КонецЦикла; + Строки.Добавить(Отступ + " *) ;;"); + Строки.Добавить(Отступ + " esac"); + Строки.Добавить(Отступ + " fi"); + + Иначе + ДобавитьТелоПараметров(Строки, Описание, Отступ + " "); + КонецЕсли; + + Строки.Добавить(Отступ + " ;;"); + +КонецПроцедуры + +Процедура ДобавитьВеткуПараметров(Строки, Описание, Отступ) + + ПаттернCase = СтрСоединить(Описание.Имена, "|"); + Строки.Добавить(Отступ + ПаттернCase + ")"); + ДобавитьТелоПараметров(Строки, Описание, Отступ + " "); + Строки.Добавить(Отступ + " ;;"); + +КонецПроцедуры + +Процедура ДобавитьТелоПараметров(Строки, Описание, Отступ) + + Для Каждого Опция Из Описание.Опции Цикл + Если Опция.Значения.Количество() = 0 Тогда + Продолжить; + КонецЕсли; + Для Каждого ИмяОпции Из Опция.Имена Цикл + Строки.Добавить(Отступ + "if [[ $words[CURRENT-1] == """ + ИмяОпции + """ ]]; then"); + ЗначенияСтр = ""; + Для Каждого Значение Из Опция.Значения Цикл + ЗначенияСтр = ЗначенияСтр + " " + Значение; + КонецЦикла; + Строки.Добавить(Отступ + " _values """ + ИмяОпции + """" + ЗначенияСтр); + Строки.Добавить(Отступ + " return"); + Строки.Добавить(Отступ + "fi"); + КонецЦикла; + КонецЦикла; + + ЗначенияАргументов = ""; + Для Каждого Аргумент Из Описание.Аргументы Цикл + Для Каждого Значение Из Аргумент.Значения Цикл + ЗначенияАргументов = ЗначенияАргументов + " " + Значение; + КонецЦикла; + КонецЦикла; + Если Не ПустаяСтрока(ЗначенияАргументов) Тогда + Строки.Добавить(Отступ + "_values ""argument""" + ЗначенияАргументов); + КонецЕсли; + +КонецПроцедуры + +Функция БезопасноеИмяФункции(ИмяПриложения) + + Результат = ""; + Для Инд = 1 По СтрДлина(ИмяПриложения) Цикл + Символ = Сред(ИмяПриложения, Инд, 1); + Если (Символ >= "a" И Символ <= "z") + Или (Символ >= "A" И Символ <= "Z") + Или (Символ >= "0" И Символ <= "9") + Или Символ = "_" Тогда + Результат = Результат + Символ; + Иначе + Результат = Результат + "_"; + КонецЕсли; + КонецЦикла; + Возврат Результат; + +КонецФункции diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" new file mode 100644 index 0000000..8cd70be --- /dev/null +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" @@ -0,0 +1,124 @@ +// Реализация команды автодополнения командной оболочки. +// Автоматически регистрируется в КонсольноеПриложение как подкоманда "completions". + +Перем ГенераторBash; +Перем ГенераторZsh; +Перем ГенераторPwsh; + +Функция ОписаниеКоманды(Команда) Экспорт + + Команда + .Опция("shell s", "bash", "Командная оболочка, для которой генерируется скрипт") + .ТПеречисление() + .Перечисление("bash", "bash", "GNU Bash") + .Перечисление("zsh", "zsh", "Z Shell") + .Перечисление("pwsh", "pwsh", "PowerShell"); + + Возврат Команда; + +КонецФункции + +Процедура ВыполнитьКоманду(Команда) Экспорт + + ТипОболочки = Команда.ЗначениеОпции("shell"); + + ПриложениеCLI = Команда.Приложение; + КореньКоманд = ПриложениеCLI.ПолучитьКоманду(); + ИмяПриложения = КореньКоманд.ПолучитьИмяКоманды(); + + Модель = ПостроитьМодельПодкоманд(КореньКоманд); + + Генератор = ВыбратьГенератор(ТипОболочки); + Скрипт = Генератор.СформироватьСкрипт(ИмяПриложения, Модель); + + Сообщить(Скрипт); + +КонецПроцедуры + +Функция ПостроитьМодельПодкоманд(КомандаCLI) + + Результат = Новый Массив; + Для Каждого Подкоманда Из КомандаCLI.ПолучитьПодкоманды() Цикл + Результат.Добавить(ПостроитьОписаниеКоманды(Подкоманда)); + КонецЦикла; + Возврат Результат; + +КонецФункции + +Функция ПостроитьОписаниеКоманды(КомандаCLI) + + Описание = Новый Структура("Имена,Опции,Аргументы,Подкоманды"); + + Имена = Новый Массив; + Имена.Добавить(КомандаCLI.ПолучитьИмяКоманды()); + Для Каждого Синоним Из КомандаCLI.ПолучитьСинонимы() Цикл + Если Синоним <> КомандаCLI.ПолучитьИмяКоманды() Тогда + Имена.Добавить(Синоним); + КонецЕсли; + КонецЦикла; + Описание.Имена = Имена; + + Опции = Новый Массив; + Для Каждого Параметр Из КомандаCLI.ПолучитьОпцииПараметрами() Цикл + Опции.Добавить(ПостроитьОписаниеПараметра(Параметр, Истина)); + КонецЦикла; + Описание.Опции = Опции; + + Аргументы = Новый Массив; + Для Каждого Параметр Из КомандаCLI.ПолучитьАргументыПараметрами() Цикл + Аргументы.Добавить(ПостроитьОписаниеПараметра(Параметр, Ложь)); + КонецЦикла; + Описание.Аргументы = Аргументы; + + Описание.Подкоманды = ПостроитьМодельПодкоманд(КомандаCLI); + + Возврат Описание; + +КонецФункции + +Функция ПостроитьОписаниеПараметра(Параметр, ЭтоОпция) + + Описание = Новый Структура("Имена,Значения"); + + Имена = Новый Массив; + Если ЭтоОпция Тогда + Для Каждого Синоним Из Параметр.ПолучитьСинонимы() Цикл + Имена.Добавить(ФлагИзСинонима(Синоним)); + КонецЦикла; + Иначе + Имена.Добавить(Параметр.ПолучитьИмя()); + КонецЕсли; + Описание.Имена = Имена; + + Описание.Значения = Параметр.ПолучитьЗначенияДополнения(); + + Возврат Описание; + +КонецФункции + +Функция ФлагИзСинонима(Синоним) + + Если СтрДлина(Синоним) = 1 Тогда + Возврат "-" + Синоним; + КонецЕсли; + Возврат "--" + Синоним; + +КонецФункции + +Функция ВыбратьГенератор(ТипОболочки) + + Если ТипОболочки = "bash" Тогда + Возврат ГенераторBash; + ИначеЕсли ТипОболочки = "zsh" Тогда + Возврат ГенераторZsh; + ИначеЕсли ТипОболочки = "pwsh" Тогда + Возврат ГенераторPwsh; + КонецЕсли; + + ВызватьИсключение "Неизвестный тип оболочки: " + ТипОболочки; + +КонецФункции + +ГенераторBash = Новый ГенераторДополненияBash(); +ГенераторZsh = Новый ГенераторДополненияZsh(); +ГенераторPwsh = Новый ГенераторДополненияPwsh(); diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217.os" index 78be19d..a931641 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217.os" @@ -744,6 +744,38 @@ КонецФункции +// Возвращает список опций команды в виде массива объектов ПараметрКоманды, +// для расширенной работы с параметрами (например, автодополнение). +// +// Возвращаемое значение: +// Массив из ПараметрКоманды +// +Функция ПолучитьОпцииПараметрами() Экспорт + + Результат = Новый Массив; + Для Каждого КлючЗначение Из Опции Цикл + Результат.Добавить(КлючЗначение.Значение); + КонецЦикла; + Возврат Результат; + +КонецФункции + +// Возвращает список аргументов команды в виде массива объектов ПараметрКоманды, +// для расширенной работы с параметрами (например, автодополнение). +// +// Возвращаемое значение: +// Массив из ПараметрКоманды +// +Функция ПолучитьАргументыПараметрами() Экспорт + + Результат = Новый Массив; + Для Каждого КлючЗначение Из Аргументы Цикл + Результат.Добавить(КлючЗначение.Значение); + КонецЦикла; + Возврат Результат; + +КонецФункции + #КонецОбласти #Область Работа_с_входящими_аргументами diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\321\201\320\276\320\273\321\214\320\275\320\276\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\320\265.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\321\201\320\276\320\273\321\214\320\275\320\276\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\320\265.os" index 02a639b..1e3ebbc 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\321\201\320\276\320\273\321\214\320\275\320\276\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\320\265.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\321\201\320\276\320\273\321\214\320\275\320\276\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\320\265.os" @@ -1,3 +1,5 @@ +#Использовать "./internal/completion" + // Класс ПараметрКоманды, для доступа к установленному значению из вне Перем ФлагВерсия Экспорт; // Строковое представление версии приложения @@ -7,6 +9,8 @@ Перем НаименованиеПриложения; Перем ОписаниеПриложения; + + // Процедура добавляет версию приложения, // при вызове данной опции, показывается установленная версия и // завершается выполнение с кодом (0) @@ -208,4 +212,8 @@ Команда = Новый КомандаПриложения(Наименование, Описание, КлассРеализацииОсновныйКоманды, ЭтотОбъект); + Команда.ДобавитьПодкоманду("completions", + "Формирует скрипт автодополнения для командной оболочки", + Новый КомандаДополнения()); + КонецПроцедуры \ No newline at end of file diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\320\232\320\276\320\274\320\260\320\275\320\264\321\213.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\320\232\320\276\320\274\320\260\320\275\320\264\321\213.os" index 0764c0c..c544936 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\320\232\320\276\320\274\320\260\320\275\320\264\321\213.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\320\232\320\276\320\274\320\260\320\275\320\264\321\213.os" @@ -1,4 +1,5 @@ #Использовать logos +#Использовать delegate // Имя параметра команды // первая строка из массива строк, переданных при создании @@ -56,6 +57,11 @@ Перем ТипЗначенияПараметра; // Произвольный класс реализуемые несколько обязательных методов Перем РазделительМассива; // Строка +// Объект-владелец метода, предоставляющего варианты автодополнения значений параметра +Перем ОбъектПоставщикаДополнения; // Произвольный +// Имя экспортного метода-функции объекта, возвращающего Массив<Строка> вариантов +Перем ИмяМетодаПоставщикаДополнения; // Строка + Перем Лог; Процедура ПриСозданииОбъекта(ВходящийТипПараметра, @@ -114,6 +120,33 @@ КонецФункции +// Возвращает имя параметра (первое слово в наименовании). +// +// Возвращаемое значение: +// Строка +// +Функция ПолучитьИмя() Экспорт + Возврат Имя; +КонецФункции + +// Возвращает все имена/синонимы параметра. +// +// Возвращаемое значение: +// Массив из Строка +// +Функция ПолучитьСинонимы() Экспорт + Возврат Синонимы; +КонецФункции + +// Возвращает тип параметра: "опция" или "аргумент". +// +// Возвращаемое значение: +// Строка +// +Функция ПолучитьТипПараметра() Экспорт + Возврат ТипПараметра; +КонецФункции + // Процедура очищает, Значение параметра, для типа Массив // Процедура Очистить() Экспорт @@ -598,6 +631,77 @@ КонецФункции +// Регистрирует поставщик вариантов автодополнения значений параметра. +// Варианты используются командой `completions` для запекания в итоговый скрипт +// автодополнения командной оболочки. +// +// Параметры: +// ОбъектРеализации - Произвольный - объект-владелец метода-поставщика +// ИмяМетода - Строка - имя экспортной функции без параметров, +// возвращающей Массив<Строка> / ФиксированныйМассив +// +// Возвращаемое значение: +// ПараметрКоманды - значение из переменной ЭтотОбъект +// +Функция ПоставщикДополнения(Знач ОбъектРеализации, Знач ИмяМетода) Экспорт + + ОбъектПоставщикаДополнения = ОбъектРеализации; + ИмяМетодаПоставщикаДополнения = ИмяМетода; + + Возврат ЭтотОбъект; + +КонецФункции + +// Возвращает признак наличия поставщика вариантов автодополнения у параметра. +// +// Возвращаемое значение: +// Булево +// +Функция ЕстьПоставщикДополнения() Экспорт + + Возврат ОбъектПоставщикаДополнения <> Неопределено И Не ПустаяСтрока(ИмяМетодаПоставщикаДополнения); + +КонецФункции + +// Вызывает поставщик и возвращает массив вариантов автодополнения. +// При отсутствии поставщика возвращает пустой массив. +// При исключении внутри поставщика — записывает предупреждение в лог и +// возвращает пустой массив, чтобы генерация скрипта не прерывалась. +// +// Возвращаемое значение: +// Массив из Строка +// +Функция ПолучитьЗначенияДополнения() Экспорт + + Результат = Новый Массив(); + + Если Не ЕстьПоставщикДополнения() Тогда + Возврат Результат; + КонецЕсли; + + Попытка + Делегат = Делегаты.Создать(ОбъектПоставщикаДополнения, ИмяМетодаПоставщикаДополнения); + ЗначениеПоставщика = Делегат.Исполнить(); + Исключение + Ошибка = ИнформацияОбОшибке(); + Лог.Предупреждение( + "Поставщик автодополнения ""%1"" завершился с ошибкой: %2", + ИмяМетодаПоставщикаДополнения, + ПодробноеПредставлениеОшибки(Ошибка)); + Возврат Результат; + КонецПопытки; + + Если ТипЗнч(ЗначениеПоставщика) = Тип("Массив") + Или ТипЗнч(ЗначениеПоставщика) = Тип("ФиксированныйМассив") Тогда + Для Каждого Элемент Из ЗначениеПоставщика Цикл + Результат.Добавить(Строка(Элемент)); + КонецЦикла; + КонецЕсли; + + Возврат Результат; + +КонецФункции + #КонецОбласти Функция ВстроенныеТипЗначенийПараметров() From 0c50f500f11222c105280792d19f7b710affc84b Mon Sep 17 00:00:00 2001 From: Nikita Fedkin Date: Thu, 23 Apr 2026 11:07:53 +0200 Subject: [PATCH 02/11] =?UTF-8?q?refactor(completion):=20promote=20=D0=9A?= =?UTF-8?q?=D0=BE=D0=BC=D0=B0=D0=BD=D0=B4=D0=B0=D0=94=D0=BE=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20to=20top-level=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Moved КомандаДополнения.os from internal/completion/Классы/ to core/Классы/ (next to other public command classes). - Registered the class in packagedef so it can be instantiated by name. - #Использовать of the internal/completion subpackage (shell generators) now lives only in КомандаДополнения itself, not in КонсольноеПриложение. - Generators are constructed in ПриСозданииОбъекта instead of module body. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- packagedef | 1 + ...0\273\320\275\320\265\320\275\320\270\321\217.os" | 12 +++++++++--- ...0\276\320\266\320\265\320\275\320\270\320\265.os" | 2 -- 3 files changed, 10 insertions(+), 5 deletions(-) rename "src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" => "src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" (93%) diff --git a/packagedef b/packagedef index cb3c51b..ebf10a7 100644 --- a/packagedef +++ b/packagedef @@ -25,4 +25,5 @@ .ОпределяетКласс("КонсольноеПриложение", "src/core/Классы/КонсольноеПриложение.os") .ОпределяетКласс("КомандаПриложения", "src/core/Классы/КомандаПриложения.os") .ОпределяетКласс("ПараметрКоманды", "src/core/Классы/ПараметрКоманды.os") + .ОпределяетКласс("КомандаДополнения", "src/core/Классы/КомандаДополнения.os") ; diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" similarity index 93% rename from "src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" rename to "src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" index 8cd70be..54c7805 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" @@ -1,6 +1,8 @@ // Реализация команды автодополнения командной оболочки. // Автоматически регистрируется в КонсольноеПриложение как подкоманда "completions". +#Использовать "./internal/completion" + Перем ГенераторBash; Перем ГенераторZsh; Перем ГенераторPwsh; @@ -119,6 +121,10 @@ КонецФункции -ГенераторBash = Новый ГенераторДополненияBash(); -ГенераторZsh = Новый ГенераторДополненияZsh(); -ГенераторPwsh = Новый ГенераторДополненияPwsh(); +Процедура ПриСозданииОбъекта() + + ГенераторBash = Новый ГенераторДополненияBash(); + ГенераторZsh = Новый ГенераторДополненияZsh(); + ГенераторPwsh = Новый ГенераторДополненияPwsh(); + +КонецПроцедуры diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\321\201\320\276\320\273\321\214\320\275\320\276\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\320\265.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\321\201\320\276\320\273\321\214\320\275\320\276\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\320\265.os" index 1e3ebbc..04e3e65 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\321\201\320\276\320\273\321\214\320\275\320\276\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\320\265.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\321\201\320\276\320\273\321\214\320\275\320\276\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\320\265.os" @@ -1,5 +1,3 @@ -#Использовать "./internal/completion" - // Класс ПараметрКоманды, для доступа к установленному значению из вне Перем ФлагВерсия Экспорт; // Строковое представление версии приложения From 767078c7df91b95847ea2711070e3cd7768ca352 Mon Sep 17 00:00:00 2001 From: Nikita Fedkin Date: Thu, 23 Apr 2026 11:20:49 +0200 Subject: [PATCH 03/11] =?UTF-8?q?refactor(completion):=20use=20existing=20?= =?UTF-8?q?=D0=9F=D0=BE=D0=BB=D1=83=D1=87=D0=B8=D1=82=D1=8C=D0=A2=D0=B0?= =?UTF-8?q?=D0=B1=D0=BB=D0=B8=D1=86=D1=83=D0=9E=D0=BF=D1=86=D0=B8=D0=B9/?= =?UTF-8?q?=D0=90=D1=80=D0=B3=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=BE=D0=B2=20?= =?UTF-8?q?for=20model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the built-in table API instead of bespoke getters: the table already carries the fully-resolved НаименованияПараметров (with - / -- prefixes) and the argument name, so the heuristic about synonym length disappears. - КомандаПриложения: added 'Параметр' column to the help-data table, carrying the ПараметрКоманды object reference so consumers can reach the completion provider without extra getters. - Dropped ПолучитьОпцииПараметрами / ПолучитьАргументыПараметрами (added in previous commit, never used outside КомандаДополнения). - Dropped ПолучитьИмя / ПолучитьСинонимы / ПолучитьТипПараметра on ПараметрКоманды (fields Имя / Синонимы / ТипПараметра are already exported). - КомандаДополнения.ПостроитьОписаниеПараметра reads НаименованияПараметров straight from the table row — no ФлагИзСинонима heuristic any more. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...20\275\320\265\320\275\320\270\321\217.os" | 34 ++++--------------- ...20\266\320\265\320\275\320\270\321\217.os" | 34 ++----------------- ...20\274\320\260\320\275\320\264\321\213.os" | 27 --------------- 3 files changed, 9 insertions(+), 86 deletions(-) diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" index 54c7805..2f86de2 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" @@ -61,14 +61,14 @@ Описание.Имена = Имена; Опции = Новый Массив; - Для Каждого Параметр Из КомандаCLI.ПолучитьОпцииПараметрами() Цикл - Опции.Добавить(ПостроитьОписаниеПараметра(Параметр, Истина)); + Для Каждого СтрокаОпции Из КомандаCLI.ПолучитьТаблицуОпций() Цикл + Опции.Добавить(ПостроитьОписаниеПараметра(СтрокаОпции)); КонецЦикла; Описание.Опции = Опции; Аргументы = Новый Массив; - Для Каждого Параметр Из КомандаCLI.ПолучитьАргументыПараметрами() Цикл - Аргументы.Добавить(ПостроитьОписаниеПараметра(Параметр, Ложь)); + Для Каждого СтрокаАргумента Из КомандаCLI.ПолучитьТаблицуАргументов() Цикл + Аргументы.Добавить(ПостроитьОписаниеПараметра(СтрокаАргумента)); КонецЦикла; Описание.Аргументы = Аргументы; @@ -78,35 +78,15 @@ КонецФункции -Функция ПостроитьОписаниеПараметра(Параметр, ЭтоОпция) +Функция ПостроитьОписаниеПараметра(СтрокаТаблицы) Описание = Новый Структура("Имена,Значения"); - - Имена = Новый Массив; - Если ЭтоОпция Тогда - Для Каждого Синоним Из Параметр.ПолучитьСинонимы() Цикл - Имена.Добавить(ФлагИзСинонима(Синоним)); - КонецЦикла; - Иначе - Имена.Добавить(Параметр.ПолучитьИмя()); - КонецЕсли; - Описание.Имена = Имена; - - Описание.Значения = Параметр.ПолучитьЗначенияДополнения(); - + Описание.Имена = СтрокаТаблицы.НаименованияПараметров; + Описание.Значения = СтрокаТаблицы.Параметр.ПолучитьЗначенияДополнения(); Возврат Описание; КонецФункции -Функция ФлагИзСинонима(Синоним) - - Если СтрДлина(Синоним) = 1 Тогда - Возврат "-" + Синоним; - КонецЕсли; - Возврат "--" + Синоним; - -КонецФункции - Функция ВыбратьГенератор(ТипОболочки) Если ТипОболочки = "bash" Тогда diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217.os" index a931641..8a3e2df 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217.os" @@ -744,38 +744,6 @@ КонецФункции -// Возвращает список опций команды в виде массива объектов ПараметрКоманды, -// для расширенной работы с параметрами (например, автодополнение). -// -// Возвращаемое значение: -// Массив из ПараметрКоманды -// -Функция ПолучитьОпцииПараметрами() Экспорт - - Результат = Новый Массив; - Для Каждого КлючЗначение Из Опции Цикл - Результат.Добавить(КлючЗначение.Значение); - КонецЦикла; - Возврат Результат; - -КонецФункции - -// Возвращает список аргументов команды в виде массива объектов ПараметрКоманды, -// для расширенной работы с параметрами (например, автодополнение). -// -// Возвращаемое значение: -// Массив из ПараметрКоманды -// -Функция ПолучитьАргументыПараметрами() Экспорт - - Результат = Новый Массив; - Для Каждого КлючЗначение Из Аргументы Цикл - Результат.Добавить(КлючЗначение.Значение); - КонецЦикла; - Возврат Результат; - -КонецФункции - #КонецОбласти #Область Работа_с_входящими_аргументами @@ -928,6 +896,7 @@ Таблица.Колонки.Добавить("СкрытьЗначение"); Таблица.Колонки.Добавить("НаименованияПараметров"); Таблица.Колонки.Добавить("Значение"); + Таблица.Колонки.Добавить("Параметр"); Возврат Таблица; @@ -1294,6 +1263,7 @@ НоваяЗапись.ПеременнаяОкружения = ПараметрСправки.ПеременнаяОкружения; НоваяЗапись.СкрытьЗначение = ПараметрСправки.СкрытьЗначение; НоваяЗапись.НаименованияПараметров = ПараметрСправки.НаименованияПараметров; + НоваяЗапись.Параметр = ПараметрСправки; Если НЕ ПараметрСправки.УстановленаИзПеременнойОкружения Тогда diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\320\232\320\276\320\274\320\260\320\275\320\264\321\213.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\320\232\320\276\320\274\320\260\320\275\320\264\321\213.os" index c544936..947a351 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\320\232\320\276\320\274\320\260\320\275\320\264\321\213.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\320\232\320\276\320\274\320\260\320\275\320\264\321\213.os" @@ -120,33 +120,6 @@ КонецФункции -// Возвращает имя параметра (первое слово в наименовании). -// -// Возвращаемое значение: -// Строка -// -Функция ПолучитьИмя() Экспорт - Возврат Имя; -КонецФункции - -// Возвращает все имена/синонимы параметра. -// -// Возвращаемое значение: -// Массив из Строка -// -Функция ПолучитьСинонимы() Экспорт - Возврат Синонимы; -КонецФункции - -// Возвращает тип параметра: "опция" или "аргумент". -// -// Возвращаемое значение: -// Строка -// -Функция ПолучитьТипПараметра() Экспорт - Возврат ТипПараметра; -КонецФункции - // Процедура очищает, Значение параметра, для типа Массив // Процедура Очистить() Экспорт From 80072b9910fc206812a510fe47fb92540f09de3b Mon Sep 17 00:00:00 2001 From: Nikita Fedkin Date: Thu, 23 Apr 2026 11:29:37 +0200 Subject: [PATCH 04/11] refactor(completion): flatten completion values into table row MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of carrying a reference to ПараметрКоманды, the help-data table row now carries the already-resolved list in a dedicated ЗначенияДополнения column, computed once in ПолучитьТаблицуПараметров via Параметр.ПолучитьЗначенияДополнения(). КомандаДополнения consumes only table columns — no reach-through to the parameter object. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...20\276\320\273\320\275\320\265\320\275\320\270\321\217.os" | 2 +- ...20\273\320\276\320\266\320\265\320\275\320\270\321\217.os" | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" index 2f86de2..a2ffe01 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" @@ -82,7 +82,7 @@ Описание = Новый Структура("Имена,Значения"); Описание.Имена = СтрокаТаблицы.НаименованияПараметров; - Описание.Значения = СтрокаТаблицы.Параметр.ПолучитьЗначенияДополнения(); + Описание.Значения = СтрокаТаблицы.ЗначенияДополнения; Возврат Описание; КонецФункции diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217.os" index 8a3e2df..ef86879 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217.os" @@ -896,7 +896,7 @@ Таблица.Колонки.Добавить("СкрытьЗначение"); Таблица.Колонки.Добавить("НаименованияПараметров"); Таблица.Колонки.Добавить("Значение"); - Таблица.Колонки.Добавить("Параметр"); + Таблица.Колонки.Добавить("ЗначенияДополнения"); Возврат Таблица; @@ -1263,7 +1263,7 @@ НоваяЗапись.ПеременнаяОкружения = ПараметрСправки.ПеременнаяОкружения; НоваяЗапись.СкрытьЗначение = ПараметрСправки.СкрытьЗначение; НоваяЗапись.НаименованияПараметров = ПараметрСправки.НаименованияПараметров; - НоваяЗапись.Параметр = ПараметрСправки; + НоваяЗапись.ЗначенияДополнения = ПараметрСправки.ПолучитьЗначенияДополнения(); Если НЕ ПараметрСправки.УстановленаИзПеременнойОкружения Тогда From 84a86ac04c396b5006a53e74c3011cf891712813 Mon Sep 17 00:00:00 2001 From: Nikita Fedkin Date: Thu, 23 Apr 2026 11:36:17 +0200 Subject: [PATCH 05/11] =?UTF-8?q?refactor(completion):=20use=20builtin=20?= =?UTF-8?q?=D0=94=D0=B5=D0=B9=D1=81=D1=82=D0=B2=D0=B8=D0=B5=20instead=20of?= =?UTF-8?q?=20delegate=20package?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ПараметрКоманды no longer imports the external delegate package — the completion provider is invoked via the language-native Действие(Объект, ИмяМетода).Выполнить(). delegate stays as a cli-wide dependency for КомандаПриложения, which still uses Делегаты.Создать extensively. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...0\232\320\276\320\274\320\260\320\275\320\264\321\213.os" | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\320\232\320\276\320\274\320\260\320\275\320\264\321\213.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\320\232\320\276\320\274\320\260\320\275\320\264\321\213.os" index 947a351..1678fae 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\320\232\320\276\320\274\320\260\320\275\320\264\321\213.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\320\232\320\276\320\274\320\260\320\275\320\264\321\213.os" @@ -1,5 +1,4 @@ #Использовать logos -#Использовать delegate // Имя параметра команды // первая строка из массива строк, переданных при создании @@ -653,8 +652,8 @@ КонецЕсли; Попытка - Делегат = Делегаты.Создать(ОбъектПоставщикаДополнения, ИмяМетодаПоставщикаДополнения); - ЗначениеПоставщика = Делегат.Исполнить(); + Действие = Новый Действие(ОбъектПоставщикаДополнения, ИмяМетодаПоставщикаДополнения); + ЗначениеПоставщика = Действие.Выполнить(); Исключение Ошибка = ИнформацияОбОшибке(); Лог.Предупреждение( From 26a8800ce626adbb80284cb76e6b11d98b96df5e Mon Sep 17 00:00:00 2001 From: Nikita Fedkin Date: Thu, 23 Apr 2026 11:37:55 +0200 Subject: [PATCH 06/11] refactor(completion): trust the provider contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The &ПоставщикДополнения contract requires the method to return an array of strings, so return it as-is instead of defensively copying and string-coercing each element. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...20\274\320\260\320\275\320\264\321\213.os" | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\320\232\320\276\320\274\320\260\320\275\320\264\321\213.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\320\232\320\276\320\274\320\260\320\275\320\264\321\213.os" index 1678fae..6e9ccb5 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\320\232\320\276\320\274\320\260\320\275\320\264\321\213.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\320\232\320\276\320\274\320\260\320\275\320\264\321\213.os" @@ -645,33 +645,21 @@ // Функция ПолучитьЗначенияДополнения() Экспорт - Результат = Новый Массив(); - Если Не ЕстьПоставщикДополнения() Тогда - Возврат Результат; + Возврат Новый Массив; КонецЕсли; Попытка Действие = Новый Действие(ОбъектПоставщикаДополнения, ИмяМетодаПоставщикаДополнения); - ЗначениеПоставщика = Действие.Выполнить(); + Возврат Действие.Выполнить(); Исключение - Ошибка = ИнформацияОбОшибке(); Лог.Предупреждение( "Поставщик автодополнения ""%1"" завершился с ошибкой: %2", ИмяМетодаПоставщикаДополнения, - ПодробноеПредставлениеОшибки(Ошибка)); - Возврат Результат; + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке())); + Возврат Новый Массив; КонецПопытки; - Если ТипЗнч(ЗначениеПоставщика) = Тип("Массив") - Или ТипЗнч(ЗначениеПоставщика) = Тип("ФиксированныйМассив") Тогда - Для Каждого Элемент Из ЗначениеПоставщика Цикл - Результат.Добавить(Строка(Элемент)); - КонецЦикла; - КонецЕсли; - - Возврат Результат; - КонецФункции #КонецОбласти From ff92eb9c5f0da2e220d53ff475dc74712b97a938 Mon Sep 17 00:00:00 2001 From: Nikita Fedkin Date: Thu, 23 Apr 2026 11:42:01 +0200 Subject: [PATCH 07/11] =?UTF-8?q?refactor:=20drop=20delegate=20package=20d?= =?UTF-8?q?ependency=20in=20favor=20of=20native=20=D0=94=D0=B5=D0=B9=D1=81?= =?UTF-8?q?=D1=82=D0=B2=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All call sites used the same pattern — save a bound method, later invoke with one positional argument — and the built-in Действие type supports this 1:1: Делегаты.Создать(Объект, "Имя") -> Новый Действие(Объект, "Имя") Делегат.Исполнить(Арг) -> Действие.Выполнить(Арг) Removes the external delegate package from cli's dependency graph. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- packagedef | 1 - ...20\266\320\265\320\275\320\270\321\217.os" | 19 +++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packagedef b/packagedef index ebf10a7..a91c2d8 100644 --- a/packagedef +++ b/packagedef @@ -15,7 +15,6 @@ //.ВключитьФайл("package-loader.os") //.ВключитьФайл("packagedef") .ЗависитОт("logos", "1.1.1") - .ЗависитОт("delegate", "0.2.0") .ЗависитОт("reflector", "0.5.1") .ЗависитОт("fluent", "0.6.0") .ЗависитОт("datetime", "0.1.0") diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217.os" index ef86879..a17cdd0 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217.os" @@ -3,7 +3,6 @@ #Использовать "./internal/types" #Использовать "./internal/tools" #Использовать "./internal/path" -#Использовать delegate #Использовать logos #Использовать reflector #Использовать fluent @@ -628,7 +627,7 @@ ВызватьИсключение НоваяИнформацияОбОшибке("Ошибка установки действия <ВыполнитьКоманду>. Объект <%1> не содержит требуемого метода <%2>", ОбъектРеализации, ИмяПроцедуры); КонецЕсли; - ДелегатДействия = Делегаты.Создать(ОбъектРеализации, ИмяПроцедуры); + ДелегатДействия = Новый Действие(ОбъектРеализации, ИмяПроцедуры); ДобавитьВИндексДействиеКоманды("ВыполнитьКоманду", ДелегатДействия); КонецПроцедуры @@ -653,7 +652,7 @@ Лог.Отладка(" >> метод %2 у класс <%1> найден", ОбъектРеализации, ИмяПроцедуры); - ДелегатДействия = Делегаты.Создать(ОбъектРеализации, ИмяПроцедуры); + ДелегатДействия = Новый Действие(ОбъектРеализации, ИмяПроцедуры); ДобавитьВИндексДействиеКоманды("ПередВыполнениемКоманды", ДелегатДействия); КонецПроцедуры @@ -678,7 +677,7 @@ Лог.Отладка(" >> метод %2 у класс <%1> найден", ОбъектРеализации, ИмяПроцедуры); - ДелегатДействия = Делегаты.Создать(ОбъектРеализации, ИмяПроцедуры); + ДелегатДействия = Новый Действие(ОбъектРеализации, ИмяПроцедуры); ДобавитьВИндексДействиеКоманды("ПослеВыполненияКоманды", ДелегатДействия); КонецПроцедуры @@ -702,7 +701,7 @@ Лог.Отладка(" >> метод %2 у класс <%1> найден", ОбъектРеализации, ИмяПроцедуры); - ДелегатДействия = Делегаты.Создать(ОбъектРеализации, ИмяПроцедуры); + ДелегатДействия = Новый Действие(ОбъектРеализации, ИмяПроцедуры); ДобавитьВИндексДействиеКоманды("ВывестиСправку", ДелегатДействия); @@ -847,7 +846,7 @@ Возврат Ложь; КонецЕсли; - ДелегатДействия.Исполнить(ЭтотОбъект); + ДелегатДействия.Выполнить(ЭтотОбъект); Возврат Истина; @@ -1210,22 +1209,22 @@ КонецЕсли; Если РеализованныеМетодыКоманды.ПередВыполнениемКоманды Тогда - ДелегатДействия = Делегаты.Создать(КлассРеализации, "ПередВыполнениемКоманды"); + ДелегатДействия = Новый Действие(КлассРеализации, "ПередВыполнениемКоманды"); ДобавитьВИндексДействиеКоманды("ПередВыполнениемКоманды", ДелегатДействия); КонецЕсли; Если РеализованныеМетодыКоманды.ВыполнитьКоманду Тогда - ДелегатДействия = Делегаты.Создать(КлассРеализации, "ВыполнитьКоманду"); + ДелегатДействия = Новый Действие(КлассРеализации, "ВыполнитьКоманду"); ДобавитьВИндексДействиеКоманды("ВыполнитьКоманду", ДелегатДействия); КонецЕсли; Если РеализованныеМетодыКоманды.ПослеВыполненияКоманды Тогда - ДелегатДействия = Делегаты.Создать(КлассРеализации, "ПослеВыполненияКоманды"); + ДелегатДействия = Новый Действие(КлассРеализации, "ПослеВыполненияКоманды"); ДобавитьВИндексДействиеКоманды("ПослеВыполненияКоманды", ДелегатДействия); КонецЕсли; Если РеализованныеМетодыКоманды.ВывестиСправку Тогда - ДелегатДействия = Делегаты.Создать(КлассРеализации, "ВывестиСправку"); + ДелегатДействия = Новый Действие(КлассРеализации, "ВывестиСправку"); ДобавитьВИндексДействиеКоманды("ВывестиСправку", ДелегатДействия); КонецЕсли; From 2d721ac1a25969139aef1214ed6532a33fbb8bbb Mon Sep 17 00:00:00 2001 From: Nikita Fedkin Date: Thu, 23 Apr 2026 14:20:59 +0200 Subject: [PATCH 08/11] refactor(completions): normalize command model and simplify shell generators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit КомандаДополнения.ПостроитьОписаниеКоманды теперь возвращает нормализованную модель с плоскими полями ИмяКаноническое, Алиасы, ИменаОпций, ЗначенияПоОпции, ЗначенияАргументов, Подкоманды. Генераторы bash/zsh/pwsh больше не итерируют по таблицам опций/аргументов — они работают с общей формой, что делает поведение единообразным на любой глубине подкоманд для всех трёх шеллов. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...75\320\265\320\275\320\270\321\217Bash.os" | 207 +++++--------- ...75\320\265\320\275\320\270\321\217Pwsh.os" | 260 ++++++++++-------- ...275\320\265\320\275\320\270\321\217Zsh.os" | 88 ++---- ...20\275\320\265\320\275\320\270\321\217.os" | 44 +-- 4 files changed, 262 insertions(+), 337 deletions(-) diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Bash.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Bash.os" index 1766645..43f690b 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Bash.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Bash.os" @@ -6,181 +6,107 @@ Функция СформироватьСкрипт(ИмяПриложения, Модель) Экспорт - ИмяФункции = "_" + ИмяФункцииИзИмени(ИмяПриложения) + "_completions"; - - СтрокиСкрипта = Новый Массив(); - СтрокиСкрипта.Добавить("# " + ИмяПриложения + " bash completions"); - СтрокиСкрипта.Добавить("# Добавьте строку ниже в ~/.bashrc для активации:"); - СтрокиСкрипта.Добавить("# source <(" + ИмяПриложения + " completions --shell bash)"); - СтрокиСкрипта.Добавить(""); - СтрокиСкрипта.Добавить(ИмяФункции + "() {"); - СтрокиСкрипта.Добавить(" local cur prev words cword"); - СтрокиСкрипта.Добавить(" _init_completion 2>/dev/null || {"); - СтрокиСкрипта.Добавить(" COMPREPLY=()"); - СтрокиСкрипта.Добавить(" cur=""${COMP_WORDS[COMP_CWORD]}"""); - СтрокиСкрипта.Добавить(" prev=""${COMP_WORDS[COMP_CWORD-1]}"""); - СтрокиСкрипта.Добавить(" words=(""${COMP_WORDS[@]}"")"); - СтрокиСкрипта.Добавить(" cword=$COMP_CWORD"); - СтрокиСкрипта.Добавить(" }"); - СтрокиСкрипта.Добавить(""); - - ВсеИменаВерхнегоУровня = Новый Массив(); - Для Каждого Описание Из Модель Цикл - Для Каждого Имя Из Описание.Имена Цикл - ВсеИменаВерхнегоУровня.Добавить(Имя); - КонецЦикла; - КонецЦикла; - - СтрокиСкрипта.Добавить(" local commands=""" + СтрСоединить(ВсеИменаВерхнегоУровня, " ") + """"); - СтрокиСкрипта.Добавить(""); - СтрокиСкрипта.Добавить(" if [ $cword -eq 1 ]; then"); - СтрокиСкрипта.Добавить(" COMPREPLY=($(compgen -W ""$commands"" -- ""$cur""))"); - СтрокиСкрипта.Добавить(" return 0"); - СтрокиСкрипта.Добавить(" fi"); - СтрокиСкрипта.Добавить(""); - СтрокиСкрипта.Добавить(" local command=""${words[1]}"""); - СтрокиСкрипта.Добавить(" case ""$command"" in"); + ИмяФункции = "_" + БезопасноеИмяФункции(ИмяПриложения) + "_completions"; + + Строки = Новый Массив(); + Строки.Добавить("# " + ИмяПриложения + " bash completions"); + Строки.Добавить("# Добавьте строку ниже в ~/.bashrc для активации:"); + Строки.Добавить("# source <(" + ИмяПриложения + " completions --shell bash)"); + Строки.Добавить(""); + Строки.Добавить(ИмяФункции + "() {"); + Строки.Добавить(" local cur prev words cword"); + Строки.Добавить(" _init_completion 2>/dev/null || {"); + Строки.Добавить(" COMPREPLY=()"); + Строки.Добавить(" cur=""${COMP_WORDS[COMP_CWORD]}"""); + Строки.Добавить(" prev=""${COMP_WORDS[COMP_CWORD-1]}"""); + Строки.Добавить(" words=(""${COMP_WORDS[@]}"")"); + Строки.Добавить(" cword=$COMP_CWORD"); + Строки.Добавить(" }"); + Строки.Добавить(""); + Строки.Добавить(" local commands=""" + СтрСоединить(СобратьИменаВсех(Модель), " ") + """"); + Строки.Добавить(""); + Строки.Добавить(" if [ $cword -eq 1 ]; then"); + Строки.Добавить(" COMPREPLY=($(compgen -W ""$commands"" -- ""$cur""))"); + Строки.Добавить(" return 0"); + Строки.Добавить(" fi"); + Строки.Добавить(""); + Строки.Добавить(" local command=""${words[1]-}"""); + Строки.Добавить(" case ""$command"" in"); Для Каждого Описание Из Модель Цикл - ДобавитьВеткуКоманды(СтрокиСкрипта, Описание, " "); + ДобавитьВеткуКоманды(Строки, Описание, " ", 1); КонецЦикла; - СтрокиСкрипта.Добавить(" *)"); - СтрокиСкрипта.Добавить(" ;;"); - СтрокиСкрипта.Добавить(" esac"); - СтрокиСкрипта.Добавить(""); - СтрокиСкрипта.Добавить(" return 0"); - СтрокиСкрипта.Добавить("}"); - СтрокиСкрипта.Добавить(""); - СтрокиСкрипта.Добавить("complete -F " + ИмяФункции + " " + ИмяПриложения); + Строки.Добавить(" *)"); + Строки.Добавить(" ;;"); + Строки.Добавить(" esac"); + Строки.Добавить(""); + Строки.Добавить(" return 0"); + Строки.Добавить("}"); + Строки.Добавить(""); + Строки.Добавить("complete -F " + ИмяФункции + " " + ИмяПриложения); - Возврат СтрСоединить(СтрокиСкрипта, Символы.ПС); + Возврат СтрСоединить(Строки, Символы.ПС); КонецФункции -Процедура ДобавитьВеткуКоманды(СтрокиСкрипта, Описание, Отступ) +Процедура ДобавитьВеткуКоманды(Строки, Описание, Отступ, Глубина) - ПаттернCase = СтрСоединить(Описание.Имена, "|"); - СтрокиСкрипта.Добавить(Отступ + ПаттернCase + ")"); + Строки.Добавить(Отступ + СтрСоединить(Описание.Имена, "|") + ")"); - ИменаОпций = ИменаПараметровДляКомплита(Описание.Опции); - - Для Каждого Опция Из Описание.Опции Цикл - Если Опция.Значения.Количество() = 0 Тогда - Продолжить; - КонецЕсли; - - Для Каждого ИмяОпции Из Опция.Имена Цикл - СтрокиСкрипта.Добавить(Отступ + " if [ ""$prev"" = """ + ИмяОпции + """ ]; then"); - СтрокиСкрипта.Добавить(Отступ + " COMPREPLY=($(compgen -W """ + СтрСоединить(Опция.Значения, " ") + """ -- ""$cur""))"); - СтрокиСкрипта.Добавить(Отступ + " return 0"); - СтрокиСкрипта.Добавить(Отступ + " fi"); - КонецЦикла; + Для Каждого КлючЗначение Из Описание.ЗначенияПоОпции Цикл + Строки.Добавить(Отступ + " if [ ""$prev"" = """ + КлючЗначение.Ключ + """ ]; then"); + Строки.Добавить(Отступ + " COMPREPLY=($(compgen -W """ + СтрСоединить(КлючЗначение.Значение, " ") + """ -- ""$cur""))"); + Строки.Добавить(Отступ + " return 0"); + Строки.Добавить(Отступ + " fi"); КонецЦикла; - СтрокиСкрипта.Добавить(Отступ + " if [[ ""$cur"" == -* ]]; then"); - СтрокиСкрипта.Добавить(Отступ + " COMPREPLY=($(compgen -W """ + СтрСоединить(ИменаОпций, " ") + """ -- ""$cur""))"); - СтрокиСкрипта.Добавить(Отступ + " return 0"); - СтрокиСкрипта.Добавить(Отступ + " fi"); + Строки.Добавить(Отступ + " if [[ ""$cur"" == -* ]]; then"); + Строки.Добавить(Отступ + " COMPREPLY=($(compgen -W """ + СтрСоединить(Описание.ИменаОпций, " ") + """ -- ""$cur""))"); + Строки.Добавить(Отступ + " return 0"); + Строки.Добавить(Отступ + " fi"); Если Описание.Подкоманды.Количество() > 0 Тогда - ИменаПодкоманд = Новый Массив(); - Для Каждого Под Из Описание.Подкоманды Цикл - Для Каждого Имя Из Под.Имена Цикл - ИменаПодкоманд.Добавить(Имя); - КонецЦикла; - КонецЦикла; - - СтрокиСкрипта.Добавить(Отступ + " if [ $cword -eq 2 ]; then"); - СтрокиСкрипта.Добавить(Отступ + " COMPREPLY=($(compgen -W """ + СтрСоединить(ИменаПодкоманд, " ") + """ -- ""$cur""))"); - СтрокиСкрипта.Добавить(Отступ + " return 0"); - СтрокиСкрипта.Добавить(Отступ + " fi"); + СледующийИндекс = Формат(Глубина + 1, "ЧГ=0"); + Строки.Добавить(Отступ + " if [ $cword -eq " + СледующийИндекс + " ]; then"); + Строки.Добавить(Отступ + " COMPREPLY=($(compgen -W """ + СтрСоединить(СобратьИменаВсех(Описание.Подкоманды), " ") + """ -- ""$cur""))"); + Строки.Добавить(Отступ + " return 0"); + Строки.Добавить(Отступ + " fi"); - СтрокиСкрипта.Добавить(Отступ + " local subcommand=""${words[2]}"""); - СтрокиСкрипта.Добавить(Отступ + " case ""$subcommand"" in"); + ПеременнаяУровня = "sub" + СледующийИндекс; + Строки.Добавить(Отступ + " local " + ПеременнаяУровня + "=""${words[" + СледующийИндекс + "]-}"""); + Строки.Добавить(Отступ + " case ""$" + ПеременнаяУровня + """ in"); Для Каждого Под Из Описание.Подкоманды Цикл - ДобавитьВеткуПодкоманды(СтрокиСкрипта, Под, Отступ + " "); + ДобавитьВеткуКоманды(Строки, Под, Отступ + " ", Глубина + 1); КонецЦикла; - СтрокиСкрипта.Добавить(Отступ + " *)"); - СтрокиСкрипта.Добавить(Отступ + " ;;"); - СтрокиСкрипта.Добавить(Отступ + " esac"); + Строки.Добавить(Отступ + " *)"); + Строки.Добавить(Отступ + " ;;"); + Строки.Добавить(Отступ + " esac"); - Иначе + ИначеЕсли Описание.ЗначенияАргументов.Количество() > 0 Тогда - ЗначенияАргументов = СобратьЗначенияАргументов(Описание); - Если ЗначенияАргументов.Количество() > 0 Тогда - СтрокиСкрипта.Добавить(Отступ + " COMPREPLY=($(compgen -W """ + СтрСоединить(ЗначенияАргументов, " ") + """ -- ""$cur""))"); - КонецЕсли; + Строки.Добавить(Отступ + " COMPREPLY=($(compgen -W """ + СтрСоединить(Описание.ЗначенияАргументов, " ") + """ -- ""$cur""))"); КонецЕсли; - СтрокиСкрипта.Добавить(Отступ + " ;;"); + Строки.Добавить(Отступ + " ;;"); КонецПроцедуры -Процедура ДобавитьВеткуПодкоманды(СтрокиСкрипта, Описание, Отступ) - - ПаттернCase = СтрСоединить(Описание.Имена, "|"); - СтрокиСкрипта.Добавить(Отступ + ПаттернCase + ")"); - - ИменаОпций = ИменаПараметровДляКомплита(Описание.Опции); - - Для Каждого Опция Из Описание.Опции Цикл - Если Опция.Значения.Количество() = 0 Тогда - Продолжить; - КонецЕсли; - Для Каждого ИмяОпции Из Опция.Имена Цикл - СтрокиСкрипта.Добавить(Отступ + " if [ ""$prev"" = """ + ИмяОпции + """ ]; then"); - СтрокиСкрипта.Добавить(Отступ + " COMPREPLY=($(compgen -W """ + СтрСоединить(Опция.Значения, " ") + """ -- ""$cur""))"); - СтрокиСкрипта.Добавить(Отступ + " return 0"); - СтрокиСкрипта.Добавить(Отступ + " fi"); - КонецЦикла; - КонецЦикла; - - СтрокиСкрипта.Добавить(Отступ + " if [[ ""$cur"" == -* ]]; then"); - СтрокиСкрипта.Добавить(Отступ + " COMPREPLY=($(compgen -W """ + СтрСоединить(ИменаОпций, " ") + """ -- ""$cur""))"); - СтрокиСкрипта.Добавить(Отступ + " return 0"); - СтрокиСкрипта.Добавить(Отступ + " fi"); - - ЗначенияАргументов = СобратьЗначенияАргументов(Описание); - Если ЗначенияАргументов.Количество() > 0 Тогда - СтрокиСкрипта.Добавить(Отступ + " COMPREPLY=($(compgen -W """ + СтрСоединить(ЗначенияАргументов, " ") + """ -- ""$cur""))"); - КонецЕсли; - - СтрокиСкрипта.Добавить(Отступ + " ;;"); - -КонецПроцедуры - -Функция ИменаПараметровДляКомплита(Опции) +Функция СобратьИменаВсех(Описания) Результат = Новый Массив(); - Результат.Добавить("--help"); - - Для Каждого Опция Из Опции Цикл - Для Каждого Имя Из Опция.Имена Цикл + Для Каждого Описание Из Описания Цикл + Для Каждого Имя Из Описание.Имена Цикл Результат.Добавить(Имя); КонецЦикла; КонецЦикла; - Возврат Результат; КонецФункции -Функция СобратьЗначенияАргументов(Описание) - - Результат = Новый Массив(); - Для Каждого Аргумент Из Описание.Аргументы Цикл - Для Каждого Значение Из Аргумент.Значения Цикл - Результат.Добавить(Значение); - КонецЦикла; - КонецЦикла; - - Возврат Результат; - -КонецФункции - -Функция ИмяФункцииИзИмени(ИмяПриложения) +Функция БезопасноеИмяФункции(ИмяПриложения) Результат = ""; Для Инд = 1 По СтрДлина(ИмяПриложения) Цикл @@ -194,7 +120,6 @@ Результат = Результат + "_"; КонецЕсли; КонецЦикла; - Возврат Результат; КонецФункции diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Pwsh.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Pwsh.os" index 9531bbf..444824b 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Pwsh.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Pwsh.os" @@ -1,4 +1,8 @@ // Генератор скрипта автодополнения для PowerShell. +// Эмитит дерево команд литералом-хэш-таблицей и небольшой обходчик +// токенов, что позволяет корректно работать при произвольной глубине +// подкоманд и при наличии опций/аргументов между родителем и подкомандой +// (cli парсит подкоманду не по фиксированной позиции). Функция Оболочка() Экспорт Возврат "pwsh"; @@ -14,42 +18,57 @@ Строки.Добавить("Register-ArgumentCompleter -Native -CommandName '" + ИмяПриложения + "' -ScriptBlock {"); Строки.Добавить(" param($wordToComplete, $commandAst, $cursorPosition)"); Строки.Добавить(""); - Строки.Добавить(" $tokens = $commandAst.CommandElements | ForEach-Object { $_.Extent.Text }"); - Строки.Добавить(" $command = if ($tokens.Count -ge 2) { $tokens[1] } else { '' }"); - Строки.Добавить(" $subcommand = if ($tokens.Count -ge 3) { $tokens[2] } else { '' }"); - Строки.Добавить(" $prev = if ($tokens.Count -ge 2) { $tokens[$tokens.Count - 2] } else { '' }"); - Строки.Добавить(" $position = $tokens.Count - 1"); - Строки.Добавить(" if (-not [string]::IsNullOrEmpty($wordToComplete)) { $position = $position - 1 }"); + Строки.Добавить(" $__tree = " + СобратьХешКорня(Модель, " ")); Строки.Добавить(""); - - СтрокаКоманд = " $commands = @("; - ПерваяКоманда = Истина; - Для Каждого Описание Из Модель Цикл - Для Каждого Имя Из Описание.Имена Цикл - Если Не ПерваяКоманда Тогда - СтрокаКоманд = СтрокаКоманд + ", "; - КонецЕсли; - СтрокаКоманд = СтрокаКоманд + "'" + Имя + "'"; - ПерваяКоманда = Ложь; - КонецЦикла; - КонецЦикла; - СтрокаКоманд = СтрокаКоманд + ")"; - Строки.Добавить(СтрокаКоманд); - + Строки.Добавить(" $tokens = @($commandAst.CommandElements | ForEach-Object { $_.Extent.Text })"); + Строки.Добавить(" $lastIndex = $tokens.Count - 1"); + Строки.Добавить(" $partial = -not [string]::IsNullOrEmpty($wordToComplete) -and $lastIndex -ge 1 -and $tokens[$lastIndex] -eq $wordToComplete"); + Строки.Добавить(" if ($partial) { $endWalk = $lastIndex } else { $endWalk = $lastIndex + 1 }"); + Строки.Добавить(""); + Строки.Добавить(" $node = $__tree"); + Строки.Добавить(" $i = 1"); + Строки.Добавить(" while ($i -lt $endWalk) {"); + Строки.Добавить(" $tok = $tokens[$i]"); + Строки.Добавить(" if ($tok.StartsWith('-')) {"); + Строки.Добавить(" if ($node.OptionValues.ContainsKey($tok) -and ($i + 1) -lt $endWalk) {"); + Строки.Добавить(" $i += 2"); + Строки.Добавить(" } else {"); + Строки.Добавить(" $i += 1"); + Строки.Добавить(" }"); + Строки.Добавить(" } elseif ($node.Commands.ContainsKey($tok)) {"); + Строки.Добавить(" $node = $node.Commands[$tok]"); + Строки.Добавить(" $i += 1"); + Строки.Добавить(" } elseif ($node.AliasMap.ContainsKey($tok)) {"); + Строки.Добавить(" $node = $node.Commands[$node.AliasMap[$tok]]"); + Строки.Добавить(" $i += 1"); + Строки.Добавить(" } else {"); + Строки.Добавить(" $i += 1"); + Строки.Добавить(" }"); + Строки.Добавить(" }"); Строки.Добавить(""); - Строки.Добавить(" if ($position -le 1) {"); - Строки.Добавить(" return $commands | Where-Object { $_ -like ""$wordToComplete*"" } | ForEach-Object {"); + Строки.Добавить(" $prev = if ($endWalk -ge 2) { $tokens[$endWalk - 1] } else { '' }"); + Строки.Добавить(""); + Строки.Добавить(" if (-not [string]::IsNullOrEmpty($wordToComplete) -and $wordToComplete.StartsWith('-')) {"); + Строки.Добавить(" return $node.OptionNames | Where-Object { $_ -like ""$wordToComplete*"" } | ForEach-Object {"); Строки.Добавить(" [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)"); Строки.Добавить(" }"); Строки.Добавить(" }"); Строки.Добавить(""); - Строки.Добавить(" switch ($command) {"); - - Для Каждого Описание Из Модель Цикл - ДобавитьВеткуКоманды(Строки, Описание, " "); - КонецЦикла; - - Строки.Добавить(" default { }"); + Строки.Добавить(" if ($node.OptionValues.ContainsKey($prev)) {"); + Строки.Добавить(" return $node.OptionValues[$prev] | Where-Object { $_ -like ""$wordToComplete*"" } | ForEach-Object {"); + Строки.Добавить(" [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)"); + Строки.Добавить(" }"); + Строки.Добавить(" }"); + Строки.Добавить(""); + Строки.Добавить(" $candidates = @()"); + Строки.Добавить(" if ($node.Commands.Count -gt 0) {"); + Строки.Добавить(" $candidates = @($node.Commands.Keys) + @($node.AliasMap.Keys)"); + Строки.Добавить(" } elseif ($node.ArgValues.Count -gt 0) {"); + Строки.Добавить(" $candidates = $node.ArgValues"); + Строки.Добавить(" }"); + Строки.Добавить(""); + Строки.Добавить(" return $candidates | Where-Object { $_ -like ""$wordToComplete*"" } | ForEach-Object {"); + Строки.Добавить(" [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)"); Строки.Добавить(" }"); Строки.Добавить("}"); @@ -57,103 +76,108 @@ КонецФункции -Процедура ДобавитьВеткуКоманды(Строки, Описание, Отступ) - - Для Каждого Имя Из Описание.Имена Цикл - Строки.Добавить(Отступ + "'" + Имя + "' {"); - - Если Описание.Подкоманды.Количество() > 0 Тогда - ПодкомандыМассив = "@("; - ПерваяП = Истина; - Для Каждого Под Из Описание.Подкоманды Цикл - Для Каждого ИмяП Из Под.Имена Цикл - Если Не ПерваяП Тогда - ПодкомандыМассив = ПодкомандыМассив + ", "; - КонецЕсли; - ПодкомандыМассив = ПодкомандыМассив + "'" + ИмяП + "'"; - ПерваяП = Ложь; - КонецЦикла; - КонецЦикла; - ПодкомандыМассив = ПодкомандыМассив + ")"; - - Строки.Добавить(Отступ + " $subs = " + ПодкомандыМассив); - Строки.Добавить(Отступ + " if ($position -le 2) {"); - Строки.Добавить(Отступ + " return $subs | Where-Object { $_ -like ""$wordToComplete*"" } | ForEach-Object {"); - Строки.Добавить(Отступ + " [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)"); - Строки.Добавить(Отступ + " }"); - Строки.Добавить(Отступ + " }"); - - Строки.Добавить(Отступ + " switch ($subcommand) {"); - Для Каждого Под Из Описание.Подкоманды Цикл - ДобавитьВеткуПодкоманды(Строки, Под, Отступ + " "); - КонецЦикла; - Строки.Добавить(Отступ + " default { }"); - Строки.Добавить(Отступ + " }"); - Иначе - ДобавитьТелоПараметров(Строки, Описание, Отступ + " "); - КонецЕсли; - - Строки.Добавить(Отступ + " return"); - Строки.Добавить(Отступ + "}"); - КонецЦикла; +Функция СобратьХешКорня(Модель, Отступ) + + Корень = Новый Структура("ИмяКаноническое,Алиасы,ИменаОпций,ЗначенияПоОпции,ЗначенияАргументов,Подкоманды"); + Корень.ИмяКаноническое = ""; + Корень.Алиасы = Новый Массив; + Корень.ИменаОпций = Новый Массив; + Корень.ЗначенияПоОпции = Новый Соответствие; + Корень.ЗначенияАргументов = Новый Массив; + Корень.Подкоманды = Модель; + + Возврат СобратьХешУзла(Корень, Отступ); + +КонецФункции + +Функция СобратьХешУзла(Описание, Отступ) + + ВнутреннийОтступ = Отступ + " "; + + Элементы = Новый Массив(); + Элементы.Добавить(ВнутреннийОтступ + "Commands = " + СобратьХешПодкоманд(Описание.Подкоманды, ВнутреннийОтступ)); + Элементы.Добавить(ВнутреннийОтступ + "AliasMap = " + СобратьХешАлиасов(Описание.Подкоманды, ВнутреннийОтступ)); + Элементы.Добавить(ВнутреннийОтступ + "OptionNames = " + МассивЛитералом(Описание.ИменаОпций)); + Элементы.Добавить(ВнутреннийОтступ + "OptionValues = " + СобратьХешЗначенийОпций(Описание.ЗначенияПоОпции, ВнутреннийОтступ)); + Элементы.Добавить(ВнутреннийОтступ + "ArgValues = " + МассивЛитералом(Описание.ЗначенияАргументов)); + + Возврат "@{" + Символы.ПС + СтрСоединить(Элементы, Символы.ПС) + Символы.ПС + Отступ + "}"; + +КонецФункции -КонецПроцедуры +Функция СобратьХешПодкоманд(Подкоманды, Отступ) -Процедура ДобавитьВеткуПодкоманды(Строки, Описание, Отступ) + Если Подкоманды.Количество() = 0 Тогда + Возврат "@{}"; + КонецЕсли; - Для Каждого Имя Из Описание.Имена Цикл - Строки.Добавить(Отступ + "'" + Имя + "' {"); - ДобавитьТелоПараметров(Строки, Описание, Отступ + " "); - Строки.Добавить(Отступ + " return"); - Строки.Добавить(Отступ + "}"); + ВнутреннийОтступ = Отступ + " "; + Пары = Новый Массив(); + Для Каждого Под Из Подкоманды Цикл + Пары.Добавить(ВнутреннийОтступ + ВКавычкахPS(Под.ИмяКаноническое) + " = " + СобратьХешУзла(Под, ВнутреннийОтступ)); КонецЦикла; -КонецПроцедуры - -Процедура ДобавитьТелоПараметров(Строки, Описание, Отступ) - - Для Каждого Опция Из Описание.Опции Цикл - Если Опция.Значения.Количество() = 0 Тогда - Продолжить; - КонецЕсли; - Для Каждого ИмяОпции Из Опция.Имена Цикл - МассивЗначений = "@("; - ПерваяЗ = Истина; - Для Каждого Значение Из Опция.Значения Цикл - Если Не ПерваяЗ Тогда - МассивЗначений = МассивЗначений + ", "; - КонецЕсли; - МассивЗначений = МассивЗначений + "'" + Значение + "'"; - ПерваяЗ = Ложь; - КонецЦикла; - МассивЗначений = МассивЗначений + ")"; - Строки.Добавить(Отступ + "if ($prev -eq '" + ИмяОпции + "') {"); - Строки.Добавить(Отступ + " return " + МассивЗначений + " | Where-Object { $_ -like ""$wordToComplete*"" } | ForEach-Object {"); - Строки.Добавить(Отступ + " [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)"); - Строки.Добавить(Отступ + " }"); - Строки.Добавить(Отступ + "}"); + Возврат "@{" + Символы.ПС + СтрСоединить(Пары, Символы.ПС) + Символы.ПС + Отступ + "}"; + +КонецФункции + +Функция СобратьХешАлиасов(Подкоманды, Отступ) + + Пары = Новый Массив(); + Для Каждого Под Из Подкоманды Цикл + Для Каждого Алиас Из Под.Алиасы Цикл + Пары.Добавить(ВКавычкахPS(Алиас) + " = " + ВКавычкахPS(Под.ИмяКаноническое)); КонецЦикла; КонецЦикла; - ЕстьЗначенияАргументов = Ложь; - МассивАргументов = "@("; - ПерваяА = Истина; - Для Каждого Аргумент Из Описание.Аргументы Цикл - Для Каждого Значение Из Аргумент.Значения Цикл - Если Не ПерваяА Тогда - МассивАргументов = МассивАргументов + ", "; - КонецЕсли; - МассивАргументов = МассивАргументов + "'" + Значение + "'"; - ПерваяА = Ложь; - ЕстьЗначенияАргументов = Истина; - КонецЦикла; + Если Пары.Количество() = 0 Тогда + Возврат "@{}"; + КонецЕсли; + + ВнутреннийОтступ = Отступ + " "; + ПерепакованныеПары = Новый Массив(); + Для Каждого Пара Из Пары Цикл + ПерепакованныеПары.Добавить(ВнутреннийОтступ + Пара); + КонецЦикла; + + Возврат "@{" + Символы.ПС + СтрСоединить(ПерепакованныеПары, Символы.ПС) + Символы.ПС + Отступ + "}"; + +КонецФункции + +Функция СобратьХешЗначенийОпций(ЗначенияПоОпции, Отступ) + + Если ЗначенияПоОпции.Количество() = 0 Тогда + Возврат "@{}"; + КонецЕсли; + + ВнутреннийОтступ = Отступ + " "; + Пары = Новый Массив(); + Для Каждого КлючЗначение Из ЗначенияПоОпции Цикл + Пары.Добавить(ВнутреннийОтступ + ВКавычкахPS(КлючЗначение.Ключ) + " = " + МассивЛитералом(КлючЗначение.Значение)); КонецЦикла; - МассивАргументов = МассивАргументов + ")"; - Если ЕстьЗначенияАргументов Тогда - Строки.Добавить(Отступ + "return " + МассивАргументов + " | Where-Object { $_ -like ""$wordToComplete*"" } | ForEach-Object {"); - Строки.Добавить(Отступ + " [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)"); - Строки.Добавить(Отступ + "}"); + Возврат "@{" + Символы.ПС + СтрСоединить(Пары, Символы.ПС) + Символы.ПС + Отступ + "}"; + +КонецФункции + +Функция МассивЛитералом(Значения) + + Если Значения.Количество() = 0 Тогда + Возврат "@()"; КонецЕсли; -КонецПроцедуры + Элементы = Новый Массив(); + Для Каждого Значение Из Значения Цикл + Элементы.Добавить(ВКавычкахPS(Значение)); + КонецЦикла; + + Возврат "@(" + СтрСоединить(Элементы, ", ") + ")"; + +КонецФункции + +Функция ВКавычкахPS(Значение) + + Экранированное = СтрЗаменить(Значение, "'", "''"); + Возврат "'" + Экранированное + "'"; + +КонецФункции diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Zsh.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Zsh.os" index 71fe0fd..618ddde 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Zsh.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Zsh.os" @@ -6,8 +6,7 @@ Функция СформироватьСкрипт(ИмяПриложения, Модель) Экспорт - БезопасноеИмя = БезопасноеИмяФункции(ИмяПриложения); - ИмяФункции = "_" + БезопасноеИмя; + ИмяФункции = "_" + БезопасноеИмяФункции(ИмяПриложения); Строки = Новый Массив(); Строки.Добавить("#compdef " + ИмяПриложения); @@ -18,16 +17,7 @@ Строки.Добавить(ИмяФункции + "() {"); Строки.Добавить(" local -a commands"); Строки.Добавить(" local context curcontext=""$curcontext"" state line"); - - СтрокаКоманд = " commands=("; - Для Каждого Описание Из Модель Цикл - Для Каждого Имя Из Описание.Имена Цикл - СтрокаКоманд = СтрокаКоманд + " " + Имя; - КонецЦикла; - КонецЦикла; - СтрокаКоманд = СтрокаКоманд + " )"; - Строки.Добавить(СтрокаКоманд); - + Строки.Добавить(" commands=( " + СтрСоединить(СобратьИменаВсех(Модель), " ") + " )"); Строки.Добавить(" _arguments -C \"); Строки.Добавить(" ""1: :->command"" \"); Строки.Добавить(" ""*:: :->args"""); @@ -40,7 +30,7 @@ Строки.Добавить(" case $words[1] in"); Для Каждого Описание Из Модель Цикл - ДобавитьВеткуКоманды(Строки, Описание, " "); + ДобавитьВеткуКоманды(Строки, Описание, " ", 1); КонецЦикла; Строки.Добавить(" *) ;;"); @@ -55,76 +45,52 @@ КонецФункции -Процедура ДобавитьВеткуКоманды(Строки, Описание, Отступ) +Процедура ДобавитьВеткуКоманды(Строки, Описание, Отступ, Глубина) - ПаттернCase = СтрСоединить(Описание.Имена, "|"); - Строки.Добавить(Отступ + ПаттернCase + ")"); + Строки.Добавить(Отступ + СтрСоединить(Описание.Имена, "|") + ")"); + + Для Каждого КлючЗначение Из Описание.ЗначенияПоОпции Цикл + Строки.Добавить(Отступ + " if [[ $words[CURRENT-1] == """ + КлючЗначение.Ключ + """ ]]; then"); + Строки.Добавить(Отступ + " _values """ + КлючЗначение.Ключ + """ " + СтрСоединить(КлючЗначение.Значение, " ")); + Строки.Добавить(Отступ + " return"); + Строки.Добавить(Отступ + " fi"); + КонецЦикла; Если Описание.Подкоманды.Количество() > 0 Тогда - ПодкомандыСтр = ""; - Для Каждого Под Из Описание.Подкоманды Цикл - Для Каждого Имя Из Под.Имена Цикл - ПодкомандыСтр = ПодкомандыСтр + " " + Имя; - КонецЦикла; - КонецЦикла; - Строки.Добавить(Отступ + " if (( CURRENT == 2 )); then"); - Строки.Добавить(Отступ + " _values ""subcommand""" + ПодкомандыСтр); + СледующийИндекс = Формат(Глубина + 1, "ЧГ=0"); + Строки.Добавить(Отступ + " if (( CURRENT == " + СледующийИндекс + " )); then"); + Строки.Добавить(Отступ + " _values ""subcommand"" " + СтрСоединить(СобратьИменаВсех(Описание.Подкоманды), " ")); Строки.Добавить(Отступ + " else"); - Строки.Добавить(Отступ + " case $words[2] in"); + Строки.Добавить(Отступ + " case $words[" + СледующийИндекс + "] in"); Для Каждого Под Из Описание.Подкоманды Цикл - ДобавитьВеткуПараметров(Строки, Под, Отступ + " "); + ДобавитьВеткуКоманды(Строки, Под, Отступ + " ", Глубина + 1); КонецЦикла; Строки.Добавить(Отступ + " *) ;;"); Строки.Добавить(Отступ + " esac"); Строки.Добавить(Отступ + " fi"); - Иначе - ДобавитьТелоПараметров(Строки, Описание, Отступ + " "); - КонецЕсли; + ИначеЕсли Описание.ЗначенияАргументов.Количество() > 0 Тогда - Строки.Добавить(Отступ + " ;;"); + Строки.Добавить(Отступ + " _values ""argument"" " + СтрСоединить(Описание.ЗначенияАргументов, " ")); -КонецПроцедуры - -Процедура ДобавитьВеткуПараметров(Строки, Описание, Отступ) + КонецЕсли; - ПаттернCase = СтрСоединить(Описание.Имена, "|"); - Строки.Добавить(Отступ + ПаттернCase + ")"); - ДобавитьТелоПараметров(Строки, Описание, Отступ + " "); Строки.Добавить(Отступ + " ;;"); КонецПроцедуры -Процедура ДобавитьТелоПараметров(Строки, Описание, Отступ) - - Для Каждого Опция Из Описание.Опции Цикл - Если Опция.Значения.Количество() = 0 Тогда - Продолжить; - КонецЕсли; - Для Каждого ИмяОпции Из Опция.Имена Цикл - Строки.Добавить(Отступ + "if [[ $words[CURRENT-1] == """ + ИмяОпции + """ ]]; then"); - ЗначенияСтр = ""; - Для Каждого Значение Из Опция.Значения Цикл - ЗначенияСтр = ЗначенияСтр + " " + Значение; - КонецЦикла; - Строки.Добавить(Отступ + " _values """ + ИмяОпции + """" + ЗначенияСтр); - Строки.Добавить(Отступ + " return"); - Строки.Добавить(Отступ + "fi"); - КонецЦикла; - КонецЦикла; +Функция СобратьИменаВсех(Описания) - ЗначенияАргументов = ""; - Для Каждого Аргумент Из Описание.Аргументы Цикл - Для Каждого Значение Из Аргумент.Значения Цикл - ЗначенияАргументов = ЗначенияАргументов + " " + Значение; + Результат = Новый Массив(); + Для Каждого Описание Из Описания Цикл + Для Каждого Имя Из Описание.Имена Цикл + Результат.Добавить(Имя); КонецЦикла; КонецЦикла; - Если Не ПустаяСтрока(ЗначенияАргументов) Тогда - Строки.Добавить(Отступ + "_values ""argument""" + ЗначенияАргументов); - КонецЕсли; + Возврат Результат; -КонецПроцедуры +КонецФункции Функция БезопасноеИмяФункции(ИмяПриложения) diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" index a2ffe01..f702146 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" @@ -10,8 +10,9 @@ Функция ОписаниеКоманды(Команда) Экспорт Команда - .Опция("shell s", "bash", "Командная оболочка, для которой генерируется скрипт") + .Опция("shell s", , "Командная оболочка, для которой генерируется скрипт") .ТПеречисление() + .Обязательный() .Перечисление("bash", "bash", "GNU Bash") .Перечисление("zsh", "zsh", "Z Shell") .Перечисление("pwsh", "pwsh", "PowerShell"); @@ -49,7 +50,7 @@ Функция ПостроитьОписаниеКоманды(КомандаCLI) - Описание = Новый Структура("Имена,Опции,Аргументы,Подкоманды"); + Описание = Новый Структура("Имена,ИмяКаноническое,Алиасы,ИменаОпций,ЗначенияПоОпции,ЗначенияАргументов,Подкоманды"); Имена = Новый Массив; Имена.Добавить(КомандаCLI.ПолучитьИмяКоманды()); @@ -59,18 +60,36 @@ КонецЕсли; КонецЦикла; Описание.Имена = Имена; + Описание.ИмяКаноническое = Имена[0]; - Опции = Новый Массив; + Алиасы = Новый Массив; + Для Инд = 1 По Имена.ВГраница() Цикл + Алиасы.Добавить(Имена[Инд]); + КонецЦикла; + Описание.Алиасы = Алиасы; + + ИменаОпций = Новый Массив; + ИменаОпций.Добавить("--help"); + ЗначенияПоОпции = Новый Соответствие; Для Каждого СтрокаОпции Из КомандаCLI.ПолучитьТаблицуОпций() Цикл - Опции.Добавить(ПостроитьОписаниеПараметра(СтрокаОпции)); + ЕстьЗначения = СтрокаОпции.ЗначенияДополнения.Количество() > 0; + Для Каждого ИмяОпции Из СтрокаОпции.НаименованияПараметров Цикл + ИменаОпций.Добавить(ИмяОпции); + Если ЕстьЗначения Тогда + ЗначенияПоОпции.Вставить(ИмяОпции, СтрокаОпции.ЗначенияДополнения); + КонецЕсли; + КонецЦикла; КонецЦикла; - Описание.Опции = Опции; + Описание.ИменаОпций = ИменаОпций; + Описание.ЗначенияПоОпции = ЗначенияПоОпции; - Аргументы = Новый Массив; + ЗначенияАргументов = Новый Массив; Для Каждого СтрокаАргумента Из КомандаCLI.ПолучитьТаблицуАргументов() Цикл - Аргументы.Добавить(ПостроитьОписаниеПараметра(СтрокаАргумента)); + Для Каждого Значение Из СтрокаАргумента.ЗначенияДополнения Цикл + ЗначенияАргументов.Добавить(Значение); + КонецЦикла; КонецЦикла; - Описание.Аргументы = Аргументы; + Описание.ЗначенияАргументов = ЗначенияАргументов; Описание.Подкоманды = ПостроитьМодельПодкоманд(КомандаCLI); @@ -78,15 +97,6 @@ КонецФункции -Функция ПостроитьОписаниеПараметра(СтрокаТаблицы) - - Описание = Новый Структура("Имена,Значения"); - Описание.Имена = СтрокаТаблицы.НаименованияПараметров; - Описание.Значения = СтрокаТаблицы.ЗначенияДополнения; - Возврат Описание; - -КонецФункции - Функция ВыбратьГенератор(ТипОболочки) Если ТипОболочки = "bash" Тогда From 2e57bf2105523ff29dbcbc84fa099b77df4ccd7d Mon Sep 17 00:00:00 2001 From: Nikita Fedkin Date: Thu, 23 Apr 2026 14:39:42 +0200 Subject: [PATCH 09/11] refactor(completions): generators as visitors over command tree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Вводится общий обход дерева команд ОбходчикКомандДополнения, который вызывает на посетителе (генераторе) методы НачатьКоманду / ПосетитьОпцию / ПосетитьАргумент / ПередПодкомандами / ОткрытьПодкоманду / ЗакрытьПодкоманду / ЗавершитьКоманду. Рекурсия по подкомандам живёт в одном месте. Генераторы bash/zsh/pwsh перестают строить промежуточную модель и работают напрямую с методами КомандаПриложения (ПолучитьТаблицуОпций, ПолучитьТаблицуАргументов, ПолучитьПодкоманды, ПолучитьСинонимы). Bash/Zsh эмитят инкрементально по мере обхода; pwsh выполняет post-order сериализацию хэш-таблицы дерева. Добавить новый шелл = написать ещё один посетитель. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...75\320\265\320\275\320\270\321\217Bash.os" | 242 +++++++++----- ...75\320\265\320\275\320\270\321\217Pwsh.os" | 306 +++++++++++------- ...275\320\265\320\275\320\270\321\217Zsh.os" | 222 +++++++++---- ...20\275\320\265\320\275\320\270\321\217.os" | 40 +++ ...20\275\320\265\320\275\320\270\321\217.os" | 63 +--- 5 files changed, 554 insertions(+), 319 deletions(-) create mode 100644 "src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\261\321\205\320\276\320\264\321\207\320\270\320\272\320\232\320\276\320\274\320\260\320\275\320\264\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Bash.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Bash.os" index 43f690b..d03d395 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Bash.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Bash.os" @@ -1,104 +1,190 @@ -// Генератор скрипта автодополнения для GNU Bash. +// Генератор (посетитель) скрипта автодополнения для GNU Bash. +// Реализует контракт ОбходчикКомандДополнения. + +Перем ИмяПриложенияГлоб; +Перем ИмяФункцииГлоб; Функция Оболочка() Экспорт Возврат "bash"; КонецФункции -Функция СформироватьСкрипт(ИмяПриложения, Модель) Экспорт - - ИмяФункции = "_" + БезопасноеИмяФункции(ИмяПриложения) + "_completions"; - - Строки = Новый Массив(); - Строки.Добавить("# " + ИмяПриложения + " bash completions"); - Строки.Добавить("# Добавьте строку ниже в ~/.bashrc для активации:"); - Строки.Добавить("# source <(" + ИмяПриложения + " completions --shell bash)"); - Строки.Добавить(""); - Строки.Добавить(ИмяФункции + "() {"); - Строки.Добавить(" local cur prev words cword"); - Строки.Добавить(" _init_completion 2>/dev/null || {"); - Строки.Добавить(" COMPREPLY=()"); - Строки.Добавить(" cur=""${COMP_WORDS[COMP_CWORD]}"""); - Строки.Добавить(" prev=""${COMP_WORDS[COMP_CWORD-1]}"""); - Строки.Добавить(" words=(""${COMP_WORDS[@]}"")"); - Строки.Добавить(" cword=$COMP_CWORD"); - Строки.Добавить(" }"); - Строки.Добавить(""); - Строки.Добавить(" local commands=""" + СтрСоединить(СобратьИменаВсех(Модель), " ") + """"); - Строки.Добавить(""); - Строки.Добавить(" if [ $cword -eq 1 ]; then"); - Строки.Добавить(" COMPREPLY=($(compgen -W ""$commands"" -- ""$cur""))"); - Строки.Добавить(" return 0"); - Строки.Добавить(" fi"); - Строки.Добавить(""); - Строки.Добавить(" local command=""${words[1]-}"""); - Строки.Добавить(" case ""$command"" in"); - - Для Каждого Описание Из Модель Цикл - ДобавитьВеткуКоманды(Строки, Описание, " ", 1); - КонецЦикла; +Функция СформироватьСкрипт(ИмяПриложения, КореньКоманд) Экспорт + + ИмяПриложенияГлоб = ИмяПриложения; + ИмяФункцииГлоб = "_" + БезопасноеИмяФункции(ИмяПриложения) + "_completions"; - Строки.Добавить(" *)"); - Строки.Добавить(" ;;"); - Строки.Добавить(" esac"); - Строки.Добавить(""); - Строки.Добавить(" return 0"); - Строки.Добавить("}"); - Строки.Добавить(""); - Строки.Добавить("complete -F " + ИмяФункции + " " + ИмяПриложения); + Буфер = Новый Массив; + Контекст = НовыйКонтекст(0, "", Буфер); - Возврат СтрСоединить(Строки, Символы.ПС); + Обходчик = Новый ОбходчикКомандДополнения(); + Обходчик.Обойти(КореньКоманд, ЭтотОбъект, Контекст); + + Возврат СтрСоединить(Буфер, Символы.ПС); КонецФункции -Процедура ДобавитьВеткуКоманды(Строки, Описание, Отступ, Глубина) +Процедура НачатьКоманду(Команда, Контекст) Экспорт + + Если Контекст.Глубина = 0 Тогда + Б = Контекст.Буфер; + Б.Добавить("# " + ИмяПриложенияГлоб + " bash completions"); + Б.Добавить("# Добавьте строку ниже в ~/.bashrc для активации:"); + Б.Добавить("# source <(" + ИмяПриложенияГлоб + " completions --shell bash)"); + Б.Добавить(""); + Б.Добавить(ИмяФункцииГлоб + "() {"); + Б.Добавить(" local cur prev words cword"); + Б.Добавить(" _init_completion 2>/dev/null || {"); + Б.Добавить(" COMPREPLY=()"); + Б.Добавить(" cur=""${COMP_WORDS[COMP_CWORD]}"""); + Б.Добавить(" prev=""${COMP_WORDS[COMP_CWORD-1]}"""); + Б.Добавить(" words=(""${COMP_WORDS[@]}"")"); + Б.Добавить(" cword=$COMP_CWORD"); + Б.Добавить(" }"); + Иначе + Контекст.Буфер.Добавить(Контекст.Отступ + СтрСоединить(ПолучитьИменаКоманды(Команда), "|") + ")"); + КонецЕсли; + +КонецПроцедуры + +Процедура ПосетитьОпцию(СтрокаОпции, Контекст) Экспорт + + ЕстьЗначения = СтрокаОпции.ЗначенияДополнения.Количество() > 0; + Для Каждого ИмяОпции Из СтрокаОпции.НаименованияПараметров Цикл + Контекст.ИменаОпций.Добавить(ИмяОпции); + Если ЕстьЗначения Тогда + Контекст.ОпцииСоЗначениями.Вставить(ИмяОпции, СтрокаОпции.ЗначенияДополнения); + КонецЕсли; + КонецЦикла; - Строки.Добавить(Отступ + СтрСоединить(Описание.Имена, "|") + ")"); +КонецПроцедуры - Для Каждого КлючЗначение Из Описание.ЗначенияПоОпции Цикл - Строки.Добавить(Отступ + " if [ ""$prev"" = """ + КлючЗначение.Ключ + """ ]; then"); - Строки.Добавить(Отступ + " COMPREPLY=($(compgen -W """ + СтрСоединить(КлючЗначение.Значение, " ") + """ -- ""$cur""))"); - Строки.Добавить(Отступ + " return 0"); - Строки.Добавить(Отступ + " fi"); +Процедура ПосетитьАргумент(СтрокаАргумента, Контекст) Экспорт + + Для Каждого Значение Из СтрокаАргумента.ЗначенияДополнения Цикл + Контекст.ЗначенияАргументов.Добавить(Значение); КонецЦикла; - Строки.Добавить(Отступ + " if [[ ""$cur"" == -* ]]; then"); - Строки.Добавить(Отступ + " COMPREPLY=($(compgen -W """ + СтрСоединить(Описание.ИменаОпций, " ") + """ -- ""$cur""))"); - Строки.Добавить(Отступ + " return 0"); - Строки.Добавить(Отступ + " fi"); +КонецПроцедуры - Если Описание.Подкоманды.Количество() > 0 Тогда +Процедура ПередПодкомандами(Команда, Подкоманды, Контекст) Экспорт + + Б = Контекст.Буфер; + О = Контекст.Отступ; + Контекст.ЕстьПодкоманды = Подкоманды.Количество() > 0; + + Если Контекст.Глубина = 0 Тогда + Б.Добавить(""); + Б.Добавить(" local commands=""" + СтрСоединить(СобратьИменаВсех(Подкоманды), " ") + """"); + Б.Добавить(""); + Б.Добавить(" if [ $cword -eq 1 ]; then"); + Б.Добавить(" COMPREPLY=($(compgen -W ""$commands"" -- ""$cur""))"); + Б.Добавить(" return 0"); + Б.Добавить(" fi"); + Б.Добавить(""); + Б.Добавить(" local command=""${words[1]-}"""); + Б.Добавить(" case ""$command"" in"); + Возврат; + КонецЕсли; - СледующийИндекс = Формат(Глубина + 1, "ЧГ=0"); - Строки.Добавить(Отступ + " if [ $cword -eq " + СледующийИндекс + " ]; then"); - Строки.Добавить(Отступ + " COMPREPLY=($(compgen -W """ + СтрСоединить(СобратьИменаВсех(Описание.Подкоманды), " ") + """ -- ""$cur""))"); - Строки.Добавить(Отступ + " return 0"); - Строки.Добавить(Отступ + " fi"); + Для Каждого КлючЗначение Из Контекст.ОпцииСоЗначениями Цикл + Б.Добавить(О + " if [ ""$prev"" = """ + КлючЗначение.Ключ + """ ]; then"); + Б.Добавить(О + " COMPREPLY=($(compgen -W """ + СтрСоединить(КлючЗначение.Значение, " ") + """ -- ""$cur""))"); + Б.Добавить(О + " return 0"); + Б.Добавить(О + " fi"); + КонецЦикла; - ПеременнаяУровня = "sub" + СледующийИндекс; - Строки.Добавить(Отступ + " local " + ПеременнаяУровня + "=""${words[" + СледующийИндекс + "]-}"""); - Строки.Добавить(Отступ + " case ""$" + ПеременнаяУровня + """ in"); - Для Каждого Под Из Описание.Подкоманды Цикл - ДобавитьВеткуКоманды(Строки, Под, Отступ + " ", Глубина + 1); - КонецЦикла; - Строки.Добавить(Отступ + " *)"); - Строки.Добавить(Отступ + " ;;"); - Строки.Добавить(Отступ + " esac"); + ИменаДляКомплита = Новый Массив; + ИменаДляКомплита.Добавить("--help"); + Для Каждого Имя Из Контекст.ИменаОпций Цикл + ИменаДляКомплита.Добавить(Имя); + КонецЦикла; + Б.Добавить(О + " if [[ ""$cur"" == -* ]]; then"); + Б.Добавить(О + " COMPREPLY=($(compgen -W """ + СтрСоединить(ИменаДляКомплита, " ") + """ -- ""$cur""))"); + Б.Добавить(О + " return 0"); + Б.Добавить(О + " fi"); + + Если Контекст.ЕстьПодкоманды Тогда + СледИндекс = Формат(Контекст.Глубина + 1, "ЧГ=0"); + Б.Добавить(О + " if [ $cword -eq " + СледИндекс + " ]; then"); + Б.Добавить(О + " COMPREPLY=($(compgen -W """ + СтрСоединить(СобратьИменаВсех(Подкоманды), " ") + """ -- ""$cur""))"); + Б.Добавить(О + " return 0"); + Б.Добавить(О + " fi"); + Б.Добавить(О + " local sub" + СледИндекс + "=""${words[" + СледИндекс + "]-}"""); + Б.Добавить(О + " case ""$sub" + СледИндекс + """ in"); + ИначеЕсли Контекст.ЗначенияАргументов.Количество() > 0 Тогда + Б.Добавить(О + " COMPREPLY=($(compgen -W """ + СтрСоединить(Контекст.ЗначенияАргументов, " ") + """ -- ""$cur""))"); + КонецЕсли; + +КонецПроцедуры - ИначеЕсли Описание.ЗначенияАргументов.Количество() > 0 Тогда +Функция ОткрытьПодкоманду(Подкоманда, Контекст) Экспорт - Строки.Добавить(Отступ + " COMPREPLY=($(compgen -W """ + СтрСоединить(Описание.ЗначенияАргументов, " ") + """ -- ""$cur""))"); + Возврат НовыйКонтекст(Контекст.Глубина + 1, Контекст.Отступ + " ", Контекст.Буфер); + +КонецФункции +Процедура ЗакрытьПодкоманду(Подкоманда, ПодчинённыйКонтекст, Контекст) Экспорт + // Для bash дочерний узел сам завершает свою ветку case в ЗавершитьКоманду. +КонецПроцедуры + +Процедура ЗавершитьКоманду(Команда, Контекст) Экспорт + + Б = Контекст.Буфер; + О = Контекст.Отступ; + + Если Контекст.Глубина = 0 Тогда + Б.Добавить(" *)"); + Б.Добавить(" ;;"); + Б.Добавить(" esac"); + Б.Добавить(""); + Б.Добавить(" return 0"); + Б.Добавить("}"); + Б.Добавить(""); + Б.Добавить("complete -F " + ИмяФункцииГлоб + " " + ИмяПриложенияГлоб); + Возврат; КонецЕсли; - Строки.Добавить(Отступ + " ;;"); + Если Контекст.ЕстьПодкоманды Тогда + Б.Добавить(О + " *)"); + Б.Добавить(О + " ;;"); + Б.Добавить(О + " esac"); + КонецЕсли; + Б.Добавить(О + " ;;"); КонецПроцедуры -Функция СобратьИменаВсех(Описания) +Функция НовыйКонтекст(Глубина, Отступ, Буфер) + + К = Новый Структура(); + К.Вставить("Глубина", Глубина); + К.Вставить("Отступ", Отступ); + К.Вставить("Буфер", Буфер); + К.Вставить("ИменаОпций", Новый Массив); + К.Вставить("ОпцииСоЗначениями", Новый Соответствие); + К.Вставить("ЗначенияАргументов", Новый Массив); + К.Вставить("ЕстьПодкоманды", Ложь); + Возврат К; + +КонецФункции + +Функция ПолучитьИменаКоманды(Команда) + + Имена = Новый Массив; + Имена.Добавить(Команда.ПолучитьИмяКоманды()); + Для Каждого Синоним Из Команда.ПолучитьСинонимы() Цикл + Если Синоним <> Команда.ПолучитьИмяКоманды() Тогда + Имена.Добавить(Синоним); + КонецЕсли; + КонецЦикла; + Возврат Имена; + +КонецФункции + +Функция СобратьИменаВсех(Подкоманды) - Результат = Новый Массив(); - Для Каждого Описание Из Описания Цикл - Для Каждого Имя Из Описание.Имена Цикл + Результат = Новый Массив; + Для Каждого Подкоманда Из Подкоманды Цикл + Для Каждого Имя Из ПолучитьИменаКоманды(Подкоманда) Цикл Результат.Добавить(Имя); КонецЦикла; КонецЦикла; @@ -106,11 +192,11 @@ КонецФункции -Функция БезопасноеИмяФункции(ИмяПриложения) +Функция БезопасноеИмяФункции(Имя) Результат = ""; - Для Инд = 1 По СтрДлина(ИмяПриложения) Цикл - Символ = Сред(ИмяПриложения, Инд, 1); + Для Инд = 1 По СтрДлина(Имя) Цикл + Символ = Сред(Имя, Инд, 1); Если (Символ >= "a" И Символ <= "z") Или (Символ >= "A" И Символ <= "Z") Или (Символ >= "0" И Символ <= "9") diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Pwsh.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Pwsh.os" index 444824b..9d222c2 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Pwsh.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Pwsh.os" @@ -1,159 +1,210 @@ -// Генератор скрипта автодополнения для PowerShell. +// Генератор (посетитель) скрипта автодополнения для PowerShell. // Эмитит дерево команд литералом-хэш-таблицей и небольшой обходчик // токенов, что позволяет корректно работать при произвольной глубине -// подкоманд и при наличии опций/аргументов между родителем и подкомандой -// (cli парсит подкоманду не по фиксированной позиции). +// подкоманд и при наличии опций/аргументов между родителем и подкомандой. +// Реализует контракт ОбходчикКомандДополнения. + +Перем ИмяПриложенияГлоб; Функция Оболочка() Экспорт Возврат "pwsh"; КонецФункции -Функция СформироватьСкрипт(ИмяПриложения, Модель) Экспорт - - Строки = Новый Массив(); - Строки.Добавить("# " + ИмяПриложения + " PowerShell completions"); - Строки.Добавить("# Активируйте строкой в профиле PowerShell ($PROFILE):"); - Строки.Добавить("# " + ИмяПриложения + " completions --shell pwsh | Out-String | Invoke-Expression"); - Строки.Добавить(""); - Строки.Добавить("Register-ArgumentCompleter -Native -CommandName '" + ИмяПриложения + "' -ScriptBlock {"); - Строки.Добавить(" param($wordToComplete, $commandAst, $cursorPosition)"); - Строки.Добавить(""); - Строки.Добавить(" $__tree = " + СобратьХешКорня(Модель, " ")); - Строки.Добавить(""); - Строки.Добавить(" $tokens = @($commandAst.CommandElements | ForEach-Object { $_.Extent.Text })"); - Строки.Добавить(" $lastIndex = $tokens.Count - 1"); - Строки.Добавить(" $partial = -not [string]::IsNullOrEmpty($wordToComplete) -and $lastIndex -ge 1 -and $tokens[$lastIndex] -eq $wordToComplete"); - Строки.Добавить(" if ($partial) { $endWalk = $lastIndex } else { $endWalk = $lastIndex + 1 }"); - Строки.Добавить(""); - Строки.Добавить(" $node = $__tree"); - Строки.Добавить(" $i = 1"); - Строки.Добавить(" while ($i -lt $endWalk) {"); - Строки.Добавить(" $tok = $tokens[$i]"); - Строки.Добавить(" if ($tok.StartsWith('-')) {"); - Строки.Добавить(" if ($node.OptionValues.ContainsKey($tok) -and ($i + 1) -lt $endWalk) {"); - Строки.Добавить(" $i += 2"); - Строки.Добавить(" } else {"); - Строки.Добавить(" $i += 1"); - Строки.Добавить(" }"); - Строки.Добавить(" } elseif ($node.Commands.ContainsKey($tok)) {"); - Строки.Добавить(" $node = $node.Commands[$tok]"); - Строки.Добавить(" $i += 1"); - Строки.Добавить(" } elseif ($node.AliasMap.ContainsKey($tok)) {"); - Строки.Добавить(" $node = $node.Commands[$node.AliasMap[$tok]]"); - Строки.Добавить(" $i += 1"); - Строки.Добавить(" } else {"); - Строки.Добавить(" $i += 1"); - Строки.Добавить(" }"); - Строки.Добавить(" }"); - Строки.Добавить(""); - Строки.Добавить(" $prev = if ($endWalk -ge 2) { $tokens[$endWalk - 1] } else { '' }"); - Строки.Добавить(""); - Строки.Добавить(" if (-not [string]::IsNullOrEmpty($wordToComplete) -and $wordToComplete.StartsWith('-')) {"); - Строки.Добавить(" return $node.OptionNames | Where-Object { $_ -like ""$wordToComplete*"" } | ForEach-Object {"); - Строки.Добавить(" [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)"); - Строки.Добавить(" }"); - Строки.Добавить(" }"); - Строки.Добавить(""); - Строки.Добавить(" if ($node.OptionValues.ContainsKey($prev)) {"); - Строки.Добавить(" return $node.OptionValues[$prev] | Where-Object { $_ -like ""$wordToComplete*"" } | ForEach-Object {"); - Строки.Добавить(" [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)"); - Строки.Добавить(" }"); - Строки.Добавить(" }"); - Строки.Добавить(""); - Строки.Добавить(" $candidates = @()"); - Строки.Добавить(" if ($node.Commands.Count -gt 0) {"); - Строки.Добавить(" $candidates = @($node.Commands.Keys) + @($node.AliasMap.Keys)"); - Строки.Добавить(" } elseif ($node.ArgValues.Count -gt 0) {"); - Строки.Добавить(" $candidates = $node.ArgValues"); - Строки.Добавить(" }"); - Строки.Добавить(""); - Строки.Добавить(" return $candidates | Where-Object { $_ -like ""$wordToComplete*"" } | ForEach-Object {"); - Строки.Добавить(" [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)"); - Строки.Добавить(" }"); - Строки.Добавить("}"); - - Возврат СтрСоединить(Строки, Символы.ПС); +Функция СформироватьСкрипт(ИмяПриложения, КореньКоманд) Экспорт + + ИмяПриложенияГлоб = ИмяПриложения; + + Буфер = Новый Массив; + Контекст = НовыйКонтекст(0, " ", Буфер); + + Обходчик = Новый ОбходчикКомандДополнения(); + Обходчик.Обойти(КореньКоманд, ЭтотОбъект, Контекст); + + Возврат СтрСоединить(Буфер, Символы.ПС); КонецФункции -Функция СобратьХешКорня(Модель, Отступ) +Процедура НачатьКоманду(Команда, Контекст) Экспорт + // Узлы инициализируются в ОткрытьПодкоманду/СформироватьСкрипт. +КонецПроцедуры + +Процедура ПосетитьОпцию(СтрокаОпции, Контекст) Экспорт + + ЕстьЗначения = СтрокаОпции.ЗначенияДополнения.Количество() > 0; + Для Каждого ИмяОпции Из СтрокаОпции.НаименованияПараметров Цикл + Контекст.ИменаОпций.Добавить(ИмяОпции); + Если ЕстьЗначения Тогда + Контекст.ОпцииСоЗначениями.Вставить(ИмяОпции, СтрокаОпции.ЗначенияДополнения); + КонецЕсли; + КонецЦикла; + +КонецПроцедуры - Корень = Новый Структура("ИмяКаноническое,Алиасы,ИменаОпций,ЗначенияПоОпции,ЗначенияАргументов,Подкоманды"); - Корень.ИмяКаноническое = ""; - Корень.Алиасы = Новый Массив; - Корень.ИменаОпций = Новый Массив; - Корень.ЗначенияПоОпции = Новый Соответствие; - Корень.ЗначенияАргументов = Новый Массив; - Корень.Подкоманды = Модель; +Процедура ПосетитьАргумент(СтрокаАргумента, Контекст) Экспорт - Возврат СобратьХешУзла(Корень, Отступ); + Для Каждого Значение Из СтрокаАргумента.ЗначенияДополнения Цикл + Контекст.ЗначенияАргументов.Добавить(Значение); + КонецЦикла; + +КонецПроцедуры + +Процедура ПередПодкомандами(Команда, Подкоманды, Контекст) Экспорт + // Никаких действий: дочерние узлы сериализуются в ЗакрытьПодкоманду. +КонецПроцедуры + +Функция ОткрытьПодкоманду(Подкоманда, Контекст) Экспорт + + Возврат НовыйКонтекст(Контекст.Глубина + 1, Контекст.Отступ + " ", Контекст.Буфер); КонецФункции -Функция СобратьХешУзла(Описание, Отступ) +Процедура ЗакрытьПодкоманду(Подкоманда, ПодчинённыйКонтекст, Контекст) Экспорт - ВнутреннийОтступ = Отступ + " "; + ИменаПодкоманды = ПолучитьИменаКоманды(Подкоманда); + ИмяКанон = ИменаПодкоманды[0]; - Элементы = Новый Массив(); - Элементы.Добавить(ВнутреннийОтступ + "Commands = " + СобратьХешПодкоманд(Описание.Подкоманды, ВнутреннийОтступ)); - Элементы.Добавить(ВнутреннийОтступ + "AliasMap = " + СобратьХешАлиасов(Описание.Подкоманды, ВнутреннийОтступ)); - Элементы.Добавить(ВнутреннийОтступ + "OptionNames = " + МассивЛитералом(Описание.ИменаОпций)); - Элементы.Добавить(ВнутреннийОтступ + "OptionValues = " + СобратьХешЗначенийОпций(Описание.ЗначенияПоОпции, ВнутреннийОтступ)); - Элементы.Добавить(ВнутреннийОтступ + "ArgValues = " + МассивЛитералом(Описание.ЗначенияАргументов)); + Контекст.СериализованныеКоманды.Вставить(ИмяКанон, ПодчинённыйКонтекст.Сериализованное); + + Для Инд = 1 По ИменаПодкоманды.ВГраница() Цикл + Пара = Новый Структура("Алиас, Канон", ИменаПодкоманды[Инд], ИмяКанон); + Контекст.ПарыАлиасов.Добавить(Пара); + КонецЦикла; + +КонецПроцедуры + +Процедура ЗавершитьКоманду(Команда, Контекст) Экспорт + + Контекст.Сериализованное = СериализоватьУзел(Контекст); + + Если Контекст.Глубина = 0 Тогда + Б = Контекст.Буфер; + Б.Добавить("# " + ИмяПриложенияГлоб + " PowerShell completions"); + Б.Добавить("# Активируйте строкой в профиле PowerShell ($PROFILE):"); + Б.Добавить("# " + ИмяПриложенияГлоб + " completions --shell pwsh | Out-String | Invoke-Expression"); + Б.Добавить(""); + Б.Добавить("Register-ArgumentCompleter -Native -CommandName '" + ИмяПриложенияГлоб + "' -ScriptBlock {"); + Б.Добавить(" param($wordToComplete, $commandAst, $cursorPosition)"); + Б.Добавить(""); + Б.Добавить(" $__tree = " + Контекст.Сериализованное); + Б.Добавить(""); + Б.Добавить(" $tokens = @($commandAst.CommandElements | ForEach-Object { $_.Extent.Text })"); + Б.Добавить(" $lastIndex = $tokens.Count - 1"); + Б.Добавить(" $partial = -not [string]::IsNullOrEmpty($wordToComplete) -and $lastIndex -ge 1 -and $tokens[$lastIndex] -eq $wordToComplete"); + Б.Добавить(" if ($partial) { $endWalk = $lastIndex } else { $endWalk = $lastIndex + 1 }"); + Б.Добавить(""); + Б.Добавить(" $node = $__tree"); + Б.Добавить(" $i = 1"); + Б.Добавить(" while ($i -lt $endWalk) {"); + Б.Добавить(" $tok = $tokens[$i]"); + Б.Добавить(" if ($tok.StartsWith('-')) {"); + Б.Добавить(" if ($node.OptionValues.ContainsKey($tok) -and ($i + 1) -lt $endWalk) {"); + Б.Добавить(" $i += 2"); + Б.Добавить(" } else {"); + Б.Добавить(" $i += 1"); + Б.Добавить(" }"); + Б.Добавить(" } elseif ($node.Commands.ContainsKey($tok)) {"); + Б.Добавить(" $node = $node.Commands[$tok]"); + Б.Добавить(" $i += 1"); + Б.Добавить(" } elseif ($node.AliasMap.ContainsKey($tok)) {"); + Б.Добавить(" $node = $node.Commands[$node.AliasMap[$tok]]"); + Б.Добавить(" $i += 1"); + Б.Добавить(" } else {"); + Б.Добавить(" $i += 1"); + Б.Добавить(" }"); + Б.Добавить(" }"); + Б.Добавить(""); + Б.Добавить(" $prev = if ($endWalk -ge 2) { $tokens[$endWalk - 1] } else { '' }"); + Б.Добавить(""); + Б.Добавить(" if (-not [string]::IsNullOrEmpty($wordToComplete) -and $wordToComplete.StartsWith('-')) {"); + Б.Добавить(" return $node.OptionNames | Where-Object { $_ -like ""$wordToComplete*"" } | ForEach-Object {"); + Б.Добавить(" [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)"); + Б.Добавить(" }"); + Б.Добавить(" }"); + Б.Добавить(""); + Б.Добавить(" if ($node.OptionValues.ContainsKey($prev)) {"); + Б.Добавить(" return $node.OptionValues[$prev] | Where-Object { $_ -like ""$wordToComplete*"" } | ForEach-Object {"); + Б.Добавить(" [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)"); + Б.Добавить(" }"); + Б.Добавить(" }"); + Б.Добавить(""); + Б.Добавить(" $candidates = @()"); + Б.Добавить(" if ($node.Commands.Count -gt 0) {"); + Б.Добавить(" $candidates = @($node.Commands.Keys) + @($node.AliasMap.Keys)"); + Б.Добавить(" } elseif ($node.ArgValues.Count -gt 0) {"); + Б.Добавить(" $candidates = $node.ArgValues"); + Б.Добавить(" }"); + Б.Добавить(""); + Б.Добавить(" return $candidates | Where-Object { $_ -like ""$wordToComplete*"" } | ForEach-Object {"); + Б.Добавить(" [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)"); + Б.Добавить(" }"); + Б.Добавить("}"); + КонецЕсли; - Возврат "@{" + Символы.ПС + СтрСоединить(Элементы, Символы.ПС) + Символы.ПС + Отступ + "}"; +КонецПроцедуры + +Функция СериализоватьУзел(Контекст) + + ВнутрОтступ = Контекст.Отступ + " "; + + ИменаОпцийДляКомплита = Новый Массив; + ИменаОпцийДляКомплита.Добавить("--help"); + Для Каждого Имя Из Контекст.ИменаОпций Цикл + ИменаОпцийДляКомплита.Добавить(Имя); + КонецЦикла; + + Элементы = Новый Массив; + Элементы.Добавить(ВнутрОтступ + "Commands = " + СериализоватьХешКоманд(Контекст.СериализованныеКоманды, ВнутрОтступ)); + Элементы.Добавить(ВнутрОтступ + "AliasMap = " + СериализоватьХешАлиасов(Контекст.ПарыАлиасов, ВнутрОтступ)); + Элементы.Добавить(ВнутрОтступ + "OptionNames = " + МассивЛитералом(ИменаОпцийДляКомплита)); + Элементы.Добавить(ВнутрОтступ + "OptionValues = " + СериализоватьХешЗначенийОпций(Контекст.ОпцииСоЗначениями, ВнутрОтступ)); + Элементы.Добавить(ВнутрОтступ + "ArgValues = " + МассивЛитералом(Контекст.ЗначенияАргументов)); + + Возврат "@{" + Символы.ПС + СтрСоединить(Элементы, Символы.ПС) + Символы.ПС + Контекст.Отступ + "}"; КонецФункции -Функция СобратьХешПодкоманд(Подкоманды, Отступ) +Функция СериализоватьХешКоманд(СериализованныеКоманды, Отступ) - Если Подкоманды.Количество() = 0 Тогда + Если СериализованныеКоманды.Количество() = 0 Тогда Возврат "@{}"; КонецЕсли; - ВнутреннийОтступ = Отступ + " "; - Пары = Новый Массив(); - Для Каждого Под Из Подкоманды Цикл - Пары.Добавить(ВнутреннийОтступ + ВКавычкахPS(Под.ИмяКаноническое) + " = " + СобратьХешУзла(Под, ВнутреннийОтступ)); + ВнутрОтступ = Отступ + " "; + Пары = Новый Массив; + Для Каждого КлючЗначение Из СериализованныеКоманды Цикл + Пары.Добавить(ВнутрОтступ + ВКавычкахPS(КлючЗначение.Ключ) + " = " + КлючЗначение.Значение); КонецЦикла; Возврат "@{" + Символы.ПС + СтрСоединить(Пары, Символы.ПС) + Символы.ПС + Отступ + "}"; КонецФункции -Функция СобратьХешАлиасов(Подкоманды, Отступ) - - Пары = Новый Массив(); - Для Каждого Под Из Подкоманды Цикл - Для Каждого Алиас Из Под.Алиасы Цикл - Пары.Добавить(ВКавычкахPS(Алиас) + " = " + ВКавычкахPS(Под.ИмяКаноническое)); - КонецЦикла; - КонецЦикла; +Функция СериализоватьХешАлиасов(ПарыАлиасов, Отступ) - Если Пары.Количество() = 0 Тогда + Если ПарыАлиасов.Количество() = 0 Тогда Возврат "@{}"; КонецЕсли; - ВнутреннийОтступ = Отступ + " "; - ПерепакованныеПары = Новый Массив(); - Для Каждого Пара Из Пары Цикл - ПерепакованныеПары.Добавить(ВнутреннийОтступ + Пара); + ВнутрОтступ = Отступ + " "; + Строки = Новый Массив; + Для Каждого Пара Из ПарыАлиасов Цикл + Строки.Добавить(ВнутрОтступ + ВКавычкахPS(Пара.Алиас) + " = " + ВКавычкахPS(Пара.Канон)); КонецЦикла; - Возврат "@{" + Символы.ПС + СтрСоединить(ПерепакованныеПары, Символы.ПС) + Символы.ПС + Отступ + "}"; + Возврат "@{" + Символы.ПС + СтрСоединить(Строки, Символы.ПС) + Символы.ПС + Отступ + "}"; КонецФункции -Функция СобратьХешЗначенийОпций(ЗначенияПоОпции, Отступ) +Функция СериализоватьХешЗначенийОпций(ОпцииСоЗначениями, Отступ) - Если ЗначенияПоОпции.Количество() = 0 Тогда + Если ОпцииСоЗначениями.Количество() = 0 Тогда Возврат "@{}"; КонецЕсли; - ВнутреннийОтступ = Отступ + " "; - Пары = Новый Массив(); - Для Каждого КлючЗначение Из ЗначенияПоОпции Цикл - Пары.Добавить(ВнутреннийОтступ + ВКавычкахPS(КлючЗначение.Ключ) + " = " + МассивЛитералом(КлючЗначение.Значение)); + ВнутрОтступ = Отступ + " "; + Пары = Новый Массив; + Для Каждого КлючЗначение Из ОпцииСоЗначениями Цикл + Пары.Добавить(ВнутрОтступ + ВКавычкахPS(КлючЗначение.Ключ) + " = " + МассивЛитералом(КлючЗначение.Значение)); КонецЦикла; Возврат "@{" + Символы.ПС + СтрСоединить(Пары, Символы.ПС) + Символы.ПС + Отступ + "}"; @@ -166,7 +217,7 @@ Возврат "@()"; КонецЕсли; - Элементы = Новый Массив(); + Элементы = Новый Массив; Для Каждого Значение Из Значения Цикл Элементы.Добавить(ВКавычкахPS(Значение)); КонецЦикла; @@ -181,3 +232,32 @@ Возврат "'" + Экранированное + "'"; КонецФункции + +Функция НовыйКонтекст(Глубина, Отступ, Буфер) + + К = Новый Структура(); + К.Вставить("Глубина", Глубина); + К.Вставить("Отступ", Отступ); + К.Вставить("Буфер", Буфер); + К.Вставить("ИменаОпций", Новый Массив); + К.Вставить("ОпцииСоЗначениями", Новый Соответствие); + К.Вставить("ЗначенияАргументов", Новый Массив); + К.Вставить("СериализованныеКоманды", Новый Соответствие); + К.Вставить("ПарыАлиасов", Новый Массив); + К.Вставить("Сериализованное", ""); + Возврат К; + +КонецФункции + +Функция ПолучитьИменаКоманды(Команда) + + Имена = Новый Массив; + Имена.Добавить(Команда.ПолучитьИмяКоманды()); + Для Каждого Синоним Из Команда.ПолучитьСинонимы() Цикл + Если Синоним <> Команда.ПолучитьИмяКоманды() Тогда + Имена.Добавить(Синоним); + КонецЕсли; + КонецЦикла; + Возврат Имена; + +КонецФункции diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Zsh.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Zsh.os" index 618ddde..228daa0 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Zsh.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Zsh.os" @@ -1,90 +1,180 @@ -// Генератор скрипта автодополнения для Zsh. +// Генератор (посетитель) скрипта автодополнения для Zsh. +// Реализует контракт ОбходчикКомандДополнения. + +Перем ИмяПриложенияГлоб; +Перем ИмяФункцииГлоб; Функция Оболочка() Экспорт Возврат "zsh"; КонецФункции -Функция СформироватьСкрипт(ИмяПриложения, Модель) Экспорт - - ИмяФункции = "_" + БезопасноеИмяФункции(ИмяПриложения); - - Строки = Новый Массив(); - Строки.Добавить("#compdef " + ИмяПриложения); - Строки.Добавить("# " + ИмяПриложения + " zsh completions"); - Строки.Добавить("# Поместите файл в путь $fpath, например ~/.zsh/completions/_" + ИмяПриложения + ","); - Строки.Добавить("# либо активируйте через: source <(" + ИмяПриложения + " completions --shell zsh)"); - Строки.Добавить(""); - Строки.Добавить(ИмяФункции + "() {"); - Строки.Добавить(" local -a commands"); - Строки.Добавить(" local context curcontext=""$curcontext"" state line"); - Строки.Добавить(" commands=( " + СтрСоединить(СобратьИменаВсех(Модель), " ") + " )"); - Строки.Добавить(" _arguments -C \"); - Строки.Добавить(" ""1: :->command"" \"); - Строки.Добавить(" ""*:: :->args"""); - Строки.Добавить(""); - Строки.Добавить(" case $state in"); - Строки.Добавить(" command)"); - Строки.Добавить(" _values ""command"" $commands"); - Строки.Добавить(" ;;"); - Строки.Добавить(" args)"); - Строки.Добавить(" case $words[1] in"); - - Для Каждого Описание Из Модель Цикл - ДобавитьВеткуКоманды(Строки, Описание, " ", 1); - КонецЦикла; +Функция СформироватьСкрипт(ИмяПриложения, КореньКоманд) Экспорт + + ИмяПриложенияГлоб = ИмяПриложения; + ИмяФункцииГлоб = "_" + БезопасноеИмяФункции(ИмяПриложения); - Строки.Добавить(" *) ;;"); - Строки.Добавить(" esac"); - Строки.Добавить(" ;;"); - Строки.Добавить(" esac"); - Строки.Добавить("}"); - Строки.Добавить(""); - Строки.Добавить("compdef " + ИмяФункции + " " + ИмяПриложения); + Буфер = Новый Массив; + Контекст = НовыйКонтекст(0, "", Буфер); - Возврат СтрСоединить(Строки, Символы.ПС); + Обходчик = Новый ОбходчикКомандДополнения(); + Обходчик.Обойти(КореньКоманд, ЭтотОбъект, Контекст); + + Возврат СтрСоединить(Буфер, Символы.ПС); КонецФункции -Процедура ДобавитьВеткуКоманды(Строки, Описание, Отступ, Глубина) +Процедура НачатьКоманду(Команда, Контекст) Экспорт + + Если Контекст.Глубина = 0 Тогда + Б = Контекст.Буфер; + Б.Добавить("#compdef " + ИмяПриложенияГлоб); + Б.Добавить("# " + ИмяПриложенияГлоб + " zsh completions"); + Б.Добавить("# Поместите файл в путь $fpath, например ~/.zsh/completions/_" + ИмяПриложенияГлоб + ","); + Б.Добавить("# либо активируйте через: source <(" + ИмяПриложенияГлоб + " completions --shell zsh)"); + Б.Добавить(""); + Б.Добавить(ИмяФункцииГлоб + "() {"); + Б.Добавить(" local -a commands"); + Б.Добавить(" local context curcontext=""$curcontext"" state line"); + Иначе + Контекст.Буфер.Добавить(Контекст.Отступ + СтрСоединить(ПолучитьИменаКоманды(Команда), "|") + ")"); + КонецЕсли; + +КонецПроцедуры - Строки.Добавить(Отступ + СтрСоединить(Описание.Имена, "|") + ")"); +Процедура ПосетитьОпцию(СтрокаОпции, Контекст) Экспорт - Для Каждого КлючЗначение Из Описание.ЗначенияПоОпции Цикл - Строки.Добавить(Отступ + " if [[ $words[CURRENT-1] == """ + КлючЗначение.Ключ + """ ]]; then"); - Строки.Добавить(Отступ + " _values """ + КлючЗначение.Ключ + """ " + СтрСоединить(КлючЗначение.Значение, " ")); - Строки.Добавить(Отступ + " return"); - Строки.Добавить(Отступ + " fi"); + ЕстьЗначения = СтрокаОпции.ЗначенияДополнения.Количество() > 0; + Для Каждого ИмяОпции Из СтрокаОпции.НаименованияПараметров Цикл + Контекст.ИменаОпций.Добавить(ИмяОпции); + Если ЕстьЗначения Тогда + Контекст.ОпцииСоЗначениями.Вставить(ИмяОпции, СтрокаОпции.ЗначенияДополнения); + КонецЕсли; КонецЦикла; - Если Описание.Подкоманды.Количество() > 0 Тогда +КонецПроцедуры - СледующийИндекс = Формат(Глубина + 1, "ЧГ=0"); - Строки.Добавить(Отступ + " if (( CURRENT == " + СледующийИндекс + " )); then"); - Строки.Добавить(Отступ + " _values ""subcommand"" " + СтрСоединить(СобратьИменаВсех(Описание.Подкоманды), " ")); - Строки.Добавить(Отступ + " else"); - Строки.Добавить(Отступ + " case $words[" + СледующийИндекс + "] in"); - Для Каждого Под Из Описание.Подкоманды Цикл - ДобавитьВеткуКоманды(Строки, Под, Отступ + " ", Глубина + 1); - КонецЦикла; - Строки.Добавить(Отступ + " *) ;;"); - Строки.Добавить(Отступ + " esac"); - Строки.Добавить(Отступ + " fi"); +Процедура ПосетитьАргумент(СтрокаАргумента, Контекст) Экспорт + + Для Каждого Значение Из СтрокаАргумента.ЗначенияДополнения Цикл + Контекст.ЗначенияАргументов.Добавить(Значение); + КонецЦикла; + +КонецПроцедуры + +Процедура ПередПодкомандами(Команда, Подкоманды, Контекст) Экспорт + + Б = Контекст.Буфер; + О = Контекст.Отступ; + Контекст.ЕстьПодкоманды = Подкоманды.Количество() > 0; + + Если Контекст.Глубина = 0 Тогда + Б.Добавить(" commands=( " + СтрСоединить(СобратьИменаВсех(Подкоманды), " ") + " )"); + Б.Добавить(" _arguments -C \"); + Б.Добавить(" ""1: :->command"" \"); + Б.Добавить(" ""*:: :->args"""); + Б.Добавить(""); + Б.Добавить(" case $state in"); + Б.Добавить(" command)"); + Б.Добавить(" _values ""command"" $commands"); + Б.Добавить(" ;;"); + Б.Добавить(" args)"); + Б.Добавить(" case $words[1] in"); + Возврат; + КонецЕсли; + + Для Каждого КлючЗначение Из Контекст.ОпцииСоЗначениями Цикл + Б.Добавить(О + " if [[ $words[CURRENT-1] == """ + КлючЗначение.Ключ + """ ]]; then"); + Б.Добавить(О + " _values """ + КлючЗначение.Ключ + """ " + СтрСоединить(КлючЗначение.Значение, " ")); + Б.Добавить(О + " return"); + Б.Добавить(О + " fi"); + КонецЦикла; + + Если Контекст.ЕстьПодкоманды Тогда + СледИндекс = Формат(Контекст.Глубина + 1, "ЧГ=0"); + Б.Добавить(О + " if (( CURRENT == " + СледИндекс + " )); then"); + Б.Добавить(О + " _values ""subcommand"" " + СтрСоединить(СобратьИменаВсех(Подкоманды), " ")); + Б.Добавить(О + " else"); + Б.Добавить(О + " case $words[" + СледИндекс + "] in"); + ИначеЕсли Контекст.ЗначенияАргументов.Количество() > 0 Тогда + Б.Добавить(О + " _values ""argument"" " + СтрСоединить(Контекст.ЗначенияАргументов, " ")); + КонецЕсли; - ИначеЕсли Описание.ЗначенияАргументов.Количество() > 0 Тогда +КонецПроцедуры + +Функция ОткрытьПодкоманду(Подкоманда, Контекст) Экспорт + + // На верхнем уровне (Глубина=0) обёрнутый case state=args добавляет + // дополнительный уровень отступа по сравнению с bash. + Если Контекст.Глубина = 0 Тогда + ОтступПодчинённого = " "; + Иначе + ОтступПодчинённого = Контекст.Отступ + " "; + КонецЕсли; + Возврат НовыйКонтекст(Контекст.Глубина + 1, ОтступПодчинённого, Контекст.Буфер); + +КонецФункции + +Процедура ЗакрытьПодкоманду(Подкоманда, ПодчинённыйКонтекст, Контекст) Экспорт + // Ветка case подкоманды закрывается в её собственном ЗавершитьКоманду. +КонецПроцедуры - Строки.Добавить(Отступ + " _values ""argument"" " + СтрСоединить(Описание.ЗначенияАргументов, " ")); +Процедура ЗавершитьКоманду(Команда, Контекст) Экспорт + Б = Контекст.Буфер; + О = Контекст.Отступ; + + Если Контекст.Глубина = 0 Тогда + Б.Добавить(" *) ;;"); + Б.Добавить(" esac"); + Б.Добавить(" ;;"); + Б.Добавить(" esac"); + Б.Добавить("}"); + Б.Добавить(""); + Б.Добавить("compdef " + ИмяФункцииГлоб + " " + ИмяПриложенияГлоб); + Возврат; КонецЕсли; - Строки.Добавить(Отступ + " ;;"); + Если Контекст.ЕстьПодкоманды Тогда + Б.Добавить(О + " *) ;;"); + Б.Добавить(О + " esac"); + Б.Добавить(О + " fi"); + КонецЕсли; + Б.Добавить(О + " ;;"); КонецПроцедуры -Функция СобратьИменаВсех(Описания) +Функция НовыйКонтекст(Глубина, Отступ, Буфер) + + К = Новый Структура(); + К.Вставить("Глубина", Глубина); + К.Вставить("Отступ", Отступ); + К.Вставить("Буфер", Буфер); + К.Вставить("ИменаОпций", Новый Массив); + К.Вставить("ОпцииСоЗначениями", Новый Соответствие); + К.Вставить("ЗначенияАргументов", Новый Массив); + К.Вставить("ЕстьПодкоманды", Ложь); + Возврат К; + +КонецФункции + +Функция ПолучитьИменаКоманды(Команда) + + Имена = Новый Массив; + Имена.Добавить(Команда.ПолучитьИмяКоманды()); + Для Каждого Синоним Из Команда.ПолучитьСинонимы() Цикл + Если Синоним <> Команда.ПолучитьИмяКоманды() Тогда + Имена.Добавить(Синоним); + КонецЕсли; + КонецЦикла; + Возврат Имена; + +КонецФункции + +Функция СобратьИменаВсех(Подкоманды) - Результат = Новый Массив(); - Для Каждого Описание Из Описания Цикл - Для Каждого Имя Из Описание.Имена Цикл + Результат = Новый Массив; + Для Каждого Подкоманда Из Подкоманды Цикл + Для Каждого Имя Из ПолучитьИменаКоманды(Подкоманда) Цикл Результат.Добавить(Имя); КонецЦикла; КонецЦикла; @@ -92,11 +182,11 @@ КонецФункции -Функция БезопасноеИмяФункции(ИмяПриложения) +Функция БезопасноеИмяФункции(Имя) Результат = ""; - Для Инд = 1 По СтрДлина(ИмяПриложения) Цикл - Символ = Сред(ИмяПриложения, Инд, 1); + Для Инд = 1 По СтрДлина(Имя) Цикл + Символ = Сред(Имя, Инд, 1); Если (Символ >= "a" И Символ <= "z") Или (Символ >= "A" И Символ <= "Z") Или (Символ >= "0" И Символ <= "9") diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\261\321\205\320\276\320\264\321\207\320\270\320\272\320\232\320\276\320\274\320\260\320\275\320\264\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\261\321\205\320\276\320\264\321\207\320\270\320\272\320\232\320\276\320\274\320\260\320\275\320\264\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" new file mode 100644 index 0000000..4b07832 --- /dev/null +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\261\321\205\320\276\320\264\321\207\320\270\320\272\320\232\320\276\320\274\320\260\320\275\320\264\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" @@ -0,0 +1,40 @@ +// Общий обходчик дерева команд для генераторов скриптов автодополнения. +// Генератор-посетитель должен реализовать следующий контракт: +// +// * НачатьКоманду(Команда, Контекст) +// * ПосетитьОпцию(СтрокаОпции, Контекст) +// * ПосетитьАргумент(СтрокаАргумента, Контекст) +// * ПередПодкомандами(Команда, Подкоманды, Контекст) +// * Функция ОткрытьПодкоманду(Подкоманда, Контекст) -> Контекст +// * ЗакрытьПодкоманду(Подкоманда, ПодчинённыйКонтекст, Контекст) +// * ЗавершитьКоманду(Команда, Контекст) +// +// Контекст - произвольная структура, зависящая от посетителя. Обходчик её +// не интерпретирует, а лишь передаёт между методами. ОткрытьПодкоманду +// возвращает новый контекст для подчинённого узла; ЗакрытьПодкоманду +// получает оба контекста, чтобы родитель мог собрать результат дочернего узла. + +Процедура Обойти(Команда, Посетитель, Контекст) Экспорт + + Посетитель.НачатьКоманду(Команда, Контекст); + + Для Каждого СтрокаОпции Из Команда.ПолучитьТаблицуОпций() Цикл + Посетитель.ПосетитьОпцию(СтрокаОпции, Контекст); + КонецЦикла; + + Для Каждого СтрокаАргумента Из Команда.ПолучитьТаблицуАргументов() Цикл + Посетитель.ПосетитьАргумент(СтрокаАргумента, Контекст); + КонецЦикла; + + Подкоманды = Команда.ПолучитьПодкоманды(); + Посетитель.ПередПодкомандами(Команда, Подкоманды, Контекст); + + Для Каждого Подкоманда Из Подкоманды Цикл + ПодчинённыйКонтекст = Посетитель.ОткрытьПодкоманду(Подкоманда, Контекст); + Обойти(Подкоманда, Посетитель, ПодчинённыйКонтекст); + Посетитель.ЗакрытьПодкоманду(Подкоманда, ПодчинённыйКонтекст, Контекст); + КонецЦикла; + + Посетитель.ЗавершитьКоманду(Команда, Контекст); + +КонецПроцедуры diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" index f702146..6560cef 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" @@ -29,74 +29,13 @@ КореньКоманд = ПриложениеCLI.ПолучитьКоманду(); ИмяПриложения = КореньКоманд.ПолучитьИмяКоманды(); - Модель = ПостроитьМодельПодкоманд(КореньКоманд); - Генератор = ВыбратьГенератор(ТипОболочки); - Скрипт = Генератор.СформироватьСкрипт(ИмяПриложения, Модель); + Скрипт = Генератор.СформироватьСкрипт(ИмяПриложения, КореньКоманд); Сообщить(Скрипт); КонецПроцедуры -Функция ПостроитьМодельПодкоманд(КомандаCLI) - - Результат = Новый Массив; - Для Каждого Подкоманда Из КомандаCLI.ПолучитьПодкоманды() Цикл - Результат.Добавить(ПостроитьОписаниеКоманды(Подкоманда)); - КонецЦикла; - Возврат Результат; - -КонецФункции - -Функция ПостроитьОписаниеКоманды(КомандаCLI) - - Описание = Новый Структура("Имена,ИмяКаноническое,Алиасы,ИменаОпций,ЗначенияПоОпции,ЗначенияАргументов,Подкоманды"); - - Имена = Новый Массив; - Имена.Добавить(КомандаCLI.ПолучитьИмяКоманды()); - Для Каждого Синоним Из КомандаCLI.ПолучитьСинонимы() Цикл - Если Синоним <> КомандаCLI.ПолучитьИмяКоманды() Тогда - Имена.Добавить(Синоним); - КонецЕсли; - КонецЦикла; - Описание.Имена = Имена; - Описание.ИмяКаноническое = Имена[0]; - - Алиасы = Новый Массив; - Для Инд = 1 По Имена.ВГраница() Цикл - Алиасы.Добавить(Имена[Инд]); - КонецЦикла; - Описание.Алиасы = Алиасы; - - ИменаОпций = Новый Массив; - ИменаОпций.Добавить("--help"); - ЗначенияПоОпции = Новый Соответствие; - Для Каждого СтрокаОпции Из КомандаCLI.ПолучитьТаблицуОпций() Цикл - ЕстьЗначения = СтрокаОпции.ЗначенияДополнения.Количество() > 0; - Для Каждого ИмяОпции Из СтрокаОпции.НаименованияПараметров Цикл - ИменаОпций.Добавить(ИмяОпции); - Если ЕстьЗначения Тогда - ЗначенияПоОпции.Вставить(ИмяОпции, СтрокаОпции.ЗначенияДополнения); - КонецЕсли; - КонецЦикла; - КонецЦикла; - Описание.ИменаОпций = ИменаОпций; - Описание.ЗначенияПоОпции = ЗначенияПоОпции; - - ЗначенияАргументов = Новый Массив; - Для Каждого СтрокаАргумента Из КомандаCLI.ПолучитьТаблицуАргументов() Цикл - Для Каждого Значение Из СтрокаАргумента.ЗначенияДополнения Цикл - ЗначенияАргументов.Добавить(Значение); - КонецЦикла; - КонецЦикла; - Описание.ЗначенияАргументов = ЗначенияАргументов; - - Описание.Подкоманды = ПостроитьМодельПодкоманд(КомандаCLI); - - Возврат Описание; - -КонецФункции - Функция ВыбратьГенератор(ТипОболочки) Если ТипОболочки = "bash" Тогда From e44226449e05545f3fdd4d8aab0092ddf3c6a0df Mon Sep 17 00:00:00 2001 From: Nikita Fedkin Date: Thu, 23 Apr 2026 14:50:33 +0200 Subject: [PATCH 10/11] =?UTF-8?q?fix(completions):=20=D0=BF=D1=80=D0=B5?= =?UTF-8?q?=D0=B4=D0=BB=D0=B0=D0=B3=D0=B0=D1=82=D1=8C=20=D1=82=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=BA=D0=BE=20=D0=BA=D0=B0=D0=BD=D0=BE=D0=BD=D0=B8=D1=87?= =?UTF-8?q?=D0=B5=D1=81=D0=BA=D0=B8=D0=B5=20=D0=B8=D0=BC=D0=B5=D0=BD=D0=B0?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=BC=D0=B0=D0=BD=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Алиасы резолвятся при вводе (через case в bash/zsh и AliasMap-lookup в pwsh), но не должны замусоривать список вариантов автодополнения. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...76\320\273\320\275\320\265\320\275\320\270\321\217Bash.os" | 4 +--- ...76\320\273\320\275\320\265\320\275\320\270\321\217Pwsh.os" | 2 +- ...276\320\273\320\275\320\265\320\275\320\270\321\217Zsh.os" | 4 +--- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Bash.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Bash.os" index d03d395..b091ad2 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Bash.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Bash.os" @@ -184,9 +184,7 @@ Результат = Новый Массив; Для Каждого Подкоманда Из Подкоманды Цикл - Для Каждого Имя Из ПолучитьИменаКоманды(Подкоманда) Цикл - Результат.Добавить(Имя); - КонецЦикла; + Результат.Добавить(Подкоманда.ПолучитьИмяКоманды()); КонецЦикла; Возврат Результат; diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Pwsh.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Pwsh.os" index 9d222c2..82cb940 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Pwsh.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Pwsh.os" @@ -129,7 +129,7 @@ Б.Добавить(""); Б.Добавить(" $candidates = @()"); Б.Добавить(" if ($node.Commands.Count -gt 0) {"); - Б.Добавить(" $candidates = @($node.Commands.Keys) + @($node.AliasMap.Keys)"); + Б.Добавить(" $candidates = @($node.Commands.Keys)"); Б.Добавить(" } elseif ($node.ArgValues.Count -gt 0) {"); Б.Добавить(" $candidates = $node.ArgValues"); Б.Добавить(" }"); diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Zsh.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Zsh.os" index 228daa0..fd41bb0 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Zsh.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Zsh.os" @@ -174,9 +174,7 @@ Результат = Новый Массив; Для Каждого Подкоманда Из Подкоманды Цикл - Для Каждого Имя Из ПолучитьИменаКоманды(Подкоманда) Цикл - Результат.Добавить(Имя); - КонецЦикла; + Результат.Добавить(Подкоманда.ПолучитьИмяКоманды()); КонецЦикла; Возврат Результат; From a76209361c7cc6f1bdf3c050464ed86ea3a62bd7 Mon Sep 17 00:00:00 2001 From: Nikita Fedkin Date: Thu, 23 Apr 2026 16:15:50 +0200 Subject: [PATCH 11/11] =?UTF-8?q?fix(completions/zsh):=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B5=D0=B4=D0=BB=D0=B0=D0=B3=D0=B0=D1=82=D1=8C=20=D0=BE=D0=BF?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D0=BF=D1=80=D0=B8=20=D0=B2=D0=B2=D0=BE?= =?UTF-8?q?=D0=B4=D0=B5=20=D1=84=D0=BB=D0=B0=D0=B3=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ветка $words[CURRENT] == -* в генераторе zsh возвращала только аргументы команды, опции не показывались. Добавлена явная ветка с _values для флагов. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...\273\320\275\320\265\320\275\320\270\321\217Zsh.os" | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Zsh.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Zsh.os" index fd41bb0..bed532e 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Zsh.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/internal/completion/\320\232\320\273\320\260\321\201\321\201\321\213/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\224\320\276\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217Zsh.os" @@ -89,6 +89,16 @@ Б.Добавить(О + " fi"); КонецЦикла; + ИменаДляКомплита = Новый Массив; + ИменаДляКомплита.Добавить("--help"); + Для Каждого Имя Из Контекст.ИменаОпций Цикл + ИменаДляКомплита.Добавить(Имя); + КонецЦикла; + Б.Добавить(О + " if [[ $words[CURRENT] == -* ]]; then"); + Б.Добавить(О + " _values ""option"" " + СтрСоединить(ИменаДляКомплита, " ")); + Б.Добавить(О + " return"); + Б.Добавить(О + " fi"); + Если Контекст.ЕстьПодкоманды Тогда СледИндекс = Формат(Контекст.Глубина + 1, "ЧГ=0"); Б.Добавить(О + " if (( CURRENT == " + СледИндекс + " )); then");