fix: hide completions tui when no results (#206)

* fix: hide completions tui when no results

* fix: gitignore

* Revert "fix(tui): completions should not close on no results (#198)"

This reverts commit 833eede1c1.

* fix: completions

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: accept

* fix: improvements

* chore(deps): update bubbles

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: improvements

* fix: accept

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
This commit is contained in:
Carlos Alexandro Becker
2025-07-16 22:11:12 -03:00
committed by GitHub
parent f664df60bc
commit a700b64a28
8 changed files with 113 additions and 52 deletions

1
.gitignore vendored
View File

@@ -48,3 +48,4 @@ Thumbs.db
manpages/
completions/
!internal/tui/components/completions/

2
go.mod
View File

@@ -16,7 +16,7 @@ require (
github.com/aymanbagabas/go-udiff v0.3.1
github.com/bmatcuk/doublestar/v4 v4.8.1
github.com/charlievieth/fastwalk v1.0.11
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250710161907-a4c42b579198
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5
github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.1
github.com/charmbracelet/fang v0.3.1-0.20250711140230-d5ebb8c1d674
github.com/charmbracelet/glamour/v2 v2.0.0-20250516160903-6f1e2c8f9ebe

4
go.sum
View File

@@ -68,8 +68,8 @@ github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR
github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/charlievieth/fastwalk v1.0.11 h1:5sLT/q9+d9xMdpKExawLppqvXFZCVKf6JHnr2u/ufj8=
github.com/charlievieth/fastwalk v1.0.11/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250710161907-a4c42b579198 h1:CkMS9Ah9ac1Ego5JDC5NJyZyAAqu23Z+O0yDwsa3IxM=
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250710161907-a4c42b579198/go.mod h1:6HamsBKWqEC/FVHuQMHgQL+knPyvHH55HwJDHl/adMw=
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5 h1:GTcMIfDQJKyNKS+xVt7GkNIwz+tBuQtIuiP50WpzNgs=
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5/go.mod h1:6HamsBKWqEC/FVHuQMHgQL+knPyvHH55HwJDHl/adMw=
github.com/charmbracelet/bubbletea-internal/v2 v2.0.0-20250716142813-5d1379f56ba2 h1:Gj/vSk7h96TxUU/GSuwbYkr9H0ze+ElAQjcl25wB0+U=
github.com/charmbracelet/bubbletea-internal/v2 v2.0.0-20250716142813-5d1379f56ba2/go.mod h1:m240IQxo1/eDQ7klblSzOCAUyc3LddHcV3Rc/YEGAgw=
github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=

View File

@@ -171,6 +171,8 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
m.attachments = append(m.attachments, msg.Attachment)
return m, nil
case completions.CompletionsOpenedMsg:
m.isCompletionsOpen = true
case completions.CompletionsClosedMsg:
m.isCompletionsOpen = false
m.currentQuery = ""
@@ -183,9 +185,6 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// If the selected item is a file, insert its path into the textarea
value := m.textarea.Value()
value = value[:m.completionsStartIndex]
if len(value) > 0 && value[len(value)-1] != ' ' {
value += " "
}
value += item.Path
m.textarea.SetValue(value)
m.isCompletionsOpen = false
@@ -199,37 +198,15 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.KeyPressMsg:
switch {
// Completions
case msg.String() == "/" && !m.isCompletionsOpen:
case msg.String() == "/" && !m.isCompletionsOpen &&
// only show if beginning of prompt, or if previous char is a space:
(len(m.textarea.Value()) == 0 || m.textarea.Value()[len(m.textarea.Value())-1] == ' '):
m.isCompletionsOpen = true
m.currentQuery = ""
cmds = append(cmds, m.startCompletions)
m.completionsStartIndex = len(m.textarea.Value())
case msg.String() == "space" && m.isCompletionsOpen:
m.isCompletionsOpen = false
m.currentQuery = ""
m.completionsStartIndex = 0
cmds = append(cmds, util.CmdHandler(completions.CloseCompletionsMsg{}))
cmds = append(cmds, m.startCompletions)
case m.isCompletionsOpen && m.textarea.Cursor().X <= m.completionsStartIndex:
cmds = append(cmds, util.CmdHandler(completions.CloseCompletionsMsg{}))
case msg.String() == "backspace" && m.isCompletionsOpen:
if len(m.currentQuery) > 0 {
m.currentQuery = m.currentQuery[:len(m.currentQuery)-1]
cmds = append(cmds, util.CmdHandler(completions.FilterCompletionsMsg{
Query: m.currentQuery,
}))
} else {
m.isCompletionsOpen = false
m.currentQuery = ""
m.completionsStartIndex = 0
cmds = append(cmds, util.CmdHandler(completions.CloseCompletionsMsg{}))
}
default:
if m.isCompletionsOpen {
m.currentQuery += msg.String()
cmds = append(cmds, util.CmdHandler(completions.FilterCompletionsMsg{
Query: m.currentQuery,
}))
}
}
if key.Matches(msg, DeleteKeyMaps.AttachmentDeleteMode) {
m.deleteMode = true
@@ -281,6 +258,36 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.textarea, cmd = m.textarea.Update(msg)
cmds = append(cmds, cmd)
if m.textarea.Focused() {
kp, ok := msg.(tea.KeyPressMsg)
if ok {
if kp.String() == "space" || m.textarea.Value() == "" {
m.isCompletionsOpen = false
m.currentQuery = ""
m.completionsStartIndex = 0
cmds = append(cmds, util.CmdHandler(completions.CloseCompletionsMsg{}))
} else {
word := m.textarea.Word()
if strings.HasPrefix(word, "/") {
// XXX: wont' work if editing in the middle of the field.
m.completionsStartIndex = strings.LastIndex(m.textarea.Value(), word)
m.currentQuery = word[1:]
m.isCompletionsOpen = true
cmds = append(cmds, util.CmdHandler(completions.FilterCompletionsMsg{
Query: m.currentQuery,
Reopen: m.isCompletionsOpen,
}))
} else {
m.isCompletionsOpen = false
m.currentQuery = ""
m.completionsStartIndex = 0
cmds = append(cmds, util.CmdHandler(completions.CloseCompletionsMsg{}))
}
}
}
}
return m, tea.Batch(cmds...)
}

View File

@@ -1,6 +1,8 @@
package completions
import (
"strings"
"github.com/charmbracelet/bubbles/v2/key"
tea "github.com/charmbracelet/bubbletea/v2"
"github.com/charmbracelet/crush/internal/tui/components/core/list"
@@ -23,11 +25,14 @@ type OpenCompletionsMsg struct {
}
type FilterCompletionsMsg struct {
Query string // The query to filter completions
Query string // The query to filter completions
Reopen bool
}
type CompletionsClosedMsg struct{}
type CompletionsOpenedMsg struct{}
type CloseCompletionsMsg struct{}
type SelectCompletionMsg struct {
@@ -126,11 +131,7 @@ func (c *completionsCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
case CloseCompletionsMsg:
c.open = false
c.query = ""
return c, tea.Batch(
c.list.SetItems([]util.Model{}),
util.CmdHandler(CompletionsClosedMsg{}),
)
return c, util.CmdHandler(CompletionsClosedMsg{})
case OpenCompletionsMsg:
c.open = true
c.query = ""
@@ -143,21 +144,41 @@ func (c *completionsCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
items = append(items, item)
}
c.height = max(min(c.height, len(items)), 1) // Ensure at least 1 item height
cmds := []tea.Cmd{
return c, tea.Batch(
c.list.SetSize(c.width, c.height),
c.list.SetItems(items),
}
return c, tea.Batch(cmds...)
util.CmdHandler(CompletionsOpenedMsg{}),
)
case FilterCompletionsMsg:
c.query = msg.Query
if !c.open {
return c, nil // If completions are not open, do nothing
if !c.open && !msg.Reopen {
return c, nil
}
if msg.Query == c.query {
// PERF: if same query, don't need to filter again
return c, nil
}
if len(c.list.Items()) == 0 &&
len(msg.Query) > len(c.query) &&
strings.HasPrefix(msg.Query, c.query) {
// PERF: if c.query didn't match anything,
// AND msg.Query is longer than c.query,
// AND msg.Query is prefixed with c.query - which means
// that the user typed more chars after a 0 match,
// it won't match anything, so return earlier.
return c, nil
}
c.query = msg.Query
var cmds []tea.Cmd
cmds = append(cmds, c.list.Filter(msg.Query))
itemsLen := len(c.list.Items())
c.height = max(min(maxCompletionsHeight, itemsLen), 1)
cmds = append(cmds, c.list.SetSize(c.width, c.height))
if itemsLen == 0 {
cmds = append(cmds, util.CmdHandler(CloseCompletionsMsg{}))
} else if msg.Reopen {
c.open = true
cmds = append(cmds, util.CmdHandler(CompletionsOpenedMsg{}))
}
return c, tea.Batch(cmds...)
}
return c, nil
@@ -165,12 +186,9 @@ func (c *completionsCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// View implements Completions.
func (c *completionsCmp) View() string {
if !c.open {
if !c.open || len(c.list.Items()) == 0 {
return ""
}
if len(c.list.Items()) == 0 {
return c.style().Render("No completions found")
}
return c.style().Render(c.list.View())
}

View File

@@ -518,9 +518,9 @@ func (m Model) canSelect(file string) bool {
}
// HighlightedPath returns the path of the currently highlighted file or directory.
func (M Model) HighlightedPath() string {
if len(M.files) == 0 || M.selected < 0 || M.selected >= len(M.files) {
func (m Model) HighlightedPath() string {
if len(m.files) == 0 || m.selected < 0 || m.selected >= len(m.files) {
return ""
}
return filepath.Join(M.CurrentDirectory, M.files[M.selected].Name())
return filepath.Join(m.CurrentDirectory, m.files[m.selected].Name())
}

View File

@@ -722,6 +722,41 @@ func (m *Model) Reset() {
m.SetCursorColumn(0)
}
// Word returns the word at the cursor position.
// A word is delimited by spaces or line-breaks.
func (m *Model) Word() string {
line := m.value[m.row]
col := m.col - 1
if col < 0 {
return ""
}
// If cursor is beyond the line, return empty string
if col >= len(line) {
return ""
}
// If cursor is on a space, return empty string
if unicode.IsSpace(line[col]) {
return ""
}
// Find the start of the word by moving left
start := col
for start > 0 && !unicode.IsSpace(line[start-1]) {
start--
}
// Find the end of the word by moving right
end := col
for end < len(line) && !unicode.IsSpace(line[end]) {
end++
}
return string(line[start:end])
}
// san initializes or retrieves the rune sanitizer.
func (m *Model) san() runeutil.Sanitizer {
if m.rsan == nil {

2
vendor/modules.txt vendored
View File

@@ -241,7 +241,7 @@ github.com/bmatcuk/doublestar/v4
github.com/charlievieth/fastwalk
github.com/charlievieth/fastwalk/internal/dirent
github.com/charlievieth/fastwalk/internal/fmtdirent
# github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250710161907-a4c42b579198
# github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5
## explicit; go 1.23.0
github.com/charmbracelet/bubbles/v2/cursor
github.com/charmbracelet/bubbles/v2/filepicker