130 lines
3.5 KiB
Go
130 lines
3.5 KiB
Go
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)
|
|
}
|
|
}
|
|
|