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:
@@ -272,6 +272,7 @@ func Lookup(input string) []Suggestion {
|
||||
}
|
||||
|
||||
func topLevelSuggestions(query string, aliases map[string]string) []Suggestion {
|
||||
scanExternalCommands()
|
||||
results, seen := []Suggestion{}, make(map[string]bool)
|
||||
|
||||
for name, target := range aliases {
|
||||
|
||||
+160
-20
@@ -195,22 +195,153 @@ 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 "…"
|
||||
}
|
||||
return string(runes[:width-1]) + "…"
|
||||
}
|
||||
if len(runes) < width {
|
||||
return s + strings.Repeat(" ", width-len(runes))
|
||||
}
|
||||
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
|
||||
}
|
||||
sb.WriteRune(r)
|
||||
currentWidth += rw
|
||||
}
|
||||
sb.WriteString("…")
|
||||
rem := width - lipgloss.Width(sb.String())
|
||||
if rem > 0 {
|
||||
sb.WriteString(strings.Repeat(" ", rem))
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (o *Overlay) RenderGhostText(buffer string, userNavigated bool) string {
|
||||
o.mu.Lock()
|
||||
@@ -229,13 +360,19 @@ 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 o.LastGhostLen > 0 {
|
||||
padLen += 4
|
||||
}
|
||||
|
||||
if ghostText != "" || padLen > 0 {
|
||||
s.WriteString("\0337")
|
||||
if ghostText != "" {
|
||||
s.WriteString("\033[90m")
|
||||
@@ -246,8 +383,7 @@ func (o *Overlay) RenderGhostText(buffer string, userNavigated bool) string {
|
||||
s.WriteString(strings.Repeat(" ", padLen))
|
||||
}
|
||||
s.WriteString("\0338")
|
||||
o.LastGhostLen = len(ghostText)
|
||||
}
|
||||
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")
|
||||
|
||||
@@ -45,11 +45,16 @@ if [ -n "$IRIS_PID" ] && [ -n "$IRIS_FD" ]; then
|
||||
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-zsh-hook
|
||||
|
||||
add-zle-hook-widget line-pre-redraw _iris_send_lbuffer
|
||||
add-zsh-hook precmd _iris_precmd
|
||||
add-zsh-hook preexec _iris_preexec
|
||||
fi
|
||||
`)
|
||||
case "bash":
|
||||
|
||||
+93
-106
@@ -220,7 +220,11 @@ func runWrapper() {
|
||||
if err != nil {
|
||||
shellPGID = core.ShellPID
|
||||
}
|
||||
var isCommandActive atomic.Bool
|
||||
isExecuting := func() bool {
|
||||
if isCommandActive.Load() {
|
||||
return true
|
||||
}
|
||||
pgrp, err := unix.IoctlGetInt(int(ptmx.Fd()), unix.TIOCGPGRP)
|
||||
if err != nil {
|
||||
return false
|
||||
@@ -304,7 +308,18 @@ func runWrapper() {
|
||||
for scanner.Scan() {
|
||||
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" {
|
||||
isCommandActive.Store(false)
|
||||
// hook: after user executes a command, print the update notice exactly once per session
|
||||
if !updatePrinted {
|
||||
select {
|
||||
@@ -319,44 +334,34 @@ func runWrapper() {
|
||||
continue
|
||||
}
|
||||
|
||||
if overlay.UserNavigated {
|
||||
isCommandActive.Store(false)
|
||||
|
||||
if overlay.GetUserNavigated() {
|
||||
continue
|
||||
}
|
||||
|
||||
if query == "" {
|
||||
bufferMu.Lock()
|
||||
wasEmpty := naiveBuffer == ""
|
||||
naiveBuffer = ""
|
||||
cursorOffset = 0
|
||||
bufferMu.Unlock()
|
||||
if !wasEmpty {
|
||||
writeStdout([]byte(overlay.ClearAndDisable()))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// sync local buffer with actual command line
|
||||
bufferMu.Lock()
|
||||
if naiveBuffer == query {
|
||||
bufferMu.Unlock()
|
||||
continue
|
||||
}
|
||||
naiveBuffer = query
|
||||
cursorOffset = 0
|
||||
bufferMu.Unlock()
|
||||
|
||||
activeModeMu.RLock()
|
||||
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()))
|
||||
renderOverlay()
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
logger.Errorf("IPC scanner error: %v", err)
|
||||
@@ -397,7 +402,7 @@ func runWrapper() {
|
||||
|
||||
var b strings.Builder
|
||||
if !navCopy {
|
||||
if bufCopy == "" && !overlay.Visible {
|
||||
if bufCopy == "" && !overlay.IsVisible() {
|
||||
writeStdout([]byte(overlay.ClearAndDisable()))
|
||||
return
|
||||
}
|
||||
@@ -411,26 +416,22 @@ func runWrapper() {
|
||||
return
|
||||
}
|
||||
|
||||
if overlay.Visible {
|
||||
if overlay.IsVisible() {
|
||||
b.WriteString(overlay.Clear())
|
||||
}
|
||||
overlay.TypedQuery = bufCopy
|
||||
overlay.UpdateItems(results)
|
||||
overlay.SetQueryAndItems(bufCopy, results)
|
||||
} else {
|
||||
if overlay.Visible {
|
||||
if overlay.IsVisible() {
|
||||
b.WriteString(overlay.Clear())
|
||||
}
|
||||
}
|
||||
|
||||
overlay.UserNavigated = navCopy
|
||||
overlay.SetUserNavigated(navCopy)
|
||||
if !disableGhostText.Load() {
|
||||
b.WriteString(overlay.RenderGhostText(bufCopy, navCopy))
|
||||
}
|
||||
currentCmd := ""
|
||||
if len(overlay.Items) > 0 && overlay.Cursor >= 0 && overlay.Cursor < len(overlay.Items) {
|
||||
currentCmd = overlay.Items[overlay.Cursor].Cmd
|
||||
}
|
||||
logger.Debugf("RenderOverlay nav: %v, cursor: %d, typedQuery: '%s', currentCmd: '%s'", navCopy, overlay.Cursor, overlay.TypedQuery, currentCmd)
|
||||
currentCmd := overlay.GetCurrentCmd()
|
||||
logger.Debugf("RenderOverlay nav: %v, typedQuery: '%s', currentCmd: '%s'", navCopy, overlay.GetTypedQuery(), currentCmd)
|
||||
b.WriteString(overlay.Render())
|
||||
writeStdout([]byte(b.String()))
|
||||
}
|
||||
@@ -515,44 +516,28 @@ func runWrapper() {
|
||||
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
|
||||
userNavigated.Store(true)
|
||||
overlay.UserNavigated = true
|
||||
|
||||
// clear ghost text synchronously
|
||||
if overlay.LastGhostLen > 0 {
|
||||
if l := overlay.ClearGhostLen(); l > 0 {
|
||||
var gs strings.Builder
|
||||
gs.WriteString("\0337")
|
||||
gs.WriteString(strings.Repeat(" ", overlay.LastGhostLen+10))
|
||||
gs.WriteString(strings.Repeat(" ", l+10))
|
||||
gs.WriteString("\0338")
|
||||
writeStdout([]byte(gs.String()))
|
||||
overlay.LastGhostLen = 0
|
||||
}
|
||||
|
||||
oldCursor := overlay.Cursor
|
||||
arrowDir := "down"
|
||||
if inputSlice[i+2] == 'A' {
|
||||
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)
|
||||
|
||||
// boundary hit - ignore redundant write to avoid PTY flooding
|
||||
if overlay.Cursor == oldCursor {
|
||||
moved, selected := overlay.MoveCursor(arrowDir)
|
||||
if !moved {
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
|
||||
selected := overlay.Items[overlay.Cursor].Cmd
|
||||
bufferMu.Lock()
|
||||
naiveBuffer = selected
|
||||
cursorOffset = 0
|
||||
@@ -563,7 +548,7 @@ func runWrapper() {
|
||||
|
||||
i += 2
|
||||
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
|
||||
activeModeMu.Lock()
|
||||
activeMode = "history"
|
||||
@@ -582,43 +567,32 @@ func runWrapper() {
|
||||
var historyList []core.Suggestion
|
||||
|
||||
if inputSlice[i+2] == 'A' {
|
||||
// up arrow: reverse the list so newest is at the bottom
|
||||
for j := limit - 1; j >= 0; j-- {
|
||||
historyList = append(historyList, results[j])
|
||||
}
|
||||
} else {
|
||||
// down arrow: normal order, newest is at the top
|
||||
for j := 0; j < limit; j++ {
|
||||
historyList = append(historyList, results[j])
|
||||
}
|
||||
}
|
||||
|
||||
overlay.TypedQuery = ""
|
||||
overlay.UpdateItems(historyList)
|
||||
|
||||
if inputSlice[i+2] == 'A' {
|
||||
overlay.Cursor = len(historyList) - 1 // up arrow: start at the bottom
|
||||
} else {
|
||||
overlay.Cursor = 0 // down arrow: start at the top
|
||||
}
|
||||
|
||||
selected := overlay.Items[overlay.Cursor].Cmd
|
||||
selected := overlay.SetHistoryList(historyList, inputSlice[i+2] == 'A')
|
||||
if selected != "" {
|
||||
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
|
||||
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()
|
||||
topCmd := overlay.Items[0].Cmd
|
||||
topCmd := overlay.GetTopCmd()
|
||||
hasMatch := strings.HasPrefix(strings.ToLower(topCmd), strings.ToLower(naiveBuffer))
|
||||
var ghostText string
|
||||
if hasMatch {
|
||||
@@ -646,7 +620,7 @@ func runWrapper() {
|
||||
if i+2 < n && (inputSlice[i+1] == '[' || inputSlice[i+1] == 'O') {
|
||||
if inputSlice[i+2] == 'D' {
|
||||
bufferMu.Lock()
|
||||
isEmptyQuery := (overlay.Visible && overlay.TypedQuery == "") || (!overlay.Visible && naiveBuffer == "")
|
||||
isEmptyQuery := (overlay.IsVisible() && overlay.GetTypedQuery() == "") || (!overlay.IsVisible() && naiveBuffer == "")
|
||||
bufferMu.Unlock()
|
||||
if isEmptyQuery {
|
||||
intercepted = true
|
||||
@@ -654,7 +628,7 @@ func runWrapper() {
|
||||
continue
|
||||
}
|
||||
bufferMu.Lock()
|
||||
if naiveBuffer != "" || overlay.Visible {
|
||||
if naiveBuffer != "" || overlay.IsVisible() {
|
||||
cursorOffset++
|
||||
if cursorOffset > len(naiveBuffer) {
|
||||
cursorOffset = len(naiveBuffer)
|
||||
@@ -666,7 +640,7 @@ func runWrapper() {
|
||||
isLeftRightArrow = true
|
||||
} else if inputSlice[i+2] == 'C' {
|
||||
bufferMu.Lock()
|
||||
isEmptyQuery := (overlay.Visible && overlay.TypedQuery == "") || (!overlay.Visible && naiveBuffer == "")
|
||||
isEmptyQuery := (overlay.IsVisible() && overlay.GetTypedQuery() == "") || (!overlay.IsVisible() && naiveBuffer == "")
|
||||
bufferMu.Unlock()
|
||||
if isEmptyQuery {
|
||||
intercepted = true
|
||||
@@ -674,7 +648,7 @@ func runWrapper() {
|
||||
continue
|
||||
}
|
||||
bufferMu.Lock()
|
||||
if naiveBuffer != "" || overlay.Visible {
|
||||
if naiveBuffer != "" || overlay.IsVisible() {
|
||||
cursorOffset--
|
||||
if cursorOffset < 0 {
|
||||
cursorOffset = 0
|
||||
@@ -724,24 +698,24 @@ func runWrapper() {
|
||||
logger.Debugf("Intercepted Ctrl+R, toggled mode to %q", activeMode)
|
||||
if userNavigated.Load() {
|
||||
bufferMu.Lock()
|
||||
naiveBuffer = overlay.TypedQuery
|
||||
naiveBuffer = overlay.GetTypedQuery()
|
||||
cursorOffset = 0
|
||||
bufferMu.Unlock()
|
||||
_, _ = ptmx.Write(append([]byte{0x15}, overlay.TypedQuery...))
|
||||
_, _ = ptmx.Write(append([]byte{0x15}, overlay.GetTypedQuery()...))
|
||||
}
|
||||
userNavigated.Store(false)
|
||||
overlay.UserNavigated = false
|
||||
overlay.Visible = true
|
||||
overlay.Show()
|
||||
shouldOverlayDraw = true
|
||||
// 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
|
||||
// it means only tab to select suggestions, and enter to execute
|
||||
// enter is not used to select suggestions
|
||||
} else if overlay.Visible && (b == 0x0d || b == 0x0a) {
|
||||
} else if b == 0x0d || b == 0x0a {
|
||||
intercepted = true
|
||||
logger.Debugf("Intercepted Enter key, navigated=%v", overlay.UserNavigated)
|
||||
if overlay.UserNavigated && len(overlay.Items) > 0 && overlay.Cursor >= 0 && overlay.Cursor < len(overlay.Items) {
|
||||
selected := overlay.Items[overlay.Cursor].Cmd
|
||||
logger.Debugf("Intercepted Enter key, navigated=%v", overlay.GetUserNavigated())
|
||||
if overlay.IsVisible() && overlay.GetUserNavigated() {
|
||||
selected := overlay.GetCurrentCmd()
|
||||
if selected != "" {
|
||||
activeModeMu.RLock()
|
||||
currentMode := activeMode
|
||||
activeModeMu.RUnlock()
|
||||
@@ -750,9 +724,36 @@ func runWrapper() {
|
||||
}
|
||||
_, _ = ptmx.Write(append([]byte{0x15}, selected...))
|
||||
}
|
||||
}
|
||||
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()
|
||||
naiveBuffer = ""
|
||||
cursorOffset = 0
|
||||
@@ -763,11 +764,11 @@ func runWrapper() {
|
||||
continue
|
||||
} else if b == 0x09 { // tab: select suggestions
|
||||
intercepted = true
|
||||
logger.Debugf("Intercepted Tab key, visible=%v, cursor=%d", overlay.Visible, overlay.Cursor)
|
||||
if !overlay.Visible {
|
||||
logger.Debugf("Intercepted Tab key, visible=%v", overlay.IsVisible())
|
||||
if !overlay.IsVisible() {
|
||||
shouldOverlayDraw = true
|
||||
} else {
|
||||
selected := overlay.Items[overlay.Cursor].Cmd
|
||||
selected := overlay.GetCurrentCmd()
|
||||
writeStdout([]byte(overlay.ClearAndDisable()))
|
||||
|
||||
activeModeMu.RLock()
|
||||
@@ -784,7 +785,7 @@ func runWrapper() {
|
||||
|
||||
_, _ = 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,
|
||||
// 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
|
||||
bufferMu.Lock()
|
||||
cursorOffset = len(naiveBuffer)
|
||||
if naiveBuffer != "" || overlay.Visible {
|
||||
if naiveBuffer != "" || overlay.IsVisible() {
|
||||
shouldOverlayDraw = true
|
||||
}
|
||||
bufferMu.Unlock()
|
||||
@@ -810,7 +811,7 @@ func runWrapper() {
|
||||
case 0x05: // ctrl+e: move to end of line
|
||||
bufferMu.Lock()
|
||||
cursorOffset = 0
|
||||
if naiveBuffer != "" || overlay.Visible {
|
||||
if naiveBuffer != "" || overlay.IsVisible() {
|
||||
shouldOverlayDraw = true
|
||||
}
|
||||
bufferMu.Unlock()
|
||||
@@ -839,14 +840,7 @@ func runWrapper() {
|
||||
isEmptyNow := len(naiveBuffer) == 0
|
||||
bufferMu.Unlock()
|
||||
|
||||
if wasEmpty {
|
||||
if overlay.Visible {
|
||||
writeStdout([]byte(overlay.ClearAndDisable()))
|
||||
}
|
||||
userNavigated.Store(false)
|
||||
continue
|
||||
}
|
||||
if isEmptyNow {
|
||||
if wasEmpty || isEmptyNow {
|
||||
writeStdout([]byte(overlay.ClearAndDisable()))
|
||||
userNavigated.Store(false)
|
||||
continue
|
||||
@@ -867,14 +861,7 @@ func runWrapper() {
|
||||
isEmptyNow := len(naiveBuffer) == 0
|
||||
bufferMu.Unlock()
|
||||
|
||||
if wasEmpty {
|
||||
if overlay.Visible {
|
||||
writeStdout([]byte(overlay.ClearAndDisable()))
|
||||
}
|
||||
userNavigated.Store(false)
|
||||
continue
|
||||
}
|
||||
if isEmptyNow {
|
||||
if wasEmpty || isEmptyNow {
|
||||
writeStdout([]byte(overlay.ClearAndDisable()))
|
||||
userNavigated.Store(false)
|
||||
continue
|
||||
@@ -936,7 +923,7 @@ func runWrapper() {
|
||||
bufferMu.Unlock()
|
||||
shouldOverlayDraw = true
|
||||
userNavigated.Store(false)
|
||||
overlay.UserNavigated = false
|
||||
overlay.SetUserNavigated(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,14 @@ if [[ -n "$IRIS_FD" ]]; then
|
||||
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-zsh-hook
|
||||
|
||||
add-zle-hook-widget line-pre-redraw _iris_send_lbuffer
|
||||
add-zsh-hook precmd _iris_precmd
|
||||
add-zsh-hook preexec _iris_preexec
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user