feat: Go to shows a repo file's icon before its name
CI / Checks (pull_request) Successful in 39s
PR title / Conventional title (pull_request) Successful in 1s

Search, Favourites, Recent and Opened links show the file's icon in its
colour (lock, leaf, whale, plain file), as the files pane does:
saft / 󰈔 backend/.env / COMPOSE_PROJECT_NAME.
This commit is contained in:
keb committed 2026-10-15 14:30:23 +02:00
1 parent 0f5248d8f6
commit 759c7d6863
4 files changed
+58 -29

No files matched your search

+26 -15
View File
@@ -97,26 +97,15 @@ func (m *Model) refreshContext(key, name string, path []string, src provider.Sou
// containerRender draws a vault or scope, starred or not. In the // containerRender draws a vault or scope, starred or not. In the
// Opened links list it also names the source, since they come from many. // Opened links list it also names the source, since they come from many.
func (m Model) containerRender(withSource bool) func(styles.Styles, provider.Container, int) string { func (m Model) containerRender(withSource bool) func(styles.Styles, provider.Container, int) string {
st, iconSet := m.store, m.icons st := m.store
return func(sty styles.Styles, c provider.Container, width int) string { return func(sty styles.Styles, c provider.Container, width int) string {
n := st.Node(storeKey(c)) n := st.Node(storeKey(c))
line := c.Name line := c.Name
if c.Source.Kind == provider.KindRepo { if c.Source.Kind == provider.KindRepo {
// A secret file: a lock for an encrypted one, a leaf for // A secret file: its icon, and its type after it ("sops ·
// Spring config, a whale for compose, a file in the warning
// colour for plain text, and its type after it ("sops ·
// age", "env · plain text"). // age", "env · plain text").
icon, style := iconSet.Encrypted, sty.KindRepo if icon := m.fileIcon(sty, c); icon != "" {
switch { line = icon + " " + line
case strings.HasPrefix(c.Detail, "spring"):
icon, style = iconSet.Spring, sty.Spring
case strings.HasPrefix(c.Detail, "compose"):
icon, style = iconSet.Compose, sty.Compose
case !strings.HasPrefix(c.Detail, "sops"):
icon, style = iconSet.PlainFile, sty.Warning
}
if icon != "" {
line = style.Render(icon) + " " + line
} }
line += sty.Muted.Render(" " + c.Detail) line += sty.Muted.Render(" " + c.Detail)
} }
@@ -155,6 +144,28 @@ func (m Model) objectRender() func(styles.Styles, provider.Object, int) string {
} }
} }
// fileIcon is a repo file's icon, in its colour: a lock for an encrypted
// one, a leaf for Spring config, a whale for compose, a file in the
// warning colour for plain text; "" for other vaults, or without icons.
func (m Model) fileIcon(sty styles.Styles, c provider.Container) string {
if c.Source.Kind != provider.KindRepo {
return ""
}
icon, style := m.icons.Encrypted, sty.KindRepo
switch {
case strings.HasPrefix(c.Detail, "spring"):
icon, style = m.icons.Spring, sty.Spring
case strings.HasPrefix(c.Detail, "compose"):
icon, style = m.icons.Compose, sty.Compose
case !strings.HasPrefix(c.Detail, "sops"):
icon, style = m.icons.PlainFile, sty.Warning
}
if icon == "" {
return ""
}
return style.Render(icon)
}
// dimIf draws a hidden item's row (shown with H) in the muted colour, // dimIf draws a hidden item's row (shown with H) in the muted colour,
// whole, so it stands apart from what's listed by the provider's rules. // whole, so it stands apart from what's listed by the provider's rules.
func dimIf(sty styles.Styles, hidden bool, line string) string { func dimIf(sty styles.Styles, hidden bool, line string) string {
+26 -8
View File
@@ -47,7 +47,7 @@ func (m *Model) storeEntries() (favs, recent []option) {
account = strings.Trim(detail+" · "+account, " ·") account = strings.Trim(detail+" · "+account, " ·")
sort := strings.ToLower(strings.Join(path, "/")) + " " + account sort := strings.ToLower(strings.Join(path, "/")) + " " + account
out = append(out, entry{depth, sort, option{ out = append(out, entry{depth, sort, option{
label: m.pathLabel(r.Kind, path, account), note: n.Note, mark: " ", label: m.pathLabel(r.Kind, path, account, m.refFileIcon(r)), note: n.Note, mark: " ",
id: prefix + key + "|" + acct, star: key, starAccount: acct, starred: n.StarredFor(acct), id: prefix + key + "|" + acct, star: key, starAccount: acct, starred: n.StarredFor(acct),
plain: m.inPlainEnv(r), plain: m.inPlainEnv(r),
run: func(m *Model) tea.Cmd { run: func(m *Model) tea.Cmd {
@@ -128,12 +128,15 @@ const recentMax = 20
// pathLabel draws a jump list entry: the provider's icon and the path in // pathLabel draws a jump list entry: the provider's icon and the path in
// its colour, then the account. // its colour, then the account.
func (m Model) pathLabel(kind provider.Kind, path []string, account string) string { func (m Model) pathLabel(kind provider.Kind, path []string, account, fileIcon string) string {
style := m.st.Kind(kind) style := m.st.Kind(kind)
parts := make([]string, len(path)) parts := make([]string, len(path))
for i, p := range path { for i, p := range path {
parts[i] = style.Render(p) parts[i] = style.Render(p)
} }
if fileIcon != "" && len(parts) > 1 {
parts[1] = fileIcon + " " + parts[1]
}
label := style.Render(m.icons.Prefix(kind)) + strings.Join(parts, m.st.Muted.Render(" / ")) label := style.Render(m.icons.Prefix(kind)) + strings.Join(parts, m.st.Muted.Render(" / "))
if account != "" { if account != "" {
label += m.st.Muted.Render(" · " + account) label += m.st.Muted.Render(" · " + account)
@@ -144,19 +147,34 @@ func (m Model) pathLabel(kind provider.Kind, path []string, account string) stri
// inPlainEnv reports whether r is a key in a plain env file, as far as // inPlainEnv reports whether r is a key in a plain env file, as far as
// the files loaded tell. // the files loaded tell.
func (m Model) inPlainEnv(r ref.Ref) bool { func (m Model) inPlainEnv(r ref.Ref) bool {
if r.Kind != provider.KindRepo || r.Name == "" { c, ok := m.repoFile(r)
return false return ok && r.Name != "" && c.Detail == provider.DetailPlainEnv
}
// repoFile is the repo file r is or is in, as far as the files loaded
// tell.
func (m Model) repoFile(r ref.Ref) (provider.Container, bool) {
if r.Kind != provider.KindRepo || r.IsSource() {
return provider.Container{}, false
} }
file := r file := r
file.Name, file.Type = "", "" file.Name, file.Type = "", ""
for _, e := range m.cache.containers { for _, e := range m.cache.containers {
for _, c := range e.value { for _, c := range e.value {
if c.Detail == provider.DetailPlainEnv && storeKey(c) == file.String() { if storeKey(c) == file.String() {
return true return c, true
} }
} }
} }
return false return provider.Container{}, false
}
// refFileIcon is the icon of the repo file r is or is in, if it's loaded.
func (m Model) refFileIcon(r ref.Ref) string {
if c, ok := m.repoFile(r); ok {
return m.fileIcon(m.st, c)
}
return ""
} }
// objectDetail is the Detail of the secret with the store key key, as far // objectDetail is the Detail of the secret with the store key key, as far
@@ -175,7 +193,7 @@ func (m Model) objectDetail(key string) string {
// repoLabel draws a repo, or a file in it, as everywhere else: the repo // repoLabel draws a repo, or a file in it, as everywhere else: the repo
// icon and colour, "app / apps/api/.env". // icon and colour, "app / apps/api/.env".
func (m Model) repoLabel(repo string, file ...string) string { func (m Model) repoLabel(repo string, file ...string) string {
return m.pathLabel(provider.KindRepo, append([]string{repo}, file...), "") return m.pathLabel(provider.KindRepo, append([]string{repo}, file...), "", "")
} }
// sourceMatches reports whether src is the subscription or workspace r // sourceMatches reports whether src is the subscription or workspace r
+1 -1
View File
@@ -269,7 +269,7 @@ func (m Model) refLabel(r ref.Ref) string {
default: default:
path = append(path, r.ContainerName(), r.Name) path = append(path, r.ContainerName(), r.Name)
} }
label := m.pathLabel(r.Kind, path, "") label := m.pathLabel(r.Kind, path, "", m.refFileIcon(r))
var extra []string var extra []string
if t := strings.ToLower(pluralLabels[r.Type]); t != "" && r.Type != provider.TypeSecret { if t := strings.ToLower(pluralLabels[r.Type]); t != "" && r.Type != provider.TypeSecret {
if r.IsContainer() { if r.IsContainer() {
+5 -5
View File
@@ -58,11 +58,11 @@ func (m *Model) searchEntries() (sources, items []option) {
var es []entry var es []entry
// key is the store key, so ctrl+f stars the entry; mark shows how // key is the store key, so ctrl+f stars the entry; mark shows how
// much is loaded, reload (ctrl+r) loads it again. // much is loaded, reload (ctrl+r) loads it again.
add := func(depth int, row string, kind provider.Kind, path []string, account, key, mark string, run, reload func(*Model) tea.Cmd) *option { add := func(depth int, row string, kind provider.Kind, path []string, account, fileIcon, key, mark string, run, reload func(*Model) tea.Cmd) *option {
n := m.store.Node(key) n := m.store.Node(key)
sort := strings.ToLower(strings.Join(path, "/")) + " " + account sort := strings.ToLower(strings.Join(path, "/")) + " " + account
es = append(es, entry{depth, sort, es = append(es, entry{depth, sort,
option{label: m.pathLabel(kind, path, account), note: n.Note, star: key, option{label: m.pathLabel(kind, path, account, fileIcon), note: n.Note, star: key,
starAccount: cachefile.ProviderOf(row), starred: n.StarredFor(cachefile.ProviderOf(row)), starAccount: cachefile.ProviderOf(row), starred: n.StarredFor(cachefile.ProviderOf(row)),
mark: mark, run: run, reload: reload, id: sort, group: row, kind: kind}}) mark: mark, run: run, reload: reload, id: sort, group: row, kind: kind}})
return &es[len(es)-1].opt return &es[len(es)-1].opt
@@ -81,7 +81,7 @@ func (m *Model) searchEntries() (sources, items []option) {
} }
src.Provider, src.ProviderLabel = p.ID(), p.Label() src.Provider, src.ProviderLabel = p.ID(), p.Label()
row := sourceRowKey(p.ID(), src.ID) row := sourceRowKey(p.ID(), src.ID)
dim(src.Hidden, add(0, row, src.Kind, []string{src.Name}, accountOf(src), sourceKey(src), m.sourceMark(row), func(m *Model) tea.Cmd { dim(src.Hidden, add(0, row, src.Kind, []string{src.Name}, accountOf(src), "", sourceKey(src), m.sourceMark(row), func(m *Model) tea.Cmd {
m.nav = &nav{sourceRow: row, sourceOnly: true} m.nav = &nav{sourceRow: row, sourceOnly: true}
return nil return nil
}, func(m *Model) tea.Cmd { return m.loadSource(src) })) }, func(m *Model) tea.Cmd { return m.loadSource(src) }))
@@ -94,7 +94,7 @@ func (m *Model) searchEntries() (sources, items []option) {
continue continue
} }
reload := func(m *Model) tea.Cmd { return m.loadContainer(c) } reload := func(m *Model) tea.Cmd { return m.loadContainer(c) }
dim(c.Hidden, add(1, row, c.Source.Kind, []string{src.Name, c.Name}, accountOf(src), storeKey(c), m.vaultMark(c), func(m *Model) tea.Cmd { dim(c.Hidden, add(1, row, c.Source.Kind, []string{src.Name, c.Name}, accountOf(src), m.fileIcon(m.st, c), storeKey(c), m.vaultMark(c), func(m *Model) tea.Cmd {
m.startNav(r, actGo, row) m.startNav(r, actGo, row)
return nil return nil
}, reload)) }, reload))
@@ -109,7 +109,7 @@ func (m *Model) searchEntries() (sources, items []option) {
typ := strings.TrimSuffix(strings.ToLower(pluralLabels[t]), "s") // "key" typ := strings.TrimSuffix(strings.ToLower(pluralLabels[t]), "s") // "key"
suffix = strings.Trim(typ+" · "+suffix, " ·") suffix = strings.Trim(typ+" · "+suffix, " ·")
} }
e := add(2, row, c.Source.Kind, []string{src.Name, c.Name, o.Name}, suffix, objectKey(o), " ", func(m *Model) tea.Cmd { e := add(2, row, c.Source.Kind, []string{src.Name, c.Name, o.Name}, suffix, m.fileIcon(m.st, c), objectKey(o), " ", func(m *Model) tea.Cmd {
m.startNav(r, actGo, row) m.startNav(r, actGo, row)
return nil return nil
}, reload) }, reload)