fix: enable text selection, dashboard multi-column layout
All checks were successful
CI / build (push) Successful in 1m44s
All checks were successful
CI / build (push) Successful in 1m44s
- 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 <crush@charm.land>
This commit is contained in:
@@ -108,7 +108,7 @@ func runTUI() {
|
|||||||
result := scanner.ScanSystem()
|
result := scanner.ScanSystem()
|
||||||
|
|
||||||
model := tui.NewModel(cfg, result)
|
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 {
|
if _, err := p.Run(); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
|
|||||||
@@ -1173,32 +1173,37 @@ func (m Model) renderTermInput() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m Model) renderDashboard() string {
|
func (m Model) renderDashboard() string {
|
||||||
var b strings.Builder
|
colWidth := m.width / 2
|
||||||
|
if colWidth < 30 {
|
||||||
|
colWidth = 30
|
||||||
|
}
|
||||||
|
|
||||||
b.WriteString(sectionStyle.Render("System"))
|
var left, right strings.Builder
|
||||||
b.WriteString("\n")
|
|
||||||
|
left.WriteString(sectionStyle.Render("System"))
|
||||||
|
left.WriteString("\n")
|
||||||
if m.scanResult != nil {
|
if m.scanResult != nil {
|
||||||
sysInfo := m.scanResult.System.String()
|
sysInfo := m.scanResult.System.String()
|
||||||
b.WriteString(" ")
|
left.WriteString(" ")
|
||||||
b.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("#E0E0E0")).Render(sysInfo))
|
left.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("#E0E0E0")).Render(sysInfo))
|
||||||
}
|
}
|
||||||
b.WriteString("\n\n")
|
left.WriteString("\n\n")
|
||||||
|
|
||||||
b.WriteString(sectionStyle.Render("Tools"))
|
left.WriteString(sectionStyle.Render("Tools"))
|
||||||
b.WriteString("\n")
|
left.WriteString("\n")
|
||||||
if m.scanResult != nil {
|
if m.scanResult != nil {
|
||||||
installed := 0
|
installed := 0
|
||||||
total := len(m.scanResult.Tools)
|
total := len(m.scanResult.Tools)
|
||||||
for _, t := range m.scanResult.Tools {
|
for _, t := range m.scanResult.Tools {
|
||||||
if t.Installed {
|
if t.Installed {
|
||||||
installed++
|
installed++
|
||||||
b.WriteString(" ")
|
left.WriteString(" ")
|
||||||
b.WriteString(itemOKStyle.Render(" "))
|
left.WriteString(itemOKStyle.Render(" "))
|
||||||
b.WriteString(fmt.Sprintf(" %-12s %s\n", t.Name, extractVersion(t.Version)))
|
left.WriteString(fmt.Sprintf(" %-12s %s\n", t.Name, extractVersion(t.Version)))
|
||||||
} else {
|
} else {
|
||||||
b.WriteString(" ")
|
left.WriteString(" ")
|
||||||
b.WriteString(itemMissingStyle.Render(" "))
|
left.WriteString(itemMissingStyle.Render(" "))
|
||||||
b.WriteString(fmt.Sprintf(" %-12s %s\n", t.Name, itemPendingStyle.Render("(not installed)")))
|
left.WriteString(fmt.Sprintf(" %-12s %s\n", t.Name, itemPendingStyle.Render("(not installed)")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
barWidth := 20
|
barWidth := 20
|
||||||
@@ -1208,30 +1213,13 @@ func (m Model) renderDashboard() string {
|
|||||||
}
|
}
|
||||||
bar := lipgloss.NewStyle().Foreground(successColor).Render(strings.Repeat("█", pct)) +
|
bar := lipgloss.NewStyle().Foreground(successColor).Render(strings.Repeat("█", pct)) +
|
||||||
lipgloss.NewStyle().Foreground(dimColor).Render(strings.Repeat("░", barWidth-pct))
|
lipgloss.NewStyle().Foreground(dimColor).Render(strings.Repeat("░", barWidth-pct))
|
||||||
b.WriteString(fmt.Sprintf("\n %s %d/%d tools installed\n", bar, installed, total))
|
left.WriteString(fmt.Sprintf("\n %s %d/%d\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("\n")
|
||||||
|
|
||||||
if m.installing {
|
if m.installing {
|
||||||
b.WriteString(sectionStyle.Render("Installing..."))
|
left.WriteString(sectionStyle.Render("Installing..."))
|
||||||
b.WriteString("\n")
|
left.WriteString("\n")
|
||||||
progBar := m.progressBar.View()
|
progBar := m.progressBar.View()
|
||||||
label := ""
|
label := ""
|
||||||
if m.installTool != "" {
|
if m.installTool != "" {
|
||||||
@@ -1239,19 +1227,21 @@ func (m Model) renderDashboard() string {
|
|||||||
} else {
|
} else {
|
||||||
label = fmt.Sprintf(" %d/%d", m.installCurrent, m.installTotal)
|
label = fmt.Sprintf(" %d/%d", m.installCurrent, m.installTotal)
|
||||||
}
|
}
|
||||||
b.WriteString(fmt.Sprintf(" %s%s\n", progBar, label))
|
left.WriteString(fmt.Sprintf(" %s%s\n", progBar, label))
|
||||||
b.WriteString("\n")
|
left.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(m.installLog) > 0 {
|
if len(m.installLog) > 0 {
|
||||||
b.WriteString(sectionStyle.Render("Install Log"))
|
left.WriteString(sectionStyle.Render("Install Log"))
|
||||||
b.WriteString("\n")
|
left.WriteString("\n")
|
||||||
for _, l := range m.installLog {
|
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 {
|
actions := []struct {
|
||||||
key string
|
key string
|
||||||
desc string
|
desc string
|
||||||
@@ -1262,68 +1252,77 @@ func (m Model) renderDashboard() string {
|
|||||||
{"l", "Scan LSP servers"},
|
{"l", "Scan LSP servers"},
|
||||||
{"m", "Configure MCP servers"},
|
{"m", "Configure MCP servers"},
|
||||||
}
|
}
|
||||||
b.WriteString(sectionStyle.Render("Quick Actions"))
|
|
||||||
b.WriteString("\n")
|
|
||||||
for _, a := range actions {
|
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+"]"),
|
lipgloss.NewStyle().Foreground(baseColor).Bold(true).Render("["+a.key+"]"),
|
||||||
a.desc))
|
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 {
|
if len(m.lspServers) > 0 {
|
||||||
b.WriteString(sectionStyle.Render("LSP Servers"))
|
right.WriteString(sectionStyle.Render("LSP Servers"))
|
||||||
b.WriteString("\n")
|
right.WriteString("\n")
|
||||||
lspInstalled := 0
|
lspInstalled := 0
|
||||||
for _, s := range m.lspServers {
|
for _, s := range m.lspServers {
|
||||||
if s.Installed {
|
if s.Installed {
|
||||||
lspInstalled++
|
lspInstalled++
|
||||||
b.WriteString(" ")
|
right.WriteString(" ")
|
||||||
b.WriteString(itemOKStyle.Render(" "))
|
right.WriteString(itemOKStyle.Render(" "))
|
||||||
b.WriteString(fmt.Sprintf(" %-28s (%s)\n", s.Name, s.Language))
|
right.WriteString(fmt.Sprintf(" %-22s (%s)\n", s.Name, s.Language))
|
||||||
} else {
|
} else {
|
||||||
b.WriteString(" ")
|
right.WriteString(" ")
|
||||||
b.WriteString(itemPendingStyle.Render(" "))
|
right.WriteString(itemPendingStyle.Render(" "))
|
||||||
b.WriteString(fmt.Sprintf(" %-28s (%s)\n", s.Name, s.Language))
|
right.WriteString(fmt.Sprintf(" %-22s (%s)\n", s.Name, s.Language))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b.WriteString(fmt.Sprintf("\n Installed: %d/%d\n", lspInstalled, len(m.lspServers)))
|
right.WriteString(fmt.Sprintf("\n Installed: %d/%d\n", lspInstalled, len(m.lspServers)))
|
||||||
b.WriteString("\n")
|
right.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.daemon != nil {
|
if m.daemon != nil {
|
||||||
b.WriteString(sectionStyle.Render("Update Daemon"))
|
right.WriteString(sectionStyle.Render("Daemon"))
|
||||||
b.WriteString("\n")
|
right.WriteString("\n")
|
||||||
if m.daemon.IsRunning() {
|
if m.daemon.IsRunning() {
|
||||||
b.WriteString(" ")
|
right.WriteString(" ")
|
||||||
b.WriteString(itemOKStyle.Render("running"))
|
right.WriteString(itemOKStyle.Render("running"))
|
||||||
lastCheck := m.daemon.LastCheck()
|
lastCheck := m.daemon.LastCheck()
|
||||||
if !lastCheck.IsZero() {
|
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 {
|
} else {
|
||||||
b.WriteString(" ")
|
right.WriteString(" ")
|
||||||
b.WriteString(itemPendingStyle.Render("stopped"))
|
right.WriteString(itemPendingStyle.Render("stopped"))
|
||||||
}
|
}
|
||||||
b.WriteString("\n")
|
right.WriteString("\n\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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mcpStatus := itemPendingStyle.Render("not configured")
|
mcpStatus := itemPendingStyle.Render("not configured")
|
||||||
if m.mcpConfigured {
|
if m.mcpConfigured {
|
||||||
mcpStatus = itemOKStyle.Render("configured")
|
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 {
|
func (m Model) renderChat() string {
|
||||||
|
|||||||
Reference in New Issue
Block a user