feat: cat middleware: list directory entries

Ayman Bagabas created

Change summary

internal/git/git.go  | 13 ++---
server/middleware.go | 98 +++++++++++++++++++++++++++++----------------
2 files changed, 69 insertions(+), 42 deletions(-)

Detailed changes

internal/git/git.go 🔗

@@ -306,13 +306,7 @@ func (rs *RepoSource) loadRepo(name string, rg *git.Repository) (*Repo, error) {
 
 // LatestFile returns the latest file at the specified path in the repository.
 func (r *Repo) LatestFile(path string) (string, error) {
-	lg, err := r.repository.Log(&git.LogOptions{
-		From: r.GetHEAD().Hash(),
-	})
-	if err != nil {
-		return "", err
-	}
-	c, err := lg.Next()
+	c, err := r.commitForHash(r.head.Hash())
 	if err != nil {
 		return "", err
 	}
@@ -326,3 +320,8 @@ func (r *Repo) LatestFile(path string) (string, error) {
 	}
 	return content, nil
 }
+
+// LatestTree returns the latest tree at the specified path in the repository.
+func (r *Repo) LatestTree(path string) (*object.Tree, error) {
+	return r.Tree(r.head, path)
+}

server/middleware.go 🔗

@@ -3,6 +3,7 @@ package server
 import (
 	"fmt"
 	"path/filepath"
+	"sort"
 	"strings"
 
 	"github.com/alecthomas/chroma/lexers"
@@ -11,16 +12,36 @@ import (
 	appCfg "github.com/charmbracelet/soft-serve/internal/config"
 	"github.com/charmbracelet/soft-serve/internal/tui/bubbles/git/types"
 	"github.com/charmbracelet/wish"
-	"github.com/charmbracelet/wish/git"
+	gitwish "github.com/charmbracelet/wish/git"
 	"github.com/gliderlabs/ssh"
-	gg "github.com/go-git/go-git/v5"
+	"github.com/go-git/go-git/v5/plumbing/filemode"
+	"github.com/go-git/go-git/v5/plumbing/object"
 	"github.com/muesli/termenv"
 )
 
 var (
-	linenoStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("8"))
+	linenoStyle   = lipgloss.NewStyle().Foreground(lipgloss.Color("8"))
+	dirnameStyle  = lipgloss.NewStyle().Foreground(lipgloss.Color("#00AAFF"))
+	filenameStyle = lipgloss.NewStyle()
+	filemodeStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#777777"))
 )
 
+type entries []object.TreeEntry
+
+func (cl entries) Len() int      { return len(cl) }
+func (cl entries) Swap(i, j int) { cl[i], cl[j] = cl[j], cl[i] }
+func (cl entries) Less(i, j int) bool {
+	if cl[i].Mode == filemode.Dir && cl[j].Mode == filemode.Dir {
+		return cl[i].Name < cl[j].Name
+	} else if cl[i].Mode == filemode.Dir {
+		return true
+	} else if cl[j].Mode == filemode.Dir {
+		return false
+	} else {
+		return cl[i].Name < cl[j].Name
+	}
+}
+
 // softServeMiddleware is a middleware that handles displaying files with the
 // option of syntax highlighting and line numbers.
 func softServeMiddleware(ac *appCfg.Config) wish.Middleware {
@@ -48,7 +69,7 @@ func softServeMiddleware(ac *appCfg.Config) wish.Middleware {
 						return
 					}
 					auth := ac.AuthRepo(repo, s.PublicKey())
-					if auth < git.ReadOnlyAccess {
+					if auth < gitwish.ReadOnlyAccess {
 						s.Write([]byte("unauthorized"))
 						s.Exit(1)
 						return
@@ -66,25 +87,52 @@ func softServeMiddleware(ac *appCfg.Config) wish.Middleware {
 						_ = s.Exit(1)
 						return
 					}
-					fc, err := readFile(rs.Repository(), strings.Join(ps[1:], "/"))
-					if err != nil {
+					p := strings.Join(ps[1:], "/")
+					t, err := rs.LatestTree(p)
+					if err != nil && err != object.ErrDirectoryNotFound {
 						_, _ = s.Write([]byte(err.Error()))
 						_ = s.Exit(1)
 						return
 					}
-					if color {
-						ffc, err := withFormatting(fp, fc)
+					if err == object.ErrDirectoryNotFound {
+						fc, err := rs.LatestFile(p)
 						if err != nil {
-							s.Write([]byte(err.Error()))
-							s.Exit(1)
+							_, _ = s.Write([]byte(err.Error()))
+							_ = s.Exit(1)
 							return
 						}
-						fc = ffc
-					}
-					if lineno {
-						fc = withLineNumber(fc, color)
+						if color {
+							ffc, err := withFormatting(fp, fc)
+							if err != nil {
+								s.Write([]byte(err.Error()))
+								s.Exit(1)
+								return
+							}
+							fc = ffc
+						}
+						if lineno {
+							fc = withLineNumber(fc, color)
+						}
+						s.Write([]byte(fc))
+					} else {
+						ents := entries(t.Entries)
+						sort.Sort(ents)
+						for _, e := range ents {
+							m, _ := e.Mode.ToOSFileMode()
+							if m == 0 {
+								s.Write([]byte(strings.Repeat(" ", 10)))
+							} else {
+								s.Write([]byte(filemodeStyle.Render(m.String())))
+							}
+							s.Write([]byte(" "))
+							if e.Mode.IsFile() {
+								s.Write([]byte(filenameStyle.Render(e.Name)))
+							} else {
+								s.Write([]byte(dirnameStyle.Render(e.Name)))
+							}
+							s.Write([]byte("\n"))
+						}
 					}
-					s.Write([]byte(fc))
 				}()
 			}
 			sh(s)
@@ -92,26 +140,6 @@ func softServeMiddleware(ac *appCfg.Config) wish.Middleware {
 	}
 }
 
-func readFile(r *gg.Repository, fp string) (string, error) {
-	l, err := r.Log(&gg.LogOptions{})
-	if err != nil {
-		return "", err
-	}
-	c, err := l.Next()
-	if err != nil {
-		return "", err
-	}
-	f, err := c.File(fp)
-	if err != nil {
-		return "", err
-	}
-	fc, err := f.Contents()
-	if err != nil {
-		return "", err
-	}
-	return fc, nil
-}
-
 func withLineNumber(s string, color bool) string {
 	lines := strings.Split(s, "\n")
 	mll := fmt.Sprintf("%d", len(fmt.Sprintf("%d", len(lines))))