1package server
  2
  3import (
  4	"fmt"
  5	"path/filepath"
  6	"strings"
  7
  8	"github.com/alecthomas/chroma/lexers"
  9	gansi "github.com/charmbracelet/glamour/ansi"
 10	"github.com/charmbracelet/lipgloss"
 11	appCfg "github.com/charmbracelet/soft-serve/internal/config"
 12	"github.com/charmbracelet/soft-serve/internal/tui/bubbles/git/types"
 13	"github.com/charmbracelet/wish"
 14	"github.com/charmbracelet/wish/git"
 15	"github.com/gliderlabs/ssh"
 16	gg "github.com/go-git/go-git/v5"
 17	"github.com/muesli/termenv"
 18)
 19
 20// softServeMiddleware is a middleware that handles displaying files with the
 21// option of syntax highlighting and line numbers.
 22func softServeMiddleware(ac *appCfg.Config) wish.Middleware {
 23	return func(sh ssh.Handler) ssh.Handler {
 24		return func(s ssh.Session) {
 25			_, _, active := s.Pty()
 26			cmds := s.Command()
 27			if !active && len(cmds) > 0 {
 28				func() {
 29					color := false
 30					lineno := false
 31					fp := filepath.Clean(cmds[0])
 32					ps := strings.Split(fp, "/")
 33					repo := ps[0]
 34					repoExists := false
 35					for _, rp := range ac.Source.AllRepos() {
 36						if rp.Name == repo {
 37							repoExists = true
 38						}
 39					}
 40					if !repoExists {
 41						return
 42					}
 43					auth := ac.AuthRepo(repo, s.PublicKey())
 44					if auth < git.ReadOnlyAccess {
 45						s.Write([]byte("unauthorized"))
 46						s.Exit(1)
 47						return
 48					}
 49					for _, op := range cmds[1:] {
 50						if op == "-c" || op == "--color" {
 51							color = true
 52						} else if op == "-l" || op == "--lineno" || op == "--linenumber" {
 53							lineno = true
 54						}
 55					}
 56					rs, err := ac.Source.GetRepo(repo)
 57					if err != nil {
 58						_, _ = s.Write([]byte(err.Error()))
 59						_ = s.Exit(1)
 60						return
 61					}
 62					fc, err := readFile(rs.Repository, strings.Join(ps[1:], "/"))
 63					if err != nil {
 64						_, _ = s.Write([]byte(err.Error()))
 65						_ = s.Exit(1)
 66						return
 67					}
 68					if color {
 69						ffc, err := withFormatting(fp, fc)
 70						if err != nil {
 71							s.Write([]byte(err.Error()))
 72							s.Exit(1)
 73							return
 74						}
 75						fc = ffc
 76					}
 77					if lineno {
 78						fc = withLineNumber(fc, color)
 79					}
 80					s.Write([]byte(fc))
 81				}()
 82			}
 83			sh(s)
 84		}
 85	}
 86}
 87
 88func readFile(r *gg.Repository, fp string) (string, error) {
 89	l, err := r.Log(&gg.LogOptions{})
 90	if err != nil {
 91		return "", err
 92	}
 93	c, err := l.Next()
 94	if err != nil {
 95		return "", err
 96	}
 97	f, err := c.File(fp)
 98	if err != nil {
 99		return "", err
100	}
101	fc, err := f.Contents()
102	if err != nil {
103		return "", err
104	}
105	return fc, nil
106}
107
108func withLineNumber(s string, color bool) string {
109	st := lipgloss.NewStyle().Foreground(lipgloss.Color("15"))
110	lines := strings.Split(s, "\n")
111	mll := fmt.Sprintf("%d", len(fmt.Sprintf("%d", len(lines))))
112	for i, l := range lines {
113		lines[i] = fmt.Sprintf("%-"+mll+"d │ %s", i+1, l)
114		if color {
115			lines[i] = st.Render(lines[i])
116		}
117	}
118	return strings.Join(lines, "\n")
119}
120
121func withFormatting(p, c string) (string, error) {
122	zero := uint(0)
123	lang := ""
124	lexer := lexers.Match(p)
125	if lexer != nil && lexer.Config() != nil {
126		lang = lexer.Config().Name
127	}
128	formatter := &gansi.CodeBlockElement{
129		Code:     c,
130		Language: lang,
131	}
132	r := strings.Builder{}
133	styles := types.DefaultStyles()
134	styles.CodeBlock.Margin = &zero
135	rctx := gansi.NewRenderContext(gansi.Options{
136		Styles:       styles,
137		ColorProfile: termenv.TrueColor,
138	})
139	err := formatter.Render(&r, rctx)
140	if err != nil {
141		return "", err
142	}
143	return r.String(), nil
144}