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:
@@ -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",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user