mirror of
https://github.com/shokinn/hosts-go.git
synced 2025-08-23 08:33:02 +00:00
Implement TUI pane navigation
This commit is contained in:
parent
3561d15858
commit
a24041d664
6 changed files with 137 additions and 73 deletions
|
@ -3,8 +3,10 @@ package tui
|
|||
import (
|
||||
"fmt"
|
||||
"hosts-go/internal/core"
|
||||
"strings"
|
||||
|
||||
list "github.com/charmbracelet/bubbles/list"
|
||||
viewport "github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
|
@ -30,12 +32,21 @@ const (
|
|||
EditMode
|
||||
)
|
||||
|
||||
type pane int
|
||||
|
||||
const (
|
||||
listPane pane = iota
|
||||
detailPane
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
list list.Model
|
||||
detail viewport.Model
|
||||
hosts *core.HostsFile
|
||||
width int
|
||||
height int
|
||||
mode Mode
|
||||
focus pane
|
||||
}
|
||||
|
||||
// NewModel constructs the TUI model from a parsed HostsFile.
|
||||
|
@ -51,11 +62,39 @@ func NewModel(hf *core.HostsFile) Model {
|
|||
l.SetShowHelp(false)
|
||||
l.SetShowPagination(false)
|
||||
|
||||
return Model{
|
||||
list: l,
|
||||
hosts: hf,
|
||||
mode: ViewMode,
|
||||
m := Model{
|
||||
list: l,
|
||||
detail: viewport.New(0, 0),
|
||||
hosts: hf,
|
||||
mode: ViewMode,
|
||||
focus: listPane,
|
||||
}
|
||||
m.refreshDetail()
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *Model) refreshDetail() {
|
||||
if len(m.hosts.Entries) == 0 {
|
||||
m.detail.SetContent("")
|
||||
return
|
||||
}
|
||||
entry := m.hosts.Entries[m.list.Index()]
|
||||
var b strings.Builder
|
||||
status := "active"
|
||||
if !entry.Active {
|
||||
status = "inactive"
|
||||
}
|
||||
fmt.Fprintf(&b, "IP: %s\n", entry.IP)
|
||||
fmt.Fprintf(&b, "Host: %s\n", entry.Hostname)
|
||||
if len(entry.Aliases) > 0 {
|
||||
fmt.Fprintf(&b, "Aliases: %s\n", strings.Join(entry.Aliases, ", "))
|
||||
}
|
||||
if entry.Comment != "" {
|
||||
fmt.Fprintf(&b, "Comment: %s\n", entry.Comment)
|
||||
}
|
||||
fmt.Fprintf(&b, "Status: %s", status)
|
||||
m.detail.SetContent(b.String())
|
||||
m.detail.YOffset = 0
|
||||
}
|
||||
|
||||
// Init satisfies tea.Model.
|
||||
|
|
|
@ -12,13 +12,47 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
switch msg.String() {
|
||||
case "q", "ctrl+c":
|
||||
return m, tea.Quit
|
||||
case "tab":
|
||||
if m.focus == listPane {
|
||||
m.focus = detailPane
|
||||
} else {
|
||||
m.focus = listPane
|
||||
}
|
||||
case "up", "k":
|
||||
if m.focus == detailPane {
|
||||
m.detail.LineUp(1)
|
||||
} else {
|
||||
m.list, cmd = m.list.Update(msg)
|
||||
m.refreshDetail()
|
||||
}
|
||||
return m, cmd
|
||||
case "down", "j":
|
||||
if m.focus == detailPane {
|
||||
m.detail.LineDown(1)
|
||||
} else {
|
||||
m.list, cmd = m.list.Update(msg)
|
||||
m.refreshDetail()
|
||||
}
|
||||
return m, cmd
|
||||
}
|
||||
if m.focus == listPane {
|
||||
m.list, cmd = m.list.Update(msg)
|
||||
m.refreshDetail()
|
||||
}
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
m.list.SetSize(msg.Width/2, msg.Height)
|
||||
m.height = msg.Height - 1
|
||||
m.list.SetSize(msg.Width/2, m.height)
|
||||
m.detail.Width = msg.Width - msg.Width/2
|
||||
m.detail.Height = m.height
|
||||
if m.focus == listPane {
|
||||
m.list, cmd = m.list.Update(msg)
|
||||
}
|
||||
default:
|
||||
if m.focus == listPane {
|
||||
m.list, cmd = m.list.Update(msg)
|
||||
m.refreshDetail()
|
||||
}
|
||||
}
|
||||
|
||||
m.list, cmd = m.list.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
|
|
@ -2,41 +2,29 @@ package tui
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
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"))
|
||||
listStyle = lipgloss.NewStyle().Padding(0, 1)
|
||||
detailStyle = lipgloss.NewStyle().Padding(0, 1)
|
||||
focusedStyle = lipgloss.NewStyle().BorderStyle(lipgloss.RoundedBorder()).BorderForeground(lipgloss.Color("62"))
|
||||
statusStyle = lipgloss.NewStyle().Padding(0, 1).Foreground(lipgloss.Color("240")).Background(lipgloss.Color("236"))
|
||||
)
|
||||
|
||||
// View renders the two-pane layout.
|
||||
func (m Model) View() string {
|
||||
listView := m.list.View()
|
||||
|
||||
var detail strings.Builder
|
||||
if len(m.hosts.Entries) > 0 {
|
||||
entry := m.hosts.Entries[m.list.Index()]
|
||||
status := "active"
|
||||
if !entry.Active {
|
||||
status = "inactive"
|
||||
}
|
||||
fmt.Fprintf(&detail, "IP: %s\n", entry.IP)
|
||||
fmt.Fprintf(&detail, "Host: %s\n", entry.Hostname)
|
||||
if len(entry.Aliases) > 0 {
|
||||
fmt.Fprintf(&detail, "Aliases: %s\n", strings.Join(entry.Aliases, ", "))
|
||||
}
|
||||
if entry.Comment != "" {
|
||||
fmt.Fprintf(&detail, "Comment: %s\n", entry.Comment)
|
||||
}
|
||||
fmt.Fprintf(&detail, "Status: %s", status)
|
||||
}
|
||||
detailView := m.detail.View()
|
||||
|
||||
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())
|
||||
right := detailStyle.Width(m.width - m.width/2).Height(m.height).Render(detailView)
|
||||
if m.focus == listPane {
|
||||
left = focusedStyle.Render(left)
|
||||
} else {
|
||||
right = focusedStyle.Render(right)
|
||||
}
|
||||
panes := lipgloss.JoinHorizontal(lipgloss.Top, left, right)
|
||||
|
||||
status := fmt.Sprintf("VIEW MODE • %d entries", len(m.hosts.Entries))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue