diff --git a/internal/cli/prompt.go b/internal/cli/prompt.go deleted file mode 100644 index f655fb8..0000000 --- a/internal/cli/prompt.go +++ /dev/null @@ -1,204 +0,0 @@ -// /futriis/internal/cli/prompt.go -// Пакет cli реализует интерактивное приглашение командной строки с поддержкой истории. -// Обеспечивает редактирование строки ввода, навигацию по истории стрелками, -// поддержку Unicode (включая кириллицу) и управление курсором терминала. -// Использует raw режим терминала для обработки специальных клавиш. - -package cli - -import ( - "bufio" - "fmt" - "os" - - "futriis/pkg/utils" - "github.com/mattn/go-runewidth" - "golang.org/x/term" -) - -// ANSI escape sequences для управления курсором -const ( - ansiHideCursor = "\033[?25l" - ansiShowCursor = "\033[?25h" - ansiClearLine = "\033[2K" - ansiCarriageReturn = "\r" -) - -// Prompt представляет интерактивное приглашение -type Prompt struct { - history *History - buffer []rune - pos int -} - -// NewPrompt создаёт новое приглашение -func NewPrompt() *Prompt { - return &Prompt{ - history: NewHistory(100), - buffer: make([]rune, 0), - pos: 0, - } -} - -// ReadLine читает строку с поддержкой истории и редактирования -func (p *Prompt) ReadLine() (string, error) { - oldState, err := term.MakeRaw(int(os.Stdin.Fd())) - if err != nil { - return p.readSimple() - } - defer term.Restore(int(os.Stdin.Fd()), oldState) - - // Скрываем курсор в начале строки - fmt.Print(ansiHideCursor) - - // Очищаем буфер - p.buffer = make([]rune, 0) - p.pos = 0 - - // Показываем приглашение - promptStr := utils.ColorPrompt + "futriis:~> " + utils.ColorReset - fmt.Print(promptStr) - - // Показываем курсор только после приглашения (в месте ввода) - fmt.Print(ansiShowCursor) - - reader := bufio.NewReader(os.Stdin) - - for { - r, _, err := reader.ReadRune() - if err != nil { - return "", err - } - - switch r { - case 3: // Ctrl+C - return "", nil - - case 4: // Ctrl+D - if len(p.buffer) == 0 { - return "exit", nil - } - - case 13, 10: // Enter - // Скрываем курсор перед завершением - fmt.Print(ansiHideCursor) - fmt.Println() - cmd := string(p.buffer) - if cmd != "" { - p.history.Add(cmd) - } - p.history.Reset() - - // Логируем команду - if logger := utils.GetLogger(); logger != nil && cmd != "" { - logger.Log("CMD", cmd) - } - - return cmd, nil - - case 127: // Backspace - if p.pos > 0 { - // Удаляем символ перед курсором - p.buffer = append(p.buffer[:p.pos-1], p.buffer[p.pos:]...) - p.pos-- - p.refreshLine() - } - - case 27: // Escape sequence (стрелки) - // Читаем следующие два символа - r2, _, _ := reader.ReadRune() - r3, _, _ := reader.ReadRune() - - if r2 == '[' { - switch r3 { - case 'A': // Up arrow - prev := p.history.GetPrevious() - if prev != "" { - p.buffer = []rune(prev) - p.pos = len(p.buffer) - p.refreshLine() - } - - case 'B': // Down arrow - next := p.history.GetNext() - p.buffer = []rune(next) - p.pos = len(p.buffer) - p.refreshLine() - - case 'C': // Right arrow - if p.pos < len(p.buffer) { - p.pos++ - p.refreshLine() - } - - case 'D': // Left arrow - if p.pos > 0 { - p.pos-- - p.refreshLine() - } - } - } - - default: - // Добавляем символ (поддержка Unicode, включая русский) - if r >= 32 { // Печатные символы - // Вставляем символ в позицию курсора - if p.pos == len(p.buffer) { - p.buffer = append(p.buffer, r) - } else { - p.buffer = append(p.buffer[:p.pos], append([]rune{r}, p.buffer[p.pos:]...)...) - } - p.pos++ - p.refreshLine() - } - } - } -} - -// refreshLine обновляет текущую строку с правильным позиционированием курсора -func (p *Prompt) refreshLine() { - // Скрываем курсор во время перерисовки - fmt.Print(ansiHideCursor) - - // Возврат в начало строки и очистка - fmt.Print(ansiCarriageReturn + ansiClearLine) - - // Печатаем приглашение - promptStr := utils.ColorPrompt + "futriis:~> " + utils.ColorReset - fmt.Print(promptStr) - - // Печатаем текущий буфер - if len(p.buffer) > 0 { - fmt.Print(string(p.buffer)) - } - - // Вычисляем ширину приглашения - promptWidth := runewidth.StringWidth("futriis:~> ") - - // Вычисляем позицию курсора - cursorPos := promptWidth - for i := 0; i < p.pos; i++ { - cursorPos += runewidth.RuneWidth(p.buffer[i]) - } - - // Перемещаем курсор на правильную позицию и показываем его - fmt.Printf("\033[%dG", cursorPos+1) - fmt.Print(ansiShowCursor) -} - -// readSimple читает строку без специальной обработки (fallback) -func (p *Prompt) readSimple() (string, error) { - fmt.Print(utils.GetPrompt()) - reader := bufio.NewReader(os.Stdin) - - cmd, err := reader.ReadString('\n') - if err == nil { - cmd = cmd[:len(cmd)-1] // Убираем \n - // Логируем команду - if logger := utils.GetLogger(); logger != nil && cmd != "" { - logger.Log("CMD", cmd) - } - } - - return cmd, err -}