futriis/internal/cli/prompt.go

158 lines
4.0 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// /futriis/internal/cli/prompt.go
// Пакет cli реализует интерактивное приглашение с поддержкой истории команд
package cli
import (
"bufio"
"fmt"
"os"
"futriis/pkg/utils"
"github.com/mattn/go-runewidth"
"golang.org/x/term"
)
// 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)
// Очищаем буфер
p.buffer = make([]rune, 0)
p.pos = 0
// Показываем приглашение
fmt.Print(utils.GetPrompt())
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.Println()
cmd := string(p.buffer)
p.history.Add(cmd)
p.history.Reset()
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("\r\033[K") // Возврат в начало строки и очистка
// Печатаем приглашение и текущий буфер
promptStr := utils.ColorPromptCode + "futriis:~> " + utils.ColorReset
fmt.Print(promptStr + string(p.buffer))
// Вычисляем ширину приглашения (без ANSI кодов)
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)
}
// readSimple читает строку без специальной обработки (fallback)
func (p *Prompt) readSimple() (string, error) {
fmt.Print(utils.GetPrompt())
reader := bufio.NewReader(os.Stdin)
return reader.ReadString('\n')
}