diff --git a/internal/tui/model.go b/internal/tui/model.go index acfcfb2..9b9f9a2 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -111,3 +111,8 @@ func (m Model) SelectedEntry() *core.HostEntry { } return m.hosts.Entries[idx] } + +// Mode returns the current operating mode of the TUI. +func (m Model) Mode() Mode { + return m.mode +} diff --git a/internal/tui/update.go b/internal/tui/update.go index 15b220b..e29fd15 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -18,6 +18,20 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } else { m.focus = listPane } + case "e": + if m.mode == ViewMode { + m.mode = EditMode + } else { + m.mode = ViewMode + } + case "a": + if m.mode == EditMode { + if entry := m.SelectedEntry(); entry != nil { + entry.Active = !entry.Active + m.list.SetItem(m.list.Index(), entryItem{entry}) + m.refreshDetail() + } + } case "up", "k": if m.focus == detailPane { m.detail.LineUp(1) diff --git a/internal/tui/view.go b/internal/tui/view.go index ecc371e..8190a0e 100644 --- a/internal/tui/view.go +++ b/internal/tui/view.go @@ -47,7 +47,11 @@ func (m Model) View() string { // join panes and status bar panes := lipgloss.JoinHorizontal(lipgloss.Top, left, right) - status := fmt.Sprintf("VIEW MODE โ€ข %d entries", len(m.hosts.Entries)) + modeLabel := "VIEW" + if m.mode == EditMode { + modeLabel = "EDIT" + } + status := fmt.Sprintf("%s MODE โ€ข %d entries", modeLabel, 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 65148e1..c19e750 100644 --- a/memory-bank/activeContext.md +++ b/memory-bank/activeContext.md @@ -2,8 +2,8 @@ ## Current Work Focus -**Status**: Phase 2 Complete - Basic TUI prototype finished -**Priority**: Begin Phase 3 - Edit mode and file integration +**Status**: Phase 3 In Progress - Edit mode basics implemented +**Priority**: Expand Phase 3 with file integration and advanced editing ## Recent Changes @@ -23,6 +23,11 @@ - โœ… **View mode** showing entry details and status bar - โœ… **Parser integration** loading `/etc/hosts` into the UI +### Phase 3: Edit Mode (IN PROGRESS) +- ๐Ÿ”„ **Edit mode toggle** via 'e' key with status bar indication +- ๐Ÿ”„ **Entry activation toggle** using 'a' key +- ๐Ÿ”„ **Model tests** covering edit mode behavior + ### Parser Capabilities Achieved - โœ… **Standard entries**: IPv4, IPv6, multiple aliases, inline comments - โœ… **Disabled entries**: Commented lines with `# IP hostname` format detection @@ -43,19 +48,19 @@ ### Immediate (Phase 3 - Current Priority) 1. **Edit Mode Implementation** - - Explicit mode transition with visual indicators - - Permission handling with sudo request - - Entry modification forms with validation + - โœ… Explicit mode transition with visual indicators + - ๐Ÿ”„ Permission handling with sudo request + - ๐Ÿ”„ Entry modification forms with validation 2. **File Integration** - - Connect TUI with existing parser functionality for writes - - Real-time display of actual `/etc/hosts` content - - Live validation and formatting preview + - ๐Ÿ”„ Connect TUI with existing parser functionality for writes + - ๐Ÿ”„ Real-time display of actual `/etc/hosts` content + - ๐Ÿ”„ Live validation and formatting preview 3. **Advanced Features** - - Entry toggle (activate/deactivate) - - Add/edit/delete operations - - Sorting and filtering capabilities + - โœ… Entry toggle (activate/deactivate) + - ๐Ÿ”„ Add/edit/delete operations + - ๐Ÿ”„ Sorting and filtering capabilities ## Active Decisions and Considerations diff --git a/memory-bank/progress.md b/memory-bank/progress.md index 620cd14..aedc400 100644 --- a/memory-bank/progress.md +++ b/memory-bank/progress.md @@ -53,9 +53,10 @@ - [x] **Integration**: Connect TUI with existing parser functionality ### ๐Ÿ”ง Edit Functionality (Phase 3) -- [ ] **Edit mode transition**: Explicit mode switching with visual indicators +- [x] **Edit mode transition**: Explicit mode switching with visual indicators - [ ] **Permission handling**: Request sudo access when entering edit mode -- [ ] **Entry modification**: Add, edit, delete, toggle active status +- [x] **Entry modification**: Toggle active status +- [ ] **Entry modification**: Add, edit, delete operations - [ ] **File writing**: Atomic updates with backup and rollback - [ ] **Input validation**: Real-time validation of IP and hostname inputs @@ -67,7 +68,7 @@ - [ ] **Search/filter**: Find entries quickly in large files ### ๐Ÿงช Testing & Quality (Ongoing) -- [ ] **TUI tests**: User interactions, state transitions +- [x] **TUI tests**: User interactions, state transitions - [ ] **Integration tests**: Complete TUI workflows with file operations - [ ] **Permission tests**: sudo scenarios, graceful degradation - [ ] **End-to-end tests**: Full application workflows @@ -75,8 +76,8 @@ ## Current Status ### Project Phase: **Phase 2 Complete โ†’ Phase 3 (Edit Mode Implementation)** -- **Completion**: ~75% (parser and basic TUI implemented) -- **Active work**: Begin edit mode and file integration (Phase 3) +- **Completion**: ~80% (parser, TUI, and basic edit mode implemented) +- **Active work**: Expand edit mode with file integration and advanced editing - **Blockers**: None - comprehensive parser foundation with 54 tests completed - **Parser status**: Production-ready with all safety features implemented diff --git a/tests/tui_test.go b/tests/tui_test.go index b9753ff..a73719a 100644 --- a/tests/tui_test.go +++ b/tests/tui_test.go @@ -62,3 +62,26 @@ func TestPaneSwitching(t *testing.T) { m = nm.(tui.Model) assert.Equal(t, "localhost", m.SelectedEntry().Hostname) } + +func TestEditModeToggleAndActivation(t *testing.T) { + sample := "127.0.0.1 localhost\n192.168.1.10 example.com" + lines := strings.Split(sample, "\n") + hf, _, err := core.ParseHostsContent(lines) + require.NoError(t, err) + + m := tui.NewModel(hf) + + // enter edit mode + nm, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'e'}}) + m = nm.(tui.Model) + assert.Equal(t, tui.EditMode, m.Mode()) + + // toggle active state of first entry + nm, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'a'}}) + m = nm.(tui.Model) + assert.False(t, m.SelectedEntry().Active) + + // status bar should reflect edit mode + view := m.View() + assert.Contains(t, view, "EDIT MODE") +}