From 82b281641d9ce8a0bc5cad4d433cdde017ad412b Mon Sep 17 00:00:00 2001 From: Augustin Date: Mon, 20 Apr 2026 00:33:12 +0200 Subject: [PATCH] fix: enable text selection, dashboard multi-column layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove WithMouseCellMotion to allow text selection in terminal - Dashboard now uses 2-column layout (left: system/tools, right: actions/status) - More compact dashboard with better space usage 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush --- cmd/muyue/main.go | 2 +- internal/tui/app.go | 147 ++++++++++++++++++++++---------------------- 2 files changed, 74 insertions(+), 75 deletions(-) diff --git a/cmd/muyue/main.go b/cmd/muyue/main.go index e30b513..b28e3dc 100644 --- a/cmd/muyue/main.go +++ b/cmd/muyue/main.go @@ -108,7 +108,7 @@ func runTUI() { result := scanner.ScanSystem() model := tui.NewModel(cfg, result) - p := tea.NewProgram(model, tea.WithAltScreen(), tea.WithMouseCellMotion()) + p := tea.NewProgram(model, tea.WithAltScreen()) if _, err := p.Run(); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) diff --git a/internal/tui/app.go b/internal/tui/app.go index e880acc..1cf6252 100644 --- a/internal/tui/app.go +++ b/internal/tui/app.go @@ -1173,32 +1173,37 @@ func (m Model) renderTermInput() string { } func (m Model) renderDashboard() string { - var b strings.Builder + colWidth := m.width / 2 + if colWidth < 30 { + colWidth = 30 + } - b.WriteString(sectionStyle.Render("System")) - b.WriteString("\n") + var left, right strings.Builder + + left.WriteString(sectionStyle.Render("System")) + left.WriteString("\n") if m.scanResult != nil { sysInfo := m.scanResult.System.String() - b.WriteString(" ") - b.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("#E0E0E0")).Render(sysInfo)) + left.WriteString(" ") + left.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("#E0E0E0")).Render(sysInfo)) } - b.WriteString("\n\n") + left.WriteString("\n\n") - b.WriteString(sectionStyle.Render("Tools")) - b.WriteString("\n") + left.WriteString(sectionStyle.Render("Tools")) + left.WriteString("\n") if m.scanResult != nil { installed := 0 total := len(m.scanResult.Tools) for _, t := range m.scanResult.Tools { if t.Installed { installed++ - b.WriteString(" ") - b.WriteString(itemOKStyle.Render(" ")) - b.WriteString(fmt.Sprintf(" %-12s %s\n", t.Name, extractVersion(t.Version))) + left.WriteString(" ") + left.WriteString(itemOKStyle.Render(" ")) + left.WriteString(fmt.Sprintf(" %-12s %s\n", t.Name, extractVersion(t.Version))) } else { - b.WriteString(" ") - b.WriteString(itemMissingStyle.Render(" ")) - b.WriteString(fmt.Sprintf(" %-12s %s\n", t.Name, itemPendingStyle.Render("(not installed)"))) + left.WriteString(" ") + left.WriteString(itemMissingStyle.Render(" ")) + left.WriteString(fmt.Sprintf(" %-12s %s\n", t.Name, itemPendingStyle.Render("(not installed)"))) } } barWidth := 20 @@ -1208,30 +1213,13 @@ func (m Model) renderDashboard() string { } bar := lipgloss.NewStyle().Foreground(successColor).Render(strings.Repeat("█", pct)) + lipgloss.NewStyle().Foreground(dimColor).Render(strings.Repeat("░", barWidth-pct)) - b.WriteString(fmt.Sprintf("\n %s %d/%d tools installed\n", bar, installed, total)) - } - b.WriteString("\n") - - if len(m.updateStatus) > 0 { - b.WriteString(sectionStyle.Render("Updates")) - b.WriteString("\n") - for _, s := range m.updateStatus { - if s.NeedsUpdate { - b.WriteString(" ") - b.WriteString(itemWarnStyle.Render(" ")) - b.WriteString(fmt.Sprintf(" %s: %s -> %s\n", s.Tool, s.Current, s.Latest)) - } else if s.Error == "" { - b.WriteString(" ") - b.WriteString(itemOKStyle.Render(" ")) - b.WriteString(fmt.Sprintf(" %s: up to date\n", s.Tool)) - } - } - b.WriteString("\n") + left.WriteString(fmt.Sprintf("\n %s %d/%d\n", bar, installed, total)) } + left.WriteString("\n") if m.installing { - b.WriteString(sectionStyle.Render("Installing...")) - b.WriteString("\n") + left.WriteString(sectionStyle.Render("Installing...")) + left.WriteString("\n") progBar := m.progressBar.View() label := "" if m.installTool != "" { @@ -1239,19 +1227,21 @@ func (m Model) renderDashboard() string { } else { label = fmt.Sprintf(" %d/%d", m.installCurrent, m.installTotal) } - b.WriteString(fmt.Sprintf(" %s%s\n", progBar, label)) - b.WriteString("\n") + left.WriteString(fmt.Sprintf(" %s%s\n", progBar, label)) + left.WriteString("\n") } if len(m.installLog) > 0 { - b.WriteString(sectionStyle.Render("Install Log")) - b.WriteString("\n") + left.WriteString(sectionStyle.Render("Install Log")) + left.WriteString("\n") for _, l := range m.installLog { - b.WriteString(l + "\n") + left.WriteString(l + "\n") } - b.WriteString("\n") + left.WriteString("\n") } + right.WriteString(sectionStyle.Render("Quick Actions")) + right.WriteString("\n") actions := []struct { key string desc string @@ -1262,68 +1252,77 @@ func (m Model) renderDashboard() string { {"l", "Scan LSP servers"}, {"m", "Configure MCP servers"}, } - b.WriteString(sectionStyle.Render("Quick Actions")) - b.WriteString("\n") for _, a := range actions { - b.WriteString(fmt.Sprintf(" %s %s\n", + right.WriteString(fmt.Sprintf(" %s %s\n", lipgloss.NewStyle().Foreground(baseColor).Bold(true).Render("["+a.key+"]"), a.desc)) } - b.WriteString("\n") + right.WriteString("\n") + + if len(m.updateStatus) > 0 { + right.WriteString(sectionStyle.Render("Updates")) + right.WriteString("\n") + for _, s := range m.updateStatus { + if s.NeedsUpdate { + right.WriteString(" ") + right.WriteString(itemWarnStyle.Render(" ")) + right.WriteString(fmt.Sprintf(" %s: %s -> %s\n", s.Tool, s.Current, s.Latest)) + } else if s.Error == "" { + right.WriteString(" ") + right.WriteString(itemOKStyle.Render(" ")) + right.WriteString(fmt.Sprintf(" %s: up to date\n", s.Tool)) + } + } + right.WriteString("\n") + } if len(m.lspServers) > 0 { - b.WriteString(sectionStyle.Render("LSP Servers")) - b.WriteString("\n") + right.WriteString(sectionStyle.Render("LSP Servers")) + right.WriteString("\n") lspInstalled := 0 for _, s := range m.lspServers { if s.Installed { lspInstalled++ - b.WriteString(" ") - b.WriteString(itemOKStyle.Render(" ")) - b.WriteString(fmt.Sprintf(" %-28s (%s)\n", s.Name, s.Language)) + right.WriteString(" ") + right.WriteString(itemOKStyle.Render(" ")) + right.WriteString(fmt.Sprintf(" %-22s (%s)\n", s.Name, s.Language)) } else { - b.WriteString(" ") - b.WriteString(itemPendingStyle.Render(" ")) - b.WriteString(fmt.Sprintf(" %-28s (%s)\n", s.Name, s.Language)) + right.WriteString(" ") + right.WriteString(itemPendingStyle.Render(" ")) + right.WriteString(fmt.Sprintf(" %-22s (%s)\n", s.Name, s.Language)) } } - b.WriteString(fmt.Sprintf("\n Installed: %d/%d\n", lspInstalled, len(m.lspServers))) - b.WriteString("\n") + right.WriteString(fmt.Sprintf("\n Installed: %d/%d\n", lspInstalled, len(m.lspServers))) + right.WriteString("\n") } if m.daemon != nil { - b.WriteString(sectionStyle.Render("Update Daemon")) - b.WriteString("\n") + right.WriteString(sectionStyle.Render("Daemon")) + right.WriteString("\n") if m.daemon.IsRunning() { - b.WriteString(" ") - b.WriteString(itemOKStyle.Render("running")) + right.WriteString(" ") + right.WriteString(itemOKStyle.Render("running")) lastCheck := m.daemon.LastCheck() if !lastCheck.IsZero() { - b.WriteString(fmt.Sprintf(" last check: %s", lastCheck.Format("15:04:05"))) + right.WriteString(fmt.Sprintf(" last: %s", lastCheck.Format("15:04:05"))) } } else { - b.WriteString(" ") - b.WriteString(itemPendingStyle.Render("stopped")) + right.WriteString(" ") + right.WriteString(itemPendingStyle.Render("stopped")) } - b.WriteString("\n") - - logs := m.daemon.Logs() - if len(logs) > 3 { - logs = logs[len(logs)-3:] - } - for _, l := range logs { - b.WriteString(" " + itemPendingStyle.Render(l) + "\n") - } - b.WriteString("\n") + right.WriteString("\n\n") } mcpStatus := itemPendingStyle.Render("not configured") if m.mcpConfigured { mcpStatus = itemOKStyle.Render("configured") } - b.WriteString(fmt.Sprintf("MCP Servers: %s\n", mcpStatus)) + right.WriteString(fmt.Sprintf("MCP: %s\n", mcpStatus)) - return b.String() + leftCol := lipgloss.NewStyle().Width(colWidth).Render(left.String()) + rightCol := lipgloss.NewStyle().Width(colWidth).Render(right.String()) + + return lipgloss.JoinHorizontal(lipgloss.Top, leftCol, rightCol) } func (m Model) renderChat() string {