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 {
|
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 {
|
||||||
|
|||||||
+160
-20
@@ -195,22 +195,153 @@ 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 "…"
|
|
||||||
}
|
|
||||||
return string(runes[:width-1]) + "…"
|
|
||||||
}
|
|
||||||
if len(runes) < width {
|
|
||||||
return s + strings.Repeat(" ", width-len(runes))
|
|
||||||
}
|
|
||||||
return s
|
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 {
|
func (o *Overlay) RenderGhostText(buffer string, userNavigated bool) string {
|
||||||
o.mu.Lock()
|
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 {
|
if padLen < 0 {
|
||||||
padLen = 0
|
padLen = 0
|
||||||
}
|
}
|
||||||
padLen += 10
|
if o.LastGhostLen > 0 {
|
||||||
|
padLen += 4
|
||||||
|
}
|
||||||
|
|
||||||
if ghostText != "" || padLen > 0 {
|
|
||||||
s.WriteString("\0337")
|
s.WriteString("\0337")
|
||||||
if ghostText != "" {
|
if ghostText != "" {
|
||||||
s.WriteString("\033[90m")
|
s.WriteString("\033[90m")
|
||||||
@@ -246,8 +383,7 @@ func (o *Overlay) RenderGhostText(buffer string, userNavigated bool) string {
|
|||||||
s.WriteString(strings.Repeat(" ", padLen))
|
s.WriteString(strings.Repeat(" ", padLen))
|
||||||
}
|
}
|
||||||
s.WriteString("\0338")
|
s.WriteString("\0338")
|
||||||
o.LastGhostLen = len(ghostText)
|
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")
|
||||||
|
|||||||
@@ -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":
|
||||||
|
|||||||
+93
-106
@@ -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()
|
||||||
|
if !wasEmpty {
|
||||||
writeStdout([]byte(overlay.ClearAndDisable()))
|
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 {
|
moved, selected := overlay.MoveCursor(arrowDir)
|
||||||
overlay.Cursor++
|
if !moved {
|
||||||
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 {
|
|
||||||
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 != "" {
|
||||||
|
|
||||||
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
|
|
||||||
bufferMu.Lock()
|
bufferMu.Lock()
|
||||||
naiveBuffer = selected
|
naiveBuffer = selected
|
||||||
cursorOffset = 0
|
cursorOffset = 0
|
||||||
bufferMu.Unlock()
|
bufferMu.Unlock()
|
||||||
|
|
||||||
userNavigated.Store(true)
|
userNavigated.Store(true)
|
||||||
overlay.UserNavigated = true
|
|
||||||
|
|
||||||
writeStdout([]byte(overlay.Render()))
|
writeStdout([]byte(overlay.Render()))
|
||||||
_, _ = ptmx.Write(append([]byte{0x15}, selected...))
|
_, _ = 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,24 +698,24 @@ 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()
|
||||||
|
if selected != "" {
|
||||||
activeModeMu.RLock()
|
activeModeMu.RLock()
|
||||||
currentMode := activeMode
|
currentMode := activeMode
|
||||||
activeModeMu.RUnlock()
|
activeModeMu.RUnlock()
|
||||||
@@ -750,9 +724,36 @@ func runWrapper() {
|
|||||||
}
|
}
|
||||||
_, _ = 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user