diff --git a/internal/tui/model.go b/internal/tui/model.go index 8c37166..49124a6 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -1,6 +1,7 @@ package tui import ( + "fmt" "hosts-go/internal/core" list "github.com/charmbracelet/bubbles/list" @@ -10,16 +11,31 @@ import ( // entryItem wraps a HostEntry for display in a list component. type entryItem struct{ entry *core.HostEntry } -func (e entryItem) Title() string { return e.entry.Hostname } +func (e entryItem) Title() string { + prefix := "[ ]" + if e.entry.Active { + prefix = "[✓]" + } + return fmt.Sprintf("%s %s", prefix, e.entry.Hostname) +} + func (e entryItem) Description() string { return e.entry.IP } func (e entryItem) FilterValue() string { return e.entry.Hostname } // Model is the main Bubble Tea model for the application. +type Mode int + +const ( + ViewMode Mode = iota + EditMode +) + type Model struct { list list.Model hosts *core.HostsFile width int height int + mode Mode } // NewModel constructs the TUI model from a parsed HostsFile. @@ -38,6 +54,7 @@ func NewModel(hf *core.HostsFile) Model { return Model{ list: l, hosts: hf, + mode: ViewMode, } } diff --git a/internal/tui/view.go b/internal/tui/view.go index 0af600c..d0d7582 100644 --- a/internal/tui/view.go +++ b/internal/tui/view.go @@ -10,6 +10,7 @@ import ( var ( listStyle = lipgloss.NewStyle().Padding(0, 1) detailStyle = lipgloss.NewStyle().Padding(0, 1) + statusStyle = lipgloss.NewStyle().Padding(0, 1).Foreground(lipgloss.Color("240")).Background(lipgloss.Color("236")) ) // View renders the two-pane layout. @@ -36,5 +37,9 @@ func (m Model) View() string { left := listStyle.Width(m.width / 2).Height(m.height).Render(listView) right := detailStyle.Width(m.width - m.width/2).Height(m.height).Render(detail.String()) - return lipgloss.JoinHorizontal(lipgloss.Top, left, right) + panes := lipgloss.JoinHorizontal(lipgloss.Top, left, right) + + status := fmt.Sprintf("VIEW MODE • %d entries", len(m.hosts.Entries)) + bar := statusStyle.Width(m.width).Render(status) + return lipgloss.JoinVertical(lipgloss.Left, panes, bar) } diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md index 5c4e57c..33e991b 100644 --- a/memory-bank/activeContext.md +++ b/memory-bank/activeContext.md @@ -51,7 +51,8 @@ 4. **View Mode Implementation** 🟡 - Read-only display of `/etc/hosts` entries - - Needs status indicators and better styling + - Status bar and active/inactive indicators in list implemented + - Further styling improvements pending ### Medium-term (Phase 3) 1. **Edit Mode Implementation** diff --git a/memory-bank/progress.md b/memory-bank/progress.md index 0003adc..c93c8ff 100644 --- a/memory-bank/progress.md +++ b/memory-bank/progress.md @@ -49,8 +49,8 @@ - [x] **Two-pane layout**: Left list + right detail view - [x] **Entry list display**: Show IP and hostname columns - [x] **Entry selection**: Navigate and select entries with keyboard -- [ ] **View mode**: Safe browsing with status indicators -- [ ] **Integration**: Connect TUI with existing parser functionality + - [x] **View mode**: Safe browsing with status bar and active/inactive indicators + - [ ] **Integration**: Connect TUI with existing parser functionality ### 🔧 Edit Functionality (Phase 3) - [ ] **Edit mode transition**: Explicit mode switching with visual indicators diff --git a/tests/tui_test.go b/tests/tui_test.go index e6d9286..4c9dda9 100644 --- a/tests/tui_test.go +++ b/tests/tui_test.go @@ -28,3 +28,20 @@ func TestModelSelection(t *testing.T) { m = nm.(tui.Model) assert.Equal(t, "example.com", m.SelectedEntry().Hostname) } + +func TestViewModeStatusBar(t *testing.T) { + sample := `127.0.0.1 localhost +# 192.168.1.10 example.com` + lines := strings.Split(sample, "\n") + hf, _, err := core.ParseHostsContent(lines) + require.NoError(t, err) + + m := tui.NewModel(hf) + nm, _ := m.Update(tea.WindowSizeMsg{Width: 80, Height: 20}) + m = nm.(tui.Model) + view := m.View() + + assert.Contains(t, view, "VIEW MODE") + assert.Contains(t, view, "[✓] localhost") + assert.Contains(t, view, "[ ] example.com") +}