Search ui

pull/268/head
Anton Medvedev 9 months ago
parent 6fab9ce2e3
commit 9282066284
No known key found for this signature in database

@ -23,8 +23,8 @@ type KeyMap struct {
PrevSibling key.Binding
ToggleWrap key.Binding
Search key.Binding
Next key.Binding
Prev key.Binding
SearchNext key.Binding
SearchPrev key.Binding
Dig key.Binding
}
@ -112,11 +112,11 @@ func init() {
key.WithKeys("/"),
key.WithHelp("", "search regexp"),
),
Next: key.NewBinding(
SearchNext: key.NewBinding(
key.WithKeys("n"),
key.WithHelp("", "next search result"),
),
Prev: key.NewBinding(
SearchPrev: key.NewBinding(
key.WithKeys("N"),
key.WithHelp("", "prev search result"),
),

@ -7,9 +7,9 @@ import (
"io/fs"
"os"
"path"
"regexp"
"runtime/pprof"
"strconv"
"strings"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/textinput"
@ -118,12 +118,16 @@ func main() {
Background(lipgloss.Color("15")).
Foreground(lipgloss.Color("0"))
searchInput := textinput.New()
searchInput.Prompt = "/"
m := &model{
head: head,
top: head,
wrap: true,
digInput: digInput,
fileName: fileName,
head: head,
top: head,
wrap: true,
fileName: fileName,
digInput: digInput,
searchInput: searchInput,
}
p := tea.NewProgram(m, tea.WithAltScreen(), tea.WithMouseCellMotion())
@ -141,6 +145,10 @@ type model struct {
margin int
fileName string
digInput textinput.Model
searchInput textinput.Model
searchError error
searchResults []searchResult
searchResultCursor int
}
func (m *model) Init() tea.Cmd {
@ -190,6 +198,9 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.digInput.Focused() {
return m.handleDigKey(msg)
}
if m.searchInput.Focused() {
return m.handleSearchKey(msg)
}
return m.handleKey(msg)
}
return m, nil
@ -211,6 +222,24 @@ func (m *model) handleDigKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
return m, cmd
}
func (m *model) handleSearchKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch {
case msg.Type == tea.KeyEscape:
m.searchInput.Blur()
case msg.Type == tea.KeyEnter:
m.searchInput.Blur()
if m.searchInput.Value() != "" {
m.search(m.searchInput.Value())
}
default:
m.searchInput, cmd = m.searchInput.Update(msg)
}
return m, cmd
}
func (m *model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch {
case key.Matches(msg, keyMap.Quit):
@ -338,6 +367,16 @@ func (m *model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
m.digInput.Width = m.termWidth - 1
m.digInput.Focus()
case key.Matches(msg, keyMap.Search):
m.searchInput.CursorEnd()
m.searchInput.Width = m.termWidth - 2 // -1 for the prompt, -1 for the cursor
m.searchInput.Focus()
case key.Matches(msg, keyMap.SearchNext):
m.selectSearchResult(m.searchResultCursor + 1)
case key.Matches(msg, keyMap.SearchPrev):
m.selectSearchResult(m.searchResultCursor - 1)
}
return m, nil
}
@ -450,17 +489,38 @@ func (m *model) View() string {
if m.digInput.Focused() {
screen = append(screen, m.digInput.View()...)
} else {
var statusBar string
statusBar += m.cursorPath() + " "
statusBar += strings.Repeat(" ", max(0, m.termWidth-len(statusBar)-len(m.fileName)))
statusBar += m.fileName
statusBar := flex(m.termWidth, m.cursorPath(), m.fileName)
screen = append(screen, currentTheme.StatusBar([]byte(statusBar))...)
}
if m.searchInput.Focused() {
screen = append(screen, '\n')
screen = append(screen, m.searchInput.View()...)
} else if m.searchInput.Value() != "" {
screen = append(screen, '\n')
re, ci := regexCase(m.searchInput.Value())
if ci {
re = "/" + re + "/i"
} else {
re = "/" + re + "/"
}
if m.searchError != nil {
screen = append(screen, flex(m.termWidth, re, m.searchError.Error())...)
} else if len(m.searchResults) == 0 {
screen = append(screen, flex(m.termWidth, re, "not found")...)
} else {
cursor := fmt.Sprintf("found: [%v/%v]", m.searchResultCursor+1, len(m.searchResults))
screen = append(screen, flex(m.termWidth, re, cursor)...)
}
}
return string(screen)
}
func (m *model) viewHeight() int {
if m.searchInput.Focused() || m.searchInput.Value() != "" {
return m.termHeight - 2
}
return m.termHeight - 1
}
@ -580,3 +640,54 @@ func (m *model) dig(value string) *node {
}
return n
}
type searchResult struct {
node *node
index []int
}
func (m *model) search(s string) {
m.searchError = nil
m.searchResults = nil
m.searchResultCursor = 0
code, ci := regexCase(s)
if ci {
code = "(?i)" + code
}
re, err := regexp.Compile(code)
if err != nil {
m.searchError = err
return
}
node := m.top
for node != nil {
indexes := re.FindAllIndex(node.value, -1)
for _, index := range indexes {
m.searchResults = append(m.searchResults, searchResult{
node: node,
index: index,
})
}
node = node.next
}
m.selectSearchResult(0)
}
func (m *model) selectSearchResult(i int) {
if len(m.searchResults) == 0 {
return
}
if i < 0 {
i = len(m.searchResults) - 1
}
if i >= len(m.searchResults) {
i = 0
}
m.searchResultCursor = i
result := m.searchResults[i]
m.selectNode(result.node)
}

@ -2,6 +2,7 @@ package main
import (
"regexp"
"strings"
)
var identifier = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
@ -48,3 +49,17 @@ func prettyPrint(b []byte, selected bool, isChunk bool) []byte {
}
}
}
func regexCase(code string) (string, bool) {
if strings.HasSuffix(code, "/i") {
return code[:len(code)-2], true
} else if strings.HasSuffix(code, "/") {
return code[:len(code)-1], false
} else {
return code, true
}
}
func flex(width int, a, b string) string {
return a + strings.Repeat(" ", max(1, width-len(a)-len(b))) + b
}

Loading…
Cancel
Save