fix: data race and overlay bugs (#20)

- redundant ghost text
- prompt deletion bug on overlay
- overlay bug on the non-exists commands

<img width="1152" height="221" alt="image"
src="https://github.com/user-attachments/assets/29ef0c1f-4cd8-4e36-960f-352010fc3bd1"
/>
<img width="1275" height="470" alt="image"
src="https://github.com/user-attachments/assets/aa765110-f9fa-49c8-94f2-767a827f5e5f"
/>
<img width="2517" height="152" alt="image"
src="https://github.com/user-attachments/assets/47a6668f-fb01-46be-9ad2-2c50c283f175"
/>
This commit is contained in:
VERSE
2026-07-03 12:29:42 +07:00
committed by GitHub
parent ea82216dc9
commit ca9d30af9c
5 changed files with 285 additions and 147 deletions
+168 -28
View File
@@ -195,21 +195,152 @@ func (o *Overlay) UpdateItems(items []core.Suggestion) {
o.StartIdx = 0
}
func (o *Overlay) IsVisible() bool {
o.mu.Lock()
defer o.mu.Unlock()
return o.Visible
}
func (o *Overlay) GetUserNavigated() bool {
o.mu.Lock()
defer o.mu.Unlock()
return o.UserNavigated
}
func (o *Overlay) SetUserNavigated(v bool) {
o.mu.Lock()
defer o.mu.Unlock()
o.UserNavigated = v
}
func (o *Overlay) GetTypedQuery() string {
o.mu.Lock()
defer o.mu.Unlock()
return o.TypedQuery
}
func (o *Overlay) GetCurrentCmd() string {
o.mu.Lock()
defer o.mu.Unlock()
if len(o.Items) > 0 && o.Cursor >= 0 && o.Cursor < len(o.Items) {
return o.Items[o.Cursor].Cmd
}
return ""
}
func (o *Overlay) GetTopCmd() string {
o.mu.Lock()
defer o.mu.Unlock()
if len(o.Items) > 0 {
return o.Items[0].Cmd
}
return ""
}
func (o *Overlay) Show() {
o.mu.Lock()
defer o.mu.Unlock()
o.UserNavigated = false
o.Visible = true
}
func (o *Overlay) ResetCursor() {
o.mu.Lock()
defer o.mu.Unlock()
o.Cursor = 0
}
func (o *Overlay) SetQueryAndItems(query string, items []core.Suggestion) {
o.mu.Lock()
defer o.mu.Unlock()
o.TypedQuery = query
o.UserNavigated = false
o.Items = items
o.Visible = len(o.Items) > 0
o.Cursor = 0
o.StartIdx = 0
}
func (o *Overlay) ClearGhostLen() int {
o.mu.Lock()
defer o.mu.Unlock()
l := o.LastGhostLen
o.LastGhostLen = 0
return l
}
func (o *Overlay) MoveCursor(dir string) (moved bool, selectedCmd string) {
o.mu.Lock()
defer o.mu.Unlock()
if !o.Visible || len(o.Items) == 0 {
return false, ""
}
o.UserNavigated = true
oldCursor := o.Cursor
if dir == "up" {
o.Cursor--
if o.Cursor < 0 {
o.Cursor = 0
}
} else {
o.Cursor++
if o.Cursor >= len(o.Items) {
o.Cursor = len(o.Items) - 1
}
}
if o.Cursor == oldCursor {
return false, ""
}
return true, o.Items[o.Cursor].Cmd
}
func (o *Overlay) SetHistoryList(items []core.Suggestion, startAtBottom bool) string {
o.mu.Lock()
defer o.mu.Unlock()
o.TypedQuery = ""
o.UserNavigated = true
o.Items = items
o.Visible = len(o.Items) > 0
if startAtBottom && len(o.Items) > 0 {
o.Cursor = len(o.Items) - 1
} else {
o.Cursor = 0
}
o.StartIdx = 0
if len(o.Items) > 0 && o.Cursor >= 0 && o.Cursor < len(o.Items) {
return o.Items[o.Cursor].Cmd
}
return ""
}
func fixedWidth(s string, width int) string {
if width <= 0 {
return ""
}
runes := []rune(s)
if len(runes) > width {
if width == 1 {
return "…"
visualWidth := lipgloss.Width(s)
if visualWidth == width {
return s
}
if visualWidth < width {
return s + strings.Repeat(" ", width-visualWidth)
}
var sb strings.Builder
currentWidth := 0
for _, r := range s {
rw := lipgloss.Width(string(r))
if currentWidth+rw > width-1 {
break
}
return string(runes[:width-1]) + "…"
sb.WriteRune(r)
currentWidth += rw
}
if len(runes) < width {
return s + strings.Repeat(" ", width-len(runes))
sb.WriteString("…")
rem := width - lipgloss.Width(sb.String())
if rem > 0 {
sb.WriteString(strings.Repeat(" ", rem))
}
return s
return sb.String()
}
func (o *Overlay) RenderGhostText(buffer string, userNavigated bool) string {
@@ -229,26 +360,31 @@ func (o *Overlay) RenderGhostText(buffer string, userNavigated bool) string {
}
}
padLen := o.LastGhostLen - len(ghostText)
if ghostText == "" && o.LastGhostLen == 0 {
return ""
}
ghostWidth := lipgloss.Width(ghostText)
padLen := o.LastGhostLen - ghostWidth
if padLen < 0 {
padLen = 0
}
padLen += 10
if ghostText != "" || padLen > 0 {
s.WriteString("\0337")
if ghostText != "" {
s.WriteString("\033[90m")
s.WriteString(ghostText)
s.WriteString("\033[0m")
}
if padLen > 0 {
s.WriteString(strings.Repeat(" ", padLen))
}
s.WriteString("\0338")
o.LastGhostLen = len(ghostText)
if o.LastGhostLen > 0 {
padLen += 4
}
s.WriteString("\0337")
if ghostText != "" {
s.WriteString("\033[90m")
s.WriteString(ghostText)
s.WriteString("\033[0m")
}
if padLen > 0 {
s.WriteString(strings.Repeat(" ", padLen))
}
s.WriteString("\0338")
o.LastGhostLen = ghostWidth
return s.String()
}
@@ -272,11 +408,13 @@ func renderMatchedTitle(title, typed string, selected bool, w int) string {
return base.Render(display)
}
matchLen := len(typed)
if matchLen > len(display) {
matchLen = len(display)
typedRunes := []rune(typed)
displayRunes := []rune(display)
matchLen := len(typedRunes)
if matchLen > len(displayRunes) {
matchLen = len(displayRunes)
}
return match.Render(display[:matchLen]) + base.Render(display[matchLen:])
return match.Render(string(displayRunes[:matchLen])) + base.Render(string(displayRunes[matchLen:]))
}
func (o *Overlay) Render() string {
@@ -396,7 +534,7 @@ func (o *Overlay) draw() string {
padGap := 2
markerW := 1
iconW := 2
if isClassic {
if isClassic || !config.Get().UI.NerdFonts {
iconW = 0
}
sidePad := 1
@@ -583,6 +721,8 @@ func (o *Overlay) ClearAndDisable() string {
o.Items = nil
o.TypedQuery = ""
o.UserNavigated = false
o.Cursor = 0
o.StartIdx = 0
var s strings.Builder
s.WriteString("\033[?7l")