переход на bubble tea библиотеку
This commit is contained in:
+112
-38
@@ -5,51 +5,125 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"nshell/executor"
|
||||
"nshell/suggestions"
|
||||
"charm.land/bubbles/v2/textinput"
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"charm.land/lipgloss/v2"
|
||||
|
||||
"github.com/c-bata/go-prompt"
|
||||
"golang.org/x/term"
|
||||
"nshell/executor"
|
||||
)
|
||||
|
||||
var oldStateTerminal *term.State
|
||||
// Сообщение, которое сигнализирует о завершении работы внешней команды
|
||||
type processFinishedMsg struct{ err error }
|
||||
|
||||
func getColor(color string) prompt.Color {
|
||||
color = strings.ToUpper(color)
|
||||
|
||||
switch color {
|
||||
case "CYAN":
|
||||
return prompt.Cyan
|
||||
case "RED":
|
||||
return prompt.Red
|
||||
case "FUCHSIA":
|
||||
return prompt.Fuchsia
|
||||
case "TURQUOISE":
|
||||
return prompt.Turquoise
|
||||
case "GREEN":
|
||||
return prompt.Green
|
||||
case "PURPLE":
|
||||
return prompt.Purple
|
||||
default:
|
||||
return prompt.Green
|
||||
type model struct {
|
||||
textInput textinput.Model
|
||||
lastInput string // Храним последнюю введенную строку для красивого лога
|
||||
errText string // Храним ошибку, если команда не найдена
|
||||
}
|
||||
|
||||
func initialModel() model {
|
||||
ti := textinput.New()
|
||||
ti.Placeholder = "Type a command..."
|
||||
ti.Focus()
|
||||
ti.CharLimit = 250
|
||||
ti.SetWidth(60)
|
||||
|
||||
// НАСТРОЙКА TRUECOLOR
|
||||
prefixStyle := lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#FF00FA")).
|
||||
Bold(true)
|
||||
|
||||
ti.Prompt = prefixStyle.Render("noko> ")
|
||||
//prefixStyle=lipgloss.NewStyle().Foreground(lipgloss.Color("#00FFD1"))
|
||||
|
||||
return model{
|
||||
textInput: ti,
|
||||
lastInput: "",
|
||||
errText: "",
|
||||
}
|
||||
}
|
||||
|
||||
func (m model) Init() tea.Cmd {
|
||||
return textinput.Blink
|
||||
}
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "ctrl+c", "esc":
|
||||
return m, tea.Quit
|
||||
|
||||
case "enter":
|
||||
inputVal := m.textInput.Value()
|
||||
m.textInput.SetValue("") // Очищаем поле ввода сразу
|
||||
m.errText = "" // Сбрасываем прошлые ошибки
|
||||
|
||||
if strings.TrimSpace(inputVal) == "" {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
m.lastInput = inputVal
|
||||
|
||||
// Передаем ввод в наш executor для подготовки команды
|
||||
c, err := executor.PrepareCommand(inputVal)
|
||||
if err != nil {
|
||||
if err == executor.ErrBuiltInExit {
|
||||
return m, tea.Quit
|
||||
}
|
||||
// Если exec.LookPath не нашел бинарник в системе
|
||||
m.errText = fmt.Sprintf("noko-shell: command not found: %s", strings.Fields(inputVal)[0])
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Универсальный запуск процесса (и для ls, и для nano/top/htop)
|
||||
// Мы связываем IO процесса с системным, чтобы stderr и stdout вывелись в терминал напрямую.
|
||||
c.Stdin = os.Stdin
|
||||
c.Stdout = os.Stdout
|
||||
c.Stderr = os.Stderr
|
||||
|
||||
// Вызываем ExecProcess, который сворачивает TUI, выполняет команду и возвращает управление
|
||||
return m, tea.ExecProcess(c, func(err error) tea.Msg {
|
||||
return processFinishedMsg{err: err}
|
||||
})
|
||||
}
|
||||
|
||||
case processFinishedMsg:
|
||||
if msg.err != nil {
|
||||
// Ошибка самого процесса (например, неверные флаги у команды)
|
||||
m.errText = fmt.Sprintf("process exited with error: %v", msg.err)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
var cmd tea.Cmd
|
||||
m.textInput, cmd = m.textInput.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func (m model) View() tea.View {
|
||||
var s strings.Builder
|
||||
|
||||
if m.errText != "" {
|
||||
errStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#FF0000"))
|
||||
s.WriteString(errStyle.Render(m.errText))
|
||||
s.WriteString("\n")
|
||||
m.errText = ""
|
||||
}
|
||||
|
||||
s.WriteString(m.textInput.View())
|
||||
|
||||
// Возвращаем объект tea.View, обернув в него строку
|
||||
return tea.NewView(s.String())
|
||||
}
|
||||
|
||||
|
||||
func main() {
|
||||
|
||||
fd := int(os.Stdin.Fd())
|
||||
if state, err := term.GetState(fd); err == nil {
|
||||
oldStateTerminal = state
|
||||
// Запускаем программу
|
||||
p := tea.NewProgram(initialModel())
|
||||
if _, err := p.Run(); err != nil {
|
||||
fmt.Printf("Error running noko-shell: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("Wellcome to noko-shell(nshell) :3")
|
||||
|
||||
options := []prompt.Option{
|
||||
prompt.OptionLivePrefix(func() (string, bool) {
|
||||
return executor.GetVariable("NSHELL_PREFIX_TEXT"), true
|
||||
}),
|
||||
prompt.OptionPrefixTextColor(getColor(executor.GetVariable("NSHELL_PREFIX_COLOR"))),
|
||||
}
|
||||
|
||||
p := prompt.New(executor.BuildExecutor(oldStateTerminal, &fd), suggestions.Completer, options...)
|
||||
p.Run()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user