From 4fdf207e5035866b878a495af46af9d14816441b Mon Sep 17 00:00:00 2001 From: ruinivist Date: Thu, 1 Jan 2026 02:38:45 +0000 Subject: [PATCH] add hover highlight --- fstree/fstree.go | 19 +++++++++++++------ main.go | 19 ++++++++++++++----- styles/styles.go | 9 +++++---- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/fstree/fstree.go b/fstree/fstree.go index 481d6ad..854271a 100644 --- a/fstree/fstree.go +++ b/fstree/fstree.go @@ -182,13 +182,14 @@ func (t *FsTree) GetSelectedContent() (string, error) { return string(contentBytes), nil } -func (t *FsTree) Render() string { +func (t *FsTree) Render(hoverLine int) string { builder := &strings.Builder{} - t.renderNode(t.root, 0, builder) + lineCounter := 0 + t.renderNode(t.root, 0, builder, hoverLine, &lineCounter) return builder.String() } -func (t *FsTree) renderNode(node *FsNode, depth int, builder *strings.Builder) { +func (t *FsTree) renderNode(node *FsNode, depth int, builder *strings.Builder, hoverLine int, currentLine *int) { if node == nil { return } @@ -209,18 +210,24 @@ func (t *FsTree) renderNode(node *FsNode, depth int, builder *strings.Builder) { icon = lipgloss.NewStyle().Faint(true).Render(prevIndent + icon + indent) } - // highlight if selected + // highlight if selected or hovered fileName := node.FileName() - if node == t.selectedNode() { + isSelected := node == t.selectedNode() + isHovered := hoverLine >= 0 && *currentLine == hoverLine + + if isSelected { fileName = lipgloss.NewStyle().Foreground(styles.Highlight).Bold(true).Render(fileName) + } else if isHovered { + fileName = lipgloss.NewStyle().Foreground(styles.HoverHighlight).Render(fileName) } line := icon + " " + fileName + "\n" builder.WriteString(line) + *currentLine++ if node.expanded { for _, child := range node.children { - t.renderNode(child, depth+1, builder) + t.renderNode(child, depth+1, builder, hoverLine, currentLine) } } } diff --git a/main.go b/main.go index 7eb03d7..cc726f4 100644 --- a/main.go +++ b/main.go @@ -44,9 +44,10 @@ type model struct { tree *fs.FsTree // spinner needs to be state as I need to update the spinner on // each tick in update func - spinner spinner.Model - loading bool - viewport viewport.Model + spinner spinner.Model + loading bool + viewport viewport.Model + hoverLine int // track which line is being hovered } // initial model state @@ -63,6 +64,7 @@ func createModel() model { spinner: s, loading: true, viewport: viewport.New(0, 0), + hoverLine: -1, // no hover initially } } @@ -105,6 +107,13 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.spinner, cmd = m.spinner.Update(msg) return m, cmd case tea.MouseMsg: + // track hover for all mouse events within file tree + if msg.X < m.width { + m.hoverLine = msg.Y + } else { + m.hoverLine = -1 + } + if msg.Button == tea.MouseButtonLeft { // within file tree if msg.X < m.width { @@ -151,7 +160,7 @@ func (m model) View() string { left := lipgloss.NewStyle(). Width(m.width). - Render(m.tree.Render()) + Render(m.tree.Render(m.hoverLine)) right := lipgloss.NewStyle(). Width(m.terminalWidth - m.width - 1). @@ -173,7 +182,7 @@ func main() { p := tea.NewProgram( createModel(), tea.WithAltScreen(), // full screen tui - tea.WithMouseCellMotion(), + tea.WithMouseAllMotion(), ) if _, err := p.Run(); err != nil { fmt.Printf("Error: %v", err) diff --git a/styles/styles.go b/styles/styles.go index def01ea..16f67ea 100644 --- a/styles/styles.go +++ b/styles/styles.go @@ -11,10 +11,11 @@ import "github.com/charmbracelet/lipgloss" // ==================== colors ==================== var ( - Primary = lipgloss.Color("#7D56F4") - Secondary = lipgloss.Color("#FF5C8F") - Background = lipgloss.Color("#1E1E2E") - Highlight = lipgloss.Color("#FFA500") // Orange highlight for selected node + Primary = lipgloss.Color("#7D56F4") + Secondary = lipgloss.Color("#FF5C8F") + Background = lipgloss.Color("#1E1E2E") + Highlight = lipgloss.Color("#FFA500") // Orange highlight for selected node + HoverHighlight = lipgloss.Color("#4ECDC4") // Teal highlight for hovered node // TODO: to extend as needed, right now this is just a // placeholder )