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
+1
View File
@@ -272,6 +272,7 @@ func Lookup(input string) []Suggestion {
} }
func topLevelSuggestions(query string, aliases map[string]string) []Suggestion { func topLevelSuggestions(query string, aliases map[string]string) []Suggestion {
scanExternalCommands()
results, seen := []Suggestion{}, make(map[string]bool) results, seen := []Suggestion{}, make(map[string]bool)
for name, target := range aliases { for name, target := range aliases {
+168 -28
View File
@@ -195,21 +195,152 @@ func (o *Overlay) UpdateItems(items []core.Suggestion) {
o.StartIdx = 0 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 { func fixedWidth(s string, width int) string {
if width <= 0 { if width <= 0 {
return "" return ""
} }
runes := []rune(s) visualWidth := lipgloss.Width(s)
if len(runes) > width { if visualWidth == width {
if width == 1 { return s
return "…" }
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 { sb.WriteString("…")
return s + strings.Repeat(" ", width-len(runes)) 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 { 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 { if padLen < 0 {
padLen = 0 padLen = 0
} }
padLen += 10 if o.LastGhostLen > 0 {
padLen += 4
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)
} }
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() return s.String()
} }
@@ -272,11 +408,13 @@ func renderMatchedTitle(title, typed string, selected bool, w int) string {
return base.Render(display) return base.Render(display)
} }
matchLen := len(typed) typedRunes := []rune(typed)
if matchLen > len(display) { displayRunes := []rune(display)
matchLen = len(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 { func (o *Overlay) Render() string {
@@ -396,7 +534,7 @@ func (o *Overlay) draw() string {
padGap := 2 padGap := 2
markerW := 1 markerW := 1
iconW := 2 iconW := 2
if isClassic { if isClassic || !config.Get().UI.NerdFonts {
iconW = 0 iconW = 0
} }
sidePad := 1 sidePad := 1
@@ -583,6 +721,8 @@ func (o *Overlay) ClearAndDisable() string {
o.Items = nil o.Items = nil
o.TypedQuery = "" o.TypedQuery = ""
o.UserNavigated = false o.UserNavigated = false
o.Cursor = 0
o.StartIdx = 0
var s strings.Builder var s strings.Builder
s.WriteString("\033[?7l") s.WriteString("\033[?7l")
+5
View File
@@ -45,11 +45,16 @@ if [ -n "$IRIS_PID" ] && [ -n "$IRIS_FD" ]; then
print -u $IRIS_FD -N -r -- "IRIS_CMD_STOP" 2>/dev/null print -u $IRIS_FD -N -r -- "IRIS_CMD_STOP" 2>/dev/null
} }
_iris_preexec() {
print -u $IRIS_FD -N -r -- "IRIS_CMD_START" 2>/dev/null
}
autoload -Uz add-zle-hook-widget autoload -Uz add-zle-hook-widget
autoload -Uz add-zsh-hook autoload -Uz add-zsh-hook
add-zle-hook-widget line-pre-redraw _iris_send_lbuffer add-zle-hook-widget line-pre-redraw _iris_send_lbuffer
add-zsh-hook precmd _iris_precmd add-zsh-hook precmd _iris_precmd
add-zsh-hook preexec _iris_preexec
fi fi
`) `)
case "bash": case "bash":
+106 -119
View File
@@ -220,7 +220,11 @@ func runWrapper() {
if err != nil { if err != nil {
shellPGID = core.ShellPID shellPGID = core.ShellPID
} }
var isCommandActive atomic.Bool
isExecuting := func() bool { isExecuting := func() bool {
if isCommandActive.Load() {
return true
}
pgrp, err := unix.IoctlGetInt(int(ptmx.Fd()), unix.TIOCGPGRP) pgrp, err := unix.IoctlGetInt(int(ptmx.Fd()), unix.TIOCGPGRP)
if err != nil { if err != nil {
return false return false
@@ -304,7 +308,18 @@ func runWrapper() {
for scanner.Scan() { for scanner.Scan() {
query := scanner.Text() query := scanner.Text()
if query == "IRIS_CMD_START" {
isCommandActive.Store(true)
bufferMu.Lock()
naiveBuffer = ""
cursorOffset = 0
bufferMu.Unlock()
writeStdout([]byte(overlay.ClearAndDisable()))
continue
}
if query == "IRIS_CMD_STOP" { if query == "IRIS_CMD_STOP" {
isCommandActive.Store(false)
// hook: after user executes a command, print the update notice exactly once per session // hook: after user executes a command, print the update notice exactly once per session
if !updatePrinted { if !updatePrinted {
select { select {
@@ -319,44 +334,34 @@ func runWrapper() {
continue continue
} }
if overlay.UserNavigated { isCommandActive.Store(false)
if overlay.GetUserNavigated() {
continue continue
} }
if query == "" { if query == "" {
bufferMu.Lock() bufferMu.Lock()
wasEmpty := naiveBuffer == ""
naiveBuffer = "" naiveBuffer = ""
cursorOffset = 0 cursorOffset = 0
bufferMu.Unlock() bufferMu.Unlock()
writeStdout([]byte(overlay.ClearAndDisable())) if !wasEmpty {
writeStdout([]byte(overlay.ClearAndDisable()))
}
continue continue
} }
// sync local buffer with actual command line
bufferMu.Lock() bufferMu.Lock()
if naiveBuffer == query {
bufferMu.Unlock()
continue
}
naiveBuffer = query naiveBuffer = query
cursorOffset = 0 cursorOffset = 0
bufferMu.Unlock() bufferMu.Unlock()
activeModeMu.RLock() renderOverlay()
currentMode := activeMode
activeModeMu.RUnlock()
results := MergeResults(query, currentMode)
if len(results) == 0 {
writeStdout([]byte(overlay.ClearAndDisable()))
continue
}
writeStdout([]byte(overlay.Clear()))
overlay.TypedQuery = query
overlay.UserNavigated = false
overlay.UpdateItems(results)
var rBuf strings.Builder
if !disableGhostText.Load() {
rBuf.WriteString(overlay.RenderGhostText(query, false))
}
rBuf.WriteString(overlay.Render())
writeStdout([]byte(rBuf.String()))
} }
if err := scanner.Err(); err != nil { if err := scanner.Err(); err != nil {
logger.Errorf("IPC scanner error: %v", err) logger.Errorf("IPC scanner error: %v", err)
@@ -397,7 +402,7 @@ func runWrapper() {
var b strings.Builder var b strings.Builder
if !navCopy { if !navCopy {
if bufCopy == "" && !overlay.Visible { if bufCopy == "" && !overlay.IsVisible() {
writeStdout([]byte(overlay.ClearAndDisable())) writeStdout([]byte(overlay.ClearAndDisable()))
return return
} }
@@ -411,26 +416,22 @@ func runWrapper() {
return return
} }
if overlay.Visible { if overlay.IsVisible() {
b.WriteString(overlay.Clear()) b.WriteString(overlay.Clear())
} }
overlay.TypedQuery = bufCopy overlay.SetQueryAndItems(bufCopy, results)
overlay.UpdateItems(results)
} else { } else {
if overlay.Visible { if overlay.IsVisible() {
b.WriteString(overlay.Clear()) b.WriteString(overlay.Clear())
} }
} }
overlay.UserNavigated = navCopy overlay.SetUserNavigated(navCopy)
if !disableGhostText.Load() { if !disableGhostText.Load() {
b.WriteString(overlay.RenderGhostText(bufCopy, navCopy)) b.WriteString(overlay.RenderGhostText(bufCopy, navCopy))
} }
currentCmd := "" currentCmd := overlay.GetCurrentCmd()
if len(overlay.Items) > 0 && overlay.Cursor >= 0 && overlay.Cursor < len(overlay.Items) { logger.Debugf("RenderOverlay nav: %v, typedQuery: '%s', currentCmd: '%s'", navCopy, overlay.GetTypedQuery(), currentCmd)
currentCmd = overlay.Items[overlay.Cursor].Cmd
}
logger.Debugf("RenderOverlay nav: %v, cursor: %d, typedQuery: '%s', currentCmd: '%s'", navCopy, overlay.Cursor, overlay.TypedQuery, currentCmd)
b.WriteString(overlay.Render()) b.WriteString(overlay.Render())
writeStdout([]byte(b.String())) writeStdout([]byte(b.String()))
} }
@@ -515,44 +516,28 @@ func runWrapper() {
continue continue
} }
if overlay.Visible && (inputSlice[i+2] == 'A' || inputSlice[i+2] == 'B') { if overlay.IsVisible() && (inputSlice[i+2] == 'A' || inputSlice[i+2] == 'B') {
intercepted = true intercepted = true
userNavigated.Store(true) userNavigated.Store(true)
overlay.UserNavigated = true
// clear ghost text synchronously if l := overlay.ClearGhostLen(); l > 0 {
if overlay.LastGhostLen > 0 {
var gs strings.Builder var gs strings.Builder
gs.WriteString("\0337") gs.WriteString("\0337")
gs.WriteString(strings.Repeat(" ", overlay.LastGhostLen+10)) gs.WriteString(strings.Repeat(" ", l+10))
gs.WriteString("\0338") gs.WriteString("\0338")
writeStdout([]byte(gs.String())) writeStdout([]byte(gs.String()))
overlay.LastGhostLen = 0
} }
oldCursor := overlay.Cursor
arrowDir := "down" arrowDir := "down"
if inputSlice[i+2] == 'A' { if inputSlice[i+2] == 'A' {
arrowDir = "up" arrowDir = "up"
overlay.Cursor--
if overlay.Cursor < 0 {
overlay.Cursor = 0
}
} else {
overlay.Cursor++
if overlay.Cursor >= len(overlay.Items) {
overlay.Cursor = len(overlay.Items) - 1
}
} }
logger.Debugf("Intercepted %s Arrow, cursor moved %d -> %d", arrowDir, oldCursor, overlay.Cursor) moved, selected := overlay.MoveCursor(arrowDir)
if !moved {
// boundary hit - ignore redundant write to avoid PTY flooding
if overlay.Cursor == oldCursor {
i += 2 i += 2
continue continue
} }
selected := overlay.Items[overlay.Cursor].Cmd
bufferMu.Lock() bufferMu.Lock()
naiveBuffer = selected naiveBuffer = selected
cursorOffset = 0 cursorOffset = 0
@@ -563,7 +548,7 @@ func runWrapper() {
i += 2 i += 2
continue continue
} else if !overlay.Visible && naiveBuffer == "" && (inputSlice[i+2] == 'A' || inputSlice[i+2] == 'B') { // up/down arrow on empty prompt } else if !overlay.IsVisible() && naiveBuffer == "" && (inputSlice[i+2] == 'A' || inputSlice[i+2] == 'B') { // up/down arrow on empty prompt
intercepted = true intercepted = true
activeModeMu.Lock() activeModeMu.Lock()
activeMode = "history" activeMode = "history"
@@ -582,43 +567,32 @@ func runWrapper() {
var historyList []core.Suggestion var historyList []core.Suggestion
if inputSlice[i+2] == 'A' { if inputSlice[i+2] == 'A' {
// up arrow: reverse the list so newest is at the bottom
for j := limit - 1; j >= 0; j-- { for j := limit - 1; j >= 0; j-- {
historyList = append(historyList, results[j]) historyList = append(historyList, results[j])
} }
} else { } else {
// down arrow: normal order, newest is at the top
for j := 0; j < limit; j++ { for j := 0; j < limit; j++ {
historyList = append(historyList, results[j]) historyList = append(historyList, results[j])
} }
} }
overlay.TypedQuery = "" selected := overlay.SetHistoryList(historyList, inputSlice[i+2] == 'A')
overlay.UpdateItems(historyList) if selected != "" {
bufferMu.Lock()
naiveBuffer = selected
cursorOffset = 0
bufferMu.Unlock()
if inputSlice[i+2] == 'A' { userNavigated.Store(true)
overlay.Cursor = len(historyList) - 1 // up arrow: start at the bottom writeStdout([]byte(overlay.Render()))
} else { _, _ = ptmx.Write(append([]byte{0x15}, selected...))
overlay.Cursor = 0 // down arrow: start at the top
} }
selected := overlay.Items[overlay.Cursor].Cmd
bufferMu.Lock()
naiveBuffer = selected
cursorOffset = 0
bufferMu.Unlock()
userNavigated.Store(true)
overlay.UserNavigated = true
writeStdout([]byte(overlay.Render()))
_, _ = ptmx.Write(append([]byte{0x15}, selected...))
} }
i += 2 i += 2
continue continue
} else if overlay.Visible && !disableGhostText.Load() && inputSlice[i+2] == 'C' { // right arrow } else if overlay.IsVisible() && !disableGhostText.Load() && inputSlice[i+2] == 'C' { // right arrow
bufferMu.Lock() bufferMu.Lock()
topCmd := overlay.Items[0].Cmd topCmd := overlay.GetTopCmd()
hasMatch := strings.HasPrefix(strings.ToLower(topCmd), strings.ToLower(naiveBuffer)) hasMatch := strings.HasPrefix(strings.ToLower(topCmd), strings.ToLower(naiveBuffer))
var ghostText string var ghostText string
if hasMatch { if hasMatch {
@@ -646,7 +620,7 @@ func runWrapper() {
if i+2 < n && (inputSlice[i+1] == '[' || inputSlice[i+1] == 'O') { if i+2 < n && (inputSlice[i+1] == '[' || inputSlice[i+1] == 'O') {
if inputSlice[i+2] == 'D' { if inputSlice[i+2] == 'D' {
bufferMu.Lock() bufferMu.Lock()
isEmptyQuery := (overlay.Visible && overlay.TypedQuery == "") || (!overlay.Visible && naiveBuffer == "") isEmptyQuery := (overlay.IsVisible() && overlay.GetTypedQuery() == "") || (!overlay.IsVisible() && naiveBuffer == "")
bufferMu.Unlock() bufferMu.Unlock()
if isEmptyQuery { if isEmptyQuery {
intercepted = true intercepted = true
@@ -654,7 +628,7 @@ func runWrapper() {
continue continue
} }
bufferMu.Lock() bufferMu.Lock()
if naiveBuffer != "" || overlay.Visible { if naiveBuffer != "" || overlay.IsVisible() {
cursorOffset++ cursorOffset++
if cursorOffset > len(naiveBuffer) { if cursorOffset > len(naiveBuffer) {
cursorOffset = len(naiveBuffer) cursorOffset = len(naiveBuffer)
@@ -666,7 +640,7 @@ func runWrapper() {
isLeftRightArrow = true isLeftRightArrow = true
} else if inputSlice[i+2] == 'C' { } else if inputSlice[i+2] == 'C' {
bufferMu.Lock() bufferMu.Lock()
isEmptyQuery := (overlay.Visible && overlay.TypedQuery == "") || (!overlay.Visible && naiveBuffer == "") isEmptyQuery := (overlay.IsVisible() && overlay.GetTypedQuery() == "") || (!overlay.IsVisible() && naiveBuffer == "")
bufferMu.Unlock() bufferMu.Unlock()
if isEmptyQuery { if isEmptyQuery {
intercepted = true intercepted = true
@@ -674,7 +648,7 @@ func runWrapper() {
continue continue
} }
bufferMu.Lock() bufferMu.Lock()
if naiveBuffer != "" || overlay.Visible { if naiveBuffer != "" || overlay.IsVisible() {
cursorOffset-- cursorOffset--
if cursorOffset < 0 { if cursorOffset < 0 {
cursorOffset = 0 cursorOffset = 0
@@ -724,35 +698,62 @@ func runWrapper() {
logger.Debugf("Intercepted Ctrl+R, toggled mode to %q", activeMode) logger.Debugf("Intercepted Ctrl+R, toggled mode to %q", activeMode)
if userNavigated.Load() { if userNavigated.Load() {
bufferMu.Lock() bufferMu.Lock()
naiveBuffer = overlay.TypedQuery naiveBuffer = overlay.GetTypedQuery()
cursorOffset = 0 cursorOffset = 0
bufferMu.Unlock() bufferMu.Unlock()
_, _ = ptmx.Write(append([]byte{0x15}, overlay.TypedQuery...)) _, _ = ptmx.Write(append([]byte{0x15}, overlay.GetTypedQuery()...))
} }
userNavigated.Store(false) userNavigated.Store(false)
overlay.UserNavigated = false overlay.Show()
overlay.Visible = true
shouldOverlayDraw = true shouldOverlayDraw = true
// enter: enter behavior is a bit different from tab suggestions in code editor // enter: enter behavior is a bit different from tab suggestions in code editor
// I want it to execute the command anyway and ignore the suggestions // I want it to execute the command anyway and ignore the suggestions
// it means only tab to select suggestions, and enter to execute // it means only tab to select suggestions, and enter to execute
// enter is not used to select suggestions // enter is not used to select suggestions
} else if overlay.Visible && (b == 0x0d || b == 0x0a) { } else if b == 0x0d || b == 0x0a {
intercepted = true intercepted = true
logger.Debugf("Intercepted Enter key, navigated=%v", overlay.UserNavigated) logger.Debugf("Intercepted Enter key, navigated=%v", overlay.GetUserNavigated())
if overlay.UserNavigated && len(overlay.Items) > 0 && overlay.Cursor >= 0 && overlay.Cursor < len(overlay.Items) { if overlay.IsVisible() && overlay.GetUserNavigated() {
selected := overlay.Items[overlay.Cursor].Cmd selected := overlay.GetCurrentCmd()
activeModeMu.RLock() if selected != "" {
currentMode := activeMode activeModeMu.RLock()
activeModeMu.RUnlock() currentMode := activeMode
if currentMode == "spec" { activeModeMu.RUnlock()
selected = strings.TrimSpace(selected) + " " if currentMode == "spec" {
selected = strings.TrimSpace(selected) + " "
}
_, _ = ptmx.Write(append([]byte{0x15}, selected...))
} }
_, _ = ptmx.Write(append([]byte{0x15}, selected...))
} }
writeStdout([]byte(overlay.ClearAndDisable())) writeStdout([]byte(overlay.ClearAndDisable()))
renderMu.Lock()
if renderTimer != nil {
renderTimer.Stop()
renderTimer = nil
}
renderMu.Unlock()
_, _ = ptmx.Write([]byte{0x0d}) isCommandActive.Store(true)
_, _ = ptmx.Write([]byte{b})
bufferMu.Lock()
naiveBuffer = ""
cursorOffset = 0
bufferMu.Unlock()
disableGhostText.Store(false)
shouldOverlayDraw = false
userNavigated.Store(false)
continue
} else if b == 0x03 || b == 0x15 { // ctrl+c, ctrl+u
intercepted = true
writeStdout([]byte(overlay.ClearAndDisable()))
renderMu.Lock()
if renderTimer != nil {
renderTimer.Stop()
renderTimer = nil
}
renderMu.Unlock()
isCommandActive.Store(false)
_, _ = ptmx.Write([]byte{b})
bufferMu.Lock() bufferMu.Lock()
naiveBuffer = "" naiveBuffer = ""
cursorOffset = 0 cursorOffset = 0
@@ -763,11 +764,11 @@ func runWrapper() {
continue continue
} else if b == 0x09 { // tab: select suggestions } else if b == 0x09 { // tab: select suggestions
intercepted = true intercepted = true
logger.Debugf("Intercepted Tab key, visible=%v, cursor=%d", overlay.Visible, overlay.Cursor) logger.Debugf("Intercepted Tab key, visible=%v", overlay.IsVisible())
if !overlay.Visible { if !overlay.IsVisible() {
shouldOverlayDraw = true shouldOverlayDraw = true
} else { } else {
selected := overlay.Items[overlay.Cursor].Cmd selected := overlay.GetCurrentCmd()
writeStdout([]byte(overlay.ClearAndDisable())) writeStdout([]byte(overlay.ClearAndDisable()))
activeModeMu.RLock() activeModeMu.RLock()
@@ -784,7 +785,7 @@ func runWrapper() {
_, _ = ptmx.Write(append([]byte{0x15}, selected...)) _, _ = ptmx.Write(append([]byte{0x15}, selected...))
overlay.Cursor = 0 // this prevents when you tab, it switches between suggestions non-stop overlay.ResetCursor() // this prevents when you tab, it switches between suggestions non-stop
shouldOverlayDraw = true // <- rerender after tab to choose, if you set to false, shouldOverlayDraw = true // <- rerender after tab to choose, if you set to false,
// when you press tab continually, it will print all folder from menu suggestions // when you press tab continually, it will print all folder from menu suggestions
@@ -802,7 +803,7 @@ func runWrapper() {
case 0x01: // ctrl+a: move to beginning of line case 0x01: // ctrl+a: move to beginning of line
bufferMu.Lock() bufferMu.Lock()
cursorOffset = len(naiveBuffer) cursorOffset = len(naiveBuffer)
if naiveBuffer != "" || overlay.Visible { if naiveBuffer != "" || overlay.IsVisible() {
shouldOverlayDraw = true shouldOverlayDraw = true
} }
bufferMu.Unlock() bufferMu.Unlock()
@@ -810,7 +811,7 @@ func runWrapper() {
case 0x05: // ctrl+e: move to end of line case 0x05: // ctrl+e: move to end of line
bufferMu.Lock() bufferMu.Lock()
cursorOffset = 0 cursorOffset = 0
if naiveBuffer != "" || overlay.Visible { if naiveBuffer != "" || overlay.IsVisible() {
shouldOverlayDraw = true shouldOverlayDraw = true
} }
bufferMu.Unlock() bufferMu.Unlock()
@@ -839,14 +840,7 @@ func runWrapper() {
isEmptyNow := len(naiveBuffer) == 0 isEmptyNow := len(naiveBuffer) == 0
bufferMu.Unlock() bufferMu.Unlock()
if wasEmpty { if wasEmpty || isEmptyNow {
if overlay.Visible {
writeStdout([]byte(overlay.ClearAndDisable()))
}
userNavigated.Store(false)
continue
}
if isEmptyNow {
writeStdout([]byte(overlay.ClearAndDisable())) writeStdout([]byte(overlay.ClearAndDisable()))
userNavigated.Store(false) userNavigated.Store(false)
continue continue
@@ -867,14 +861,7 @@ func runWrapper() {
isEmptyNow := len(naiveBuffer) == 0 isEmptyNow := len(naiveBuffer) == 0
bufferMu.Unlock() bufferMu.Unlock()
if wasEmpty { if wasEmpty || isEmptyNow {
if overlay.Visible {
writeStdout([]byte(overlay.ClearAndDisable()))
}
userNavigated.Store(false)
continue
}
if isEmptyNow {
writeStdout([]byte(overlay.ClearAndDisable())) writeStdout([]byte(overlay.ClearAndDisable()))
userNavigated.Store(false) userNavigated.Store(false)
continue continue
@@ -936,7 +923,7 @@ func runWrapper() {
bufferMu.Unlock() bufferMu.Unlock()
shouldOverlayDraw = true shouldOverlayDraw = true
userNavigated.Store(false) userNavigated.Store(false)
overlay.UserNavigated = false overlay.SetUserNavigated(false)
} }
} }
} }
+5
View File
@@ -12,9 +12,14 @@ if [[ -n "$IRIS_FD" ]]; then
print -u $IRIS_FD -N -r -- "IRIS_CMD_STOP" 2>/dev/null print -u $IRIS_FD -N -r -- "IRIS_CMD_STOP" 2>/dev/null
} }
_iris_preexec() {
print -u $IRIS_FD -N -r -- "IRIS_CMD_START" 2>/dev/null
}
autoload -Uz add-zle-hook-widget autoload -Uz add-zle-hook-widget
autoload -Uz add-zsh-hook autoload -Uz add-zsh-hook
add-zle-hook-widget line-pre-redraw _iris_send_lbuffer add-zle-hook-widget line-pre-redraw _iris_send_lbuffer
add-zsh-hook precmd _iris_precmd add-zsh-hook precmd _iris_precmd
add-zsh-hook preexec _iris_preexec
fi fi