style(ui): redesign new menu (#18)

- 2 mode classic (the old one) and modern
a2a17cab3a4bdf27abd2a956f9d53a0c4a685909
- add icon 3a11c7a6beb1dcf11e89977f380376fdea9fba89
- fix some bugs
This commit is contained in:
VERSE
2026-07-03 11:50:35 +07:00
committed by GitHub
parent 24fddaccfe
commit ea82216dc9
11 changed files with 365 additions and 76 deletions
+9 -5
View File
@@ -47,13 +47,13 @@ func Lookup(input string) []Suggestion {
shellAliasesMu.RUnlock() shellAliasesMu.RUnlock()
if input == "" { if input == "" {
return nil return topLevelSuggestions("", aliases)
} }
tokens := Tokenize(input) tokens := Tokenize(input)
if len(tokens) == 1 && tokens[0] == "" { if len(tokens) == 1 && tokens[0] == "" {
return nil return topLevelSuggestions("", aliases)
} }
// NOTE: remember that wrapper already check if we type nothing, but I just want to make sure // NOTE: remember that wrapper already check if we type nothing, but I just want to make sure
// in the future, maybe we will write unit test or using Lookup in another module // in the future, maybe we will write unit test or using Lookup in another module
@@ -277,7 +277,7 @@ func topLevelSuggestions(query string, aliases map[string]string) []Suggestion {
for name, target := range aliases { for name, target := range aliases {
if !seen[name] && (query == "" || HasPrefix(name, query)) { if !seen[name] && (query == "" || HasPrefix(name, query)) {
results = append(results, Suggestion{ results = append(results, Suggestion{
Cmd: target, Desc: "alias: " + name, Icon: "root", Cmd: name, Desc: target, Icon: "alias",
}) })
seen[name] = true seen[name] = true
} }
@@ -299,7 +299,11 @@ func topLevelSuggestions(query string, aliases map[string]string) []Suggestion {
} }
} }
if match { if match {
results = append(results, Suggestion{Cmd: name, Desc: spec.Description, Icon: name}) icon := spec.Icon
if icon == "" {
icon = name
}
results = append(results, Suggestion{Cmd: name, Desc: spec.Description, Icon: icon})
seen[name] = true seen[name] = true
} }
} }
@@ -307,7 +311,7 @@ func topLevelSuggestions(query string, aliases map[string]string) []Suggestion {
for name := range pathCmds { for name := range pathCmds {
if !seen[name] && (query == "" || HasPrefix(name, query)) { if !seen[name] && (query == "" || HasPrefix(name, query)) {
results = append(results, Suggestion{ results = append(results, Suggestion{
Cmd: name, Desc: "system command", Icon: "root", Cmd: name, Desc: "system command", Icon: "system",
}) })
} }
} }
+2
View File
@@ -7,6 +7,7 @@ type Spec struct {
Name string Name string
Aliases []string Aliases []string
Description string Description string
Icon string
Subcommands []Subcommand Subcommands []Subcommand
Options []Option Options []Option
Generator GeneratorFunc Generator GeneratorFunc
@@ -18,6 +19,7 @@ type Subcommand struct {
Name string Name string
Aliases []string Aliases []string
Description string Description string
Icon string
Subcommands []Subcommand Subcommands []Subcommand
Options []Option Options []Option
Generator GeneratorFunc Generator GeneratorFunc
+5 -3
View File
@@ -39,9 +39,11 @@ type CoreConfig struct {
} }
type UIConfig struct { type UIConfig struct {
GhostText bool `toml:"ghost-text"` Style string `toml:"style"`
MaxSuggestions int `toml:"max-suggestions"` GhostText bool `toml:"ghost-text"`
MaxHeight int `toml:"max-height"` MaxSuggestions int `toml:"max-suggestions"`
MaxHeight int `toml:"max-height"`
NerdFonts bool `toml:"nerd-fonts"`
} }
type GitConfig struct { type GitConfig struct {
+2
View File
@@ -11,9 +11,11 @@ func DefaultConfig() *Config {
Debug: false, Debug: false,
}, },
UI: UIConfig{ UI: UIConfig{
Style: "modern",
GhostText: true, GhostText: true,
MaxSuggestions: 100, MaxSuggestions: 100,
MaxHeight: 15, MaxHeight: 15,
NerdFonts: true,
}, },
Git: GitConfig{ Git: GitConfig{
FilterActiveBranch: true, FilterActiveBranch: true,
+54
View File
@@ -0,0 +1,54 @@
package integration
import "strings"
var iconMap = map[string]string{
"git": "󰊢",
"docker": "",
"docker-compose": "",
"go": "",
"golang": "",
"python": "",
"python3": "",
"pip": "",
"node": "",
"npm": "",
"npx": "",
"bun": "",
"yarn": "",
"rust": "",
"cargo": "",
"java": "",
"mvn": "",
"gradle": "",
"nvim": "",
"vim": "",
"vi": "",
"cd": "",
"ls": "",
"eza": "",
"tree": "",
"pwd": "",
"cat": "",
"less": "",
"more": "",
"bat": "",
"grep": "",
"ripgrep": "",
"find": "",
"alias": "",
"history": "",
"system": "",
"root": "",
}
func lookupIcon(key string) string {
key = strings.ToLower(strings.TrimSpace(key))
if icon, ok := iconMap[key]; ok {
return icon
}
if len(key) > 0 && key[0] >= '0' && key[0] <= '9' {
return ""
}
return "❯"
}
+234 -57
View File
@@ -10,12 +10,13 @@ import (
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/versenilvis/iris/commands/core" "github.com/versenilvis/iris/commands/core"
"github.com/versenilvis/iris/config"
"github.com/versenilvis/iris/logger" "github.com/versenilvis/iris/logger"
"golang.org/x/term" "golang.org/x/term"
) )
const ( const (
boxWidth = 72 boxWidth = 76 // total visual width, corners included
maxItems = 6 maxItems = 6
) )
@@ -130,6 +131,36 @@ func ComputeCursorCol(data []byte) int {
return col return col
} }
type Theme struct {
Border lipgloss.Color
Accent lipgloss.Color
Muted lipgloss.Color
Text lipgloss.Color
TextSel lipgloss.Color
Match lipgloss.Color
Desc lipgloss.Color
DescSel lipgloss.Color
SelBg lipgloss.Color
ScrollInfo lipgloss.Color
}
var currentTheme = Theme{
Border: lipgloss.Color("#a277ff"),
Accent: lipgloss.Color("#61ffca"),
Muted: lipgloss.Color("#6d6a7f"),
Text: lipgloss.Color("#edecee"),
TextSel: lipgloss.Color("#ffffff"),
Match: lipgloss.Color("#61ffca"),
Desc: lipgloss.Color("#9692a8"),
DescSel: lipgloss.Color("#edecee"),
SelBg: lipgloss.Color("#3d375e"),
ScrollInfo: lipgloss.Color("#a277ff"),
}
func SetTheme(t Theme) {
currentTheme = t
}
type Overlay struct { type Overlay struct {
mu sync.Mutex mu sync.Mutex
Visible bool Visible bool
@@ -151,34 +182,13 @@ func (o *Overlay) SetPromptLen(l int) {
} }
} }
var (
selBgColor = lipgloss.Color("#44475A")
iconStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#BD93F9"))
titleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#F8F8F2"))
descStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#6272A4"))
iconSel = iconStyle.Background(selBgColor)
titleSel = titleStyle.Background(selBgColor)
descSel = descStyle.Background(selBgColor)
padSel = lipgloss.NewStyle().Background(selBgColor)
borderColor = lipgloss.Color("#6272A4")
borderStyle = lipgloss.NewStyle().Foreground(borderColor)
)
func NewOverlay() *Overlay { func NewOverlay() *Overlay {
return &Overlay{ return &Overlay{Visible: false, Cursor: 0, StartIdx: 0}
Visible: false,
Cursor: 0,
StartIdx: 0,
}
} }
func (o *Overlay) UpdateItems(items []core.Suggestion) { func (o *Overlay) UpdateItems(items []core.Suggestion) {
o.mu.Lock() o.mu.Lock()
defer o.mu.Unlock() defer o.mu.Unlock()
o.Items = items o.Items = items
o.Visible = len(o.Items) > 0 o.Visible = len(o.Items) > 0
o.Cursor = 0 o.Cursor = 0
@@ -186,8 +196,14 @@ func (o *Overlay) UpdateItems(items []core.Suggestion) {
} }
func fixedWidth(s string, width int) string { func fixedWidth(s string, width int) string {
if width <= 0 {
return ""
}
runes := []rune(s) runes := []rune(s)
if len(runes) > width { if len(runes) > width {
if width == 1 {
return "…"
}
return string(runes[:width-1]) + "…" return string(runes[:width-1]) + "…"
} }
if len(runes) < width { if len(runes) < width {
@@ -205,7 +221,6 @@ func (o *Overlay) RenderGhostText(buffer string, userNavigated bool) string {
} }
var s strings.Builder var s strings.Builder
ghostText := "" ghostText := ""
if !userNavigated && buffer != "" { if !userNavigated && buffer != "" {
topCmd := o.Items[0].Cmd topCmd := o.Items[0].Cmd
@@ -218,13 +233,10 @@ func (o *Overlay) RenderGhostText(buffer string, userNavigated bool) string {
if padLen < 0 { if padLen < 0 {
padLen = 0 padLen = 0
} }
// add extra padding to erase any stray characters left by fast backspaces
// before the debounce timer fired, 10 spaces is safe and won't hit right prompts
padLen += 10 padLen += 10
if ghostText != "" || padLen > 0 { if ghostText != "" || padLen > 0 {
s.WriteString("\0337") // save cursor at prompt s.WriteString("\0337")
if ghostText != "" { if ghostText != "" {
s.WriteString("\033[90m") s.WriteString("\033[90m")
s.WriteString(ghostText) s.WriteString(ghostText)
@@ -233,13 +245,40 @@ func (o *Overlay) RenderGhostText(buffer string, userNavigated bool) string {
if padLen > 0 { if padLen > 0 {
s.WriteString(strings.Repeat(" ", padLen)) s.WriteString(strings.Repeat(" ", padLen))
} }
s.WriteString("\0338") // restore cursor back to prompt s.WriteString("\0338")
o.LastGhostLen = len(ghostText) o.LastGhostLen = len(ghostText)
} }
return s.String() return s.String()
} }
func renderMatchedTitle(title, typed string, selected bool, w int) string {
t := currentTheme
textColor := t.Text
if selected {
textColor = t.TextSel
}
base := lipgloss.NewStyle().Foreground(textColor)
match := lipgloss.NewStyle().Foreground(t.Match).Bold(true)
if selected {
base = base.Background(t.SelBg)
match = match.Background(t.SelBg)
}
display := fixedWidth(title, w)
if typed == "" || !strings.HasPrefix(strings.ToLower(display), strings.ToLower(typed)) {
return base.Render(display)
}
matchLen := len(typed)
if matchLen > len(display) {
matchLen = len(display)
}
return match.Render(display[:matchLen]) + base.Render(display[matchLen:])
}
func (o *Overlay) Render() string { func (o *Overlay) Render() string {
return o.draw() return o.draw()
} }
@@ -252,6 +291,10 @@ func (o *Overlay) draw() string {
return "" return ""
} }
t := currentTheme
border := lipgloss.NewStyle().Foreground(t.Border)
scrollStyle := lipgloss.NewStyle().Foreground(t.ScrollInfo)
var s strings.Builder var s strings.Builder
s.WriteString("\033[?7l") s.WriteString("\033[?7l")
@@ -278,7 +321,6 @@ func (o *Overlay) draw() string {
} }
scrolloffUp := 1 scrolloffUp := 1
scrolloffDown := 0
if windowSize <= 3 { if windowSize <= 3 {
scrolloffUp = 0 scrolloffUp = 0
} }
@@ -286,8 +328,8 @@ func (o *Overlay) draw() string {
if o.Cursor < o.StartIdx+scrolloffUp { if o.Cursor < o.StartIdx+scrolloffUp {
o.StartIdx = o.Cursor - scrolloffUp o.StartIdx = o.Cursor - scrolloffUp
} }
if o.Cursor >= o.StartIdx+windowSize-scrolloffDown { if o.Cursor >= o.StartIdx+windowSize {
o.StartIdx = o.Cursor - windowSize + scrolloffDown + 1 o.StartIdx = o.Cursor - windowSize + 1
} }
if o.StartIdx < 0 { if o.StartIdx < 0 {
o.StartIdx = 0 o.StartIdx = 0
@@ -317,6 +359,12 @@ func (o *Overlay) draw() string {
} }
} }
inner := boxWidth - 2 // width between the two border pipes/corners
style := strings.ToLower(config.Get().UI.Style)
isClassic := style == "classic" || style == "minimal" || style == "minimalist"
// top side border with scroll counter
s.WriteString("\0338") s.WriteString("\0338")
fmt.Fprintf(&s, "\033[%dB", 1) fmt.Fprintf(&s, "\033[%dB", 1)
s.WriteString("\033[2K") s.WriteString("\033[2K")
@@ -326,14 +374,37 @@ func (o *Overlay) draw() string {
if len(o.Items) > windowSize { if len(o.Items) > windowSize {
scrollInfo = fmt.Sprintf(" %d/%d ", o.Cursor+1, len(o.Items)) scrollInfo = fmt.Sprintf(" %d/%d ", o.Cursor+1, len(o.Items))
} }
leftDash := 3
if isClassic && scrollInfo != "" {
leftDash = (inner - len(scrollInfo)) / 2
}
rightDash := inner - leftDash - len(scrollInfo)
if scrollInfo == "" {
leftDash = 0
rightDash = inner
}
fmt.Fprintf(&s, "%s%s%s%s%s",
border.Render("╭"),
border.Render(strings.Repeat("─", leftDash)),
scrollStyle.Render(scrollInfo),
border.Render(strings.Repeat("─", rightDash)),
border.Render("╮"),
)
borderWidth := boxWidth - len(scrollInfo) // left and right side border with item rows
topBorder := "╭" + strings.Repeat("─", borderWidth/2) + scrollInfo + strings.Repeat("─", boxWidth-borderWidth/2-len(scrollInfo)) + "╮" descW := 24
s.WriteString(borderStyle.Render(topBorder)) padGap := 2
markerW := 1
iconW := 6 iconW := 2
descW := 22 if isClassic {
titleW := boxWidth - iconW - descW - 3 iconW = 0
}
sidePad := 1
titleW := inner - sidePad*2 - markerW - 1 - iconW
if iconW > 0 {
titleW--
}
titleW = titleW - padGap - descW
for i := start; i < end; i++ { for i := start; i < end; i++ {
s.WriteString("\0338") s.WriteString("\0338")
@@ -342,33 +413,139 @@ func (o *Overlay) draw() string {
moveToTarget() moveToTarget()
it := o.Items[i] it := o.Items[i]
rawIcon := fixedWidth(it.Icon, iconW) selected := i == o.Cursor
rawTitle := fixedWidth(it.Cmd, titleW)
rawDesc := fixedWidth(it.Desc, descW)
left := borderStyle.Render("│") left := border.Render("│")
right := borderStyle.Render("│") right := border.Render("│")
if i == o.Cursor { bg := lipgloss.NewStyle()
icon := iconSel.Render(" " + rawIcon + " ") if selected {
title := titleSel.Render(rawTitle) bg = bg.Background(t.SelBg)
pad := padSel.Render(" ")
desc := descSel.Render(rawDesc)
fmt.Fprintf(&s, "%s%s%s%s%s%s", left, icon, pad, title, desc, right)
} else {
icon := iconStyle.Render(" " + rawIcon + " ")
title := titleStyle.Render(rawTitle)
desc := descStyle.Render(rawDesc)
fmt.Fprintf(&s, "%s%s %s%s%s", left, icon, title, desc, right)
} }
marker := " "
markerStyle := bg.Foreground(t.Muted)
if selected {
marker = "▶"
markerStyle = bg.Foreground(t.Accent).Bold(true)
}
iconGlyph := lookupIcon(it.Icon)
iconColor := t.Muted
if selected {
iconColor = t.Accent
}
iconStr := bg.Foreground(iconColor).Render(fixedWidth(iconGlyph, iconW))
title := renderMatchedTitle(it.Cmd, o.TypedQuery, selected, titleW)
descColor := t.Desc
if selected {
descColor = t.DescSel
}
var desc string
if isClassic {
if it.Icon == "alias" {
desc = bg.Foreground(descColor).Render(fixedWidth("alias: "+it.Desc, descW))
} else {
desc = bg.Foreground(descColor).Render(fixedWidth(it.Desc, descW))
}
} else {
switch it.Icon {
case "alias":
boxStyle := lipgloss.NewStyle().Background(lipgloss.Color("#2a2342")).Foreground(lipgloss.Color("#a277ff"))
if selected {
boxStyle = lipgloss.NewStyle().Background(lipgloss.Color("#a277ff")).Foreground(lipgloss.Color("#110f18")).Bold(true)
}
tag := boxStyle.Render(" alias ")
tw := lipgloss.Width(tag)
rem := descW - tw - 1
if rem < 0 {
rem = 0
}
desc = tag + bg.Render(" ") + bg.Foreground(descColor).Render(fixedWidth(it.Desc, rem))
case "history":
boxStyle := lipgloss.NewStyle().Background(lipgloss.Color("#1a2d36")).Foreground(lipgloss.Color("#61ffca"))
if selected {
boxStyle = lipgloss.NewStyle().Background(lipgloss.Color("#61ffca")).Foreground(lipgloss.Color("#110f18")).Bold(true)
}
tag := boxStyle.Render(" history ")
tw := lipgloss.Width(tag)
rem := descW - tw
if rem < 0 {
rem = 0
}
desc = tag + bg.Render(strings.Repeat(" ", rem))
case "system":
boxStyle := lipgloss.NewStyle().Background(lipgloss.Color("#1e1d28")).Foreground(lipgloss.Color("#a277ff"))
if selected {
boxStyle = lipgloss.NewStyle().Background(lipgloss.Color("#a277ff")).Foreground(lipgloss.Color("#110f18")).Bold(true)
}
tag := boxStyle.Render(" system ")
tw := lipgloss.Width(tag)
rem := descW - tw
if rem < 0 {
rem = 0
}
desc = tag + bg.Render(strings.Repeat(" ", rem))
default:
desc = bg.Foreground(descColor).Render(fixedWidth(it.Desc, descW))
}
}
iconSection := ""
if iconW > 0 {
iconSection = iconStr + bg.Render(" ")
}
fmt.Fprintf(&s, "%s%s%s%s%s%s%s%s%s%s",
left,
bg.Render(" "),
markerStyle.Render(marker),
bg.Render(" "),
iconSection,
title,
bg.Render(strings.Repeat(" ", padGap)),
desc,
bg.Render(" "),
right,
)
} }
// bottom side border with footer shortcut hints
s.WriteString("\0338") s.WriteString("\0338")
fmt.Fprintf(&s, "\033[%dB", windowSize+2) fmt.Fprintf(&s, "\033[%dB", windowSize+2)
s.WriteString("\033[2K") s.WriteString("\033[2K")
moveToTarget() moveToTarget()
bottomBorder := "╰" + strings.Repeat("─", boxWidth) + "╯"
s.WriteString(borderStyle.Render(bottomBorder)) footerInfo := ""
if !isClassic {
keyStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#a277ff")).Bold(true)
tabKey := keyStyle.Render("<Tab>")
ctrlRKey := keyStyle.Render("<Ctrl+R>")
acceptText := lipgloss.NewStyle().Foreground(t.ScrollInfo).Render(" Accept")
modeText := lipgloss.NewStyle().Foreground(t.ScrollInfo).Render(" Mode")
footerInfo = fmt.Sprintf(" %s%s • %s%s ", tabKey, acceptText, ctrlRKey, modeText)
}
footerRunes := lipgloss.Width(footerInfo)
rightDash = 2
leftDash = inner - footerRunes - rightDash
if footerInfo == "" {
leftDash = 0
rightDash = inner
}
if leftDash < 0 {
leftDash = 0
}
fmt.Fprintf(&s, "%s%s%s%s%s",
border.Render("╰"),
border.Render(strings.Repeat("─", leftDash)),
footerInfo,
border.Render(strings.Repeat("─", rightDash)),
border.Render("╯"),
)
s.WriteString("\0338") s.WriteString("\0338")
s.WriteString("\033[?7h") s.WriteString("\033[?7h")
+6
View File
@@ -51,6 +51,12 @@ mode = "last"
debug = false debug = false
[ui] [ui]
# visual style: "modern" (icons, category pills, shortcut footer) or "classic" (minimalist, centered number, no icons)
style = "modern"
# enable Nerd Fonts icons in overlay menu
nerd-fonts = true
# enable inline ghost text # enable inline ghost text
ghost-text = true ghost-text = true
+6
View File
@@ -175,6 +175,12 @@ mode = "last"
debug = false debug = false
[ui] [ui]
# visual style: "modern" (icons, category pills, shortcut footer) or "classic" (minimalist, centered number, no icons)
style = "modern"
# enable Nerd Fonts icons in overlay menu
nerd-fonts = true
# enable inline ghost text # enable inline ghost text
ghost-text = true ghost-text = true
+4 -8
View File
@@ -1,7 +1,6 @@
package root package root
import ( import (
"fmt"
"strings" "strings"
"github.com/versenilvis/iris/commands/core" "github.com/versenilvis/iris/commands/core"
@@ -17,11 +16,8 @@ func MergeResults(query string, mode string) []core.Suggestion {
deduped := []core.Suggestion{} deduped := []core.Suggestion{}
// always call lookup to scan aliases and get spec suggestions // always call lookup to scan aliases and get spec suggestions
var cmdResults []core.Suggestion logger.Debugf("Merge Calling Lookup for '%s'", query)
if query != "" { cmdResults := core.Lookup(query)
logger.Debugf("Merge Calling Lookup for '%s'", query)
cmdResults = core.Lookup(query)
}
// search history if in history mode // search history if in history mode
var histResults []integration.HistResult var histResults []integration.HistResult
@@ -53,8 +49,8 @@ func MergeResults(query string, mode string) []core.Suggestion {
for _, h := range histResults { for _, h := range histResults {
addSuggestion(core.Suggestion{ addSuggestion(core.Suggestion{
Cmd: h.Cmd, Cmd: h.Cmd,
Desc: " history", Desc: "history",
Icon: fmt.Sprintf("%d", h.ID), Icon: "history",
}) })
} }
for _, s := range cmdResults { for _, s := range cmdResults {
+42 -2
View File
@@ -323,6 +323,15 @@ func runWrapper() {
continue continue
} }
if query == "" {
bufferMu.Lock()
naiveBuffer = ""
cursorOffset = 0
bufferMu.Unlock()
writeStdout([]byte(overlay.ClearAndDisable()))
continue
}
// sync local buffer with actual command line // sync local buffer with actual command line
bufferMu.Lock() bufferMu.Lock()
naiveBuffer = query naiveBuffer = query
@@ -388,7 +397,7 @@ func runWrapper() {
var b strings.Builder var b strings.Builder
if !navCopy { if !navCopy {
if bufCopy == "" { if bufCopy == "" && !overlay.Visible {
writeStdout([]byte(overlay.ClearAndDisable())) writeStdout([]byte(overlay.ClearAndDisable()))
return return
} }
@@ -722,6 +731,7 @@ func runWrapper() {
} }
userNavigated.Store(false) userNavigated.Store(false)
overlay.UserNavigated = false overlay.UserNavigated = false
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
@@ -808,7 +818,8 @@ func runWrapper() {
case 127, 0x08: // backspace: remove character case 127, 0x08: // backspace: remove character
bufferMu.Lock() bufferMu.Lock()
if len(naiveBuffer) > 0 { wasEmpty := len(naiveBuffer) == 0
if !wasEmpty {
runes := []rune(naiveBuffer) runes := []rune(naiveBuffer)
if cursorOffset <= 0 { if cursorOffset <= 0 {
if len(runes) > 0 { if len(runes) > 0 {
@@ -825,11 +836,26 @@ func runWrapper() {
} }
} }
} }
isEmptyNow := len(naiveBuffer) == 0
bufferMu.Unlock() bufferMu.Unlock()
if wasEmpty {
if overlay.Visible {
writeStdout([]byte(overlay.ClearAndDisable()))
}
userNavigated.Store(false)
continue
}
if isEmptyNow {
writeStdout([]byte(overlay.ClearAndDisable()))
userNavigated.Store(false)
continue
}
shouldOverlayDraw = true shouldOverlayDraw = true
userNavigated.Store(false) userNavigated.Store(false)
case 0x17: // ctrl+w: delete the last word in the buffer case 0x17: // ctrl+w: delete the last word in the buffer
bufferMu.Lock() bufferMu.Lock()
wasEmpty := len(naiveBuffer) == 0
trimBuf := strings.TrimRight(naiveBuffer, " ") trimBuf := strings.TrimRight(naiveBuffer, " ")
lastSpace := strings.LastIndex(trimBuf, " ") lastSpace := strings.LastIndex(trimBuf, " ")
if lastSpace >= 0 { if lastSpace >= 0 {
@@ -838,7 +864,21 @@ func runWrapper() {
naiveBuffer = "" naiveBuffer = ""
} }
cursorOffset = 0 cursorOffset = 0
isEmptyNow := len(naiveBuffer) == 0
bufferMu.Unlock() bufferMu.Unlock()
if wasEmpty {
if overlay.Visible {
writeStdout([]byte(overlay.ClearAndDisable()))
}
userNavigated.Store(false)
continue
}
if isEmptyNow {
writeStdout([]byte(overlay.ClearAndDisable()))
userNavigated.Store(false)
continue
}
shouldOverlayDraw = true shouldOverlayDraw = true
userNavigated.Store(false) userNavigated.Store(false)
case 0x0c: // ctrl+l: clear screen but keep buffer and redraw menu case 0x0c: // ctrl+l: clear screen but keep buffer and redraw menu
+1 -1
View File
@@ -51,7 +51,7 @@ func TestLookup(t *testing.T) {
if tt.mustContain != "" { if tt.mustContain != "" {
found := false found := false
for _, r := range results { for _, r := range results {
if strings.Contains(r.Cmd, tt.mustContain) { if strings.Contains(r.Cmd, tt.mustContain) || strings.Contains(r.Desc, tt.mustContain) {
found = true found = true
break break
} }