package main import ( "fmt" "os" "strings" "charm.land/bubbles/v2/textinput" tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" "nshell/executor" ) // Сообщение, которое сигнализирует о завершении работы внешней команды type processFinishedMsg struct{ err error } 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() { // Запускаем программу p := tea.NewProgram(initialModel()) if _, err := p.Run(); err != nil { fmt.Printf("Error running noko-shell: %v\n", err) os.Exit(1) } }