v0.5.0 bfab294

tree.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package web
4
5import (
6	"context"
7	"net/http"
8
9	"connectrpc.com/connect"
10	"github.com/a-h/templ"
11
12	pb "bnema.dev/ermite-api/gen/ermite"
13)
14
15// maxFileTargets bounds the files of a directory offered in the ":" list.
16const maxFileTargets = 200
17
18// TreeRow is one entry of a directory.
19type TreeRow struct {
20	Glyph   string // ▾ directory, @ symbolic link, + submodule, · file
21	Name    string
22	Href    string // "" for a submodule
23	Size    string
24	Subject string
25	CHref   string
26	Author  string
27	When    string
28	WhenAt  string
29	Cursor  bool
30}
31
32// TreePage is a directory.
33type TreePage struct {
34	Rows   []TreeRow
35	Readme *Readme
36	// Notes say what the listing leaves out.
37	Notes []string
38}
39
40// tree lists a directory of a ref, with the last commit of each entry, and
41// renders the README of the directory below.
42func (s *Server) tree(ctx context.Context, r *http.Request) (templ.Component, int, error) {
43	d, err := s.loadRepo(ctx, r.PathValue("repo"))
44	if err != nil {
45		return nil, 0, err
46	}
47	t, ok := d.locate(r.PathValue("rest"))
48	if !ok {
49		return nil, 0, errNotFound
50	}
51	res, err := s.cfg.Git.GetTree(ctx, connect.NewRequest(&pb.GetTreeRequest{
52		Repo: d.name(), Revision: t.rev, Path: t.path, IncludeLastCommit: true,
53	}))
54	if err != nil {
55		return nil, 0, err
56	}
57	entries := res.Msg.GetEntries()
58	now := s.cfg.Now()
59
60	p := TreePage{}
61	l := s.repoLayout(ctx, d, "tree")
62	for i, e := range entries {
63		row := TreeRow{Glyph: "·", Name: visibleText(e.GetName()), Size: humanSize(e.GetSize()), Cursor: i == 0}
64		switch e.GetKind() {
65		case pb.EntryKind_ENTRY_KIND_DIRECTORY:
66			row.Glyph, row.Name, row.Size = "▾", row.Name+"/", "-"
67			row.Href = repoHref(d.name(), "tree", t.name, e.GetPath())
68		case pb.EntryKind_ENTRY_KIND_SYMLINK:
69			row.Glyph = "@"
70			row.Href = repoHref(d.name(), "blob", t.name, e.GetPath())
71		case pb.EntryKind_ENTRY_KIND_SUBMODULE:
72			row.Glyph, row.Size = "+", "-"
73		default:
74			row.Href = repoHref(d.name(), "blob", t.name, e.GetPath())
75			if len(l.Cmd) < maxFileTargets {
76				l.Cmd = append(l.Cmd, CmdItem{Kind: "file", Label: visibleText(e.GetPath()), Href: row.Href})
77			}
78		}
79		if c := e.GetLastCommit(); c != nil {
80			last := commitRow(d.name(), c, now)
81			row.Subject, row.Author, row.CHref, row.When, row.WhenAt = last.Subject, last.Author, last.CHref, last.When, last.WhenAt
82		}
83		p.Rows = append(p.Rows, row)
84	}
85	if res.Msg.GetTruncated() {
86		p.Notes = append(p.Notes, "this directory has more entries than are listed here: the first "+itoa(len(entries))+" are shown.")
87	}
88	if res.Msg.GetLastCommitsTruncated() {
89		p.Notes = append(p.Notes, "the last commit is shown for the first entries only.")
90	}
91	rd, err := s.readmeIn(ctx, d.name(), res.Msg.GetCommitId(), t.name, t.path, entries)
92	if err != nil {
93		return nil, 0, err
94	}
95	if rd != nil {
96		p.Readme = rd.readme
97	}
98
99	pinRef(&l, t)
100	l.Crumbs = pathCrumbs(d.name(), "tree", t)
101	if t.path == "" {
102		l.Title = "tree · " + d.name() + " · " + s.instance
103	} else {
104		l.Title = visibleText(t.path) + " · " + d.name() + " · " + s.instance
105	}
106	return Tree(l, p), http.StatusOK, nil
107}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 45

↑↓ moveenter openesc close