init commit

This commit is contained in:
ysandler
2026-01-22 23:03:49 -06:00
commit 1eae04fc60
21 changed files with 1688 additions and 0 deletions

51
internal/tui/styles.go Normal file
View File

@@ -0,0 +1,51 @@
package tui
import "github.com/charmbracelet/lipgloss"
var (
// Colors
secondaryColor = lipgloss.Color("241") // Gray
dimColor = lipgloss.Color("245") // Dimmer gray for matched text
// Styles
TitleStyle = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("213")). // Magenta/pink for title
MarginBottom(1)
// Key styles - bold and bright to stand out
KeyStyle = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("82")) // Bright green
KeyStyleSelected = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("154")) // Even brighter green for selected
LineNumStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("244")) // Medium gray
LineNumStyleSelected = lipgloss.NewStyle().
Foreground(lipgloss.Color("250")) // Lighter for selected
// Match text - dimmer to not compete with key
MatchTextStyle = lipgloss.NewStyle().
Foreground(dimColor)
MatchTextStyleSelected = lipgloss.NewStyle().
Foreground(lipgloss.Color("252")) // Slightly brighter when selected
// Cursor
CursorStyle = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("212"))
HelpStyle = lipgloss.NewStyle().
Foreground(secondaryColor).
MarginTop(1)
NoResultsStyle = lipgloss.NewStyle().
Foreground(secondaryColor).
Italic(true).
MarginTop(1)
)

187
internal/tui/tui.go Normal file
View File

@@ -0,0 +1,187 @@
package tui
import (
"fmt"
"strings"
tea "github.com/charmbracelet/bubbletea"
"github.com/yeho/doks/internal/search"
)
type Model struct {
results []search.Result
cursor int
selected *search.Result
query string
quitting bool
width int
height int
}
func New(results []search.Result, query string) Model {
return Model{
results: results,
cursor: 0,
query: query,
}
}
func (m Model) Init() tea.Cmd {
return nil
}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q", "esc":
m.quitting = true
return m, tea.Quit
case "enter":
if len(m.results) > 0 && m.cursor < len(m.results) {
m.selected = &m.results[m.cursor]
}
return m, tea.Quit
case "up", "k":
if m.cursor > 0 {
m.cursor--
}
case "down", "j":
if m.cursor < len(m.results)-1 {
m.cursor++
}
case "home", "g":
m.cursor = 0
case "end", "G":
m.cursor = len(m.results) - 1
}
case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
}
return m, nil
}
func (m Model) View() string {
if len(m.results) == 0 {
return NoResultsStyle.Render(fmt.Sprintf("No results found for '%s'", m.query))
}
var b strings.Builder
// Title
title := fmt.Sprintf("Search results for '%s' (%d found)", m.query, len(m.results))
b.WriteString(TitleStyle.Render(title))
b.WriteString("\n\n")
// Results
visibleResults := m.results
maxVisible := 10
if len(visibleResults) > maxVisible {
// Show results around cursor
start := m.cursor - maxVisible/2
if start < 0 {
start = 0
}
end := start + maxVisible
if end > len(visibleResults) {
end = len(visibleResults)
start = end - maxVisible
if start < 0 {
start = 0
}
}
visibleResults = m.results[start:end]
}
for i, result := range m.results {
// Check if this result is visible
visible := false
for _, vr := range visibleResults {
if vr.Key == result.Key && vr.LineNumber == result.LineNumber {
visible = true
break
}
}
if !visible {
continue
}
isSelected := i == m.cursor
// Cursor indicator
cursor := " "
if isSelected {
cursor = CursorStyle.Render("> ")
}
// Key - bold and bright (even brighter when selected)
var key string
if isSelected {
key = KeyStyleSelected.Render(result.Key)
} else {
key = KeyStyle.Render(result.Key)
}
// Line number - dimmer
var lineNum string
if isSelected {
lineNum = LineNumStyleSelected.Render(fmt.Sprintf(":%d", result.LineNumber))
} else {
lineNum = LineNumStyle.Render(fmt.Sprintf(":%d", result.LineNumber))
}
// Truncate line text if too long
lineText := result.LineText
maxLineLen := 50
if len(lineText) > maxLineLen {
lineText = lineText[:maxLineLen-3] + "..."
}
// Match text - dimmer than key
var matchText string
if isSelected {
matchText = MatchTextStyleSelected.Render(lineText)
} else {
matchText = MatchTextStyle.Render(lineText)
}
line := fmt.Sprintf("%s%s%s %s", cursor, key, lineNum, matchText)
b.WriteString(line)
b.WriteString("\n")
}
// Help
help := "↑/k up • ↓/j down • enter select • q/esc quit"
b.WriteString(HelpStyle.Render(help))
return b.String()
}
func (m Model) Selected() *search.Result {
return m.selected
}
func (m Model) Quitting() bool {
return m.quitting
}
func Run(results []search.Result, query string) (*search.Result, error) {
model := New(results, query)
p := tea.NewProgram(model)
finalModel, err := p.Run()
if err != nil {
return nil, err
}
m := finalModel.(Model)
return m.Selected(), nil
}