v0.6.0 d5cc8fe

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// TreeRow is one entry of a directory.
16type TreeRow struct {
17	Glyph string // ▸ directory, @ symbolic link, + submodule, · file
18	Name  string
19	Href  string // "" for a submodule
20	// Expand is the URL of the rows of a directory (Href?rows); "" for any
21	// other entry.
22	Expand  string
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	// More, for the rows of a directory expanded in place, is the page of the
39	// directory when it has more entries than are listed; "" otherwise.
40	More string
41}
42
43// tree lists a directory of a ref, with the last commit of each entry, and
44// renders the README of the directory below.
45func (s *Server) tree(ctx context.Context, r *http.Request) (templ.Component, int, error) {
46	d, err := s.loadRepo(ctx, r.PathValue("repo"))
47	if err != nil {
48		return nil, 0, err
49	}
50	t, ok := d.locate(r.PathValue("rest"))
51	if !ok {
52		return nil, 0, errNotFound
53	}
54	res, err := s.cfg.Git.GetTree(ctx, connect.NewRequest(&pb.GetTreeRequest{
55		Repo: d.name(), Revision: t.rev, Path: t.path, IncludeLastCommit: true,
56	}))
57	if err != nil {
58		return nil, 0, err
59	}
60	entries := res.Msg.GetEntries()
61	now := s.cfg.Now()
62
63	rowsOnly := r.URL.Query().Has("rows")
64	p := TreePage{}
65	for i, e := range entries {
66		row := TreeRow{Glyph: "·", Name: visibleText(e.GetName()), Size: humanSize(e.GetSize()), Cursor: i == 0 && !rowsOnly}
67		switch e.GetKind() {
68		case pb.EntryKind_ENTRY_KIND_DIRECTORY:
69			row.Glyph, row.Name, row.Size = "▸", row.Name+"/", "-"
70			row.Href = repoHref(d.name(), "tree", t.name, e.GetPath())
71			row.Expand = row.Href + "?rows"
72		case pb.EntryKind_ENTRY_KIND_SYMLINK:
73			row.Glyph = "@"
74			row.Href = repoHref(d.name(), "blob", t.name, e.GetPath())
75		case pb.EntryKind_ENTRY_KIND_SUBMODULE:
76			row.Glyph, row.Size = "+", "-"
77		default:
78			row.Href = repoHref(d.name(), "blob", t.name, e.GetPath())
79		}
80		if c := e.GetLastCommit(); c != nil {
81			last := commitRow(d.name(), c, now)
82			row.Subject, row.Author, row.CHref, row.When, row.WhenAt = last.Subject, last.Author, last.CHref, last.When, last.WhenAt
83		}
84		p.Rows = append(p.Rows, row)
85	}
86	if rowsOnly {
87		// The rows alone, for a directory opened in place (files.js).
88		if res.Msg.GetTruncated() {
89			p.More = repoHref(d.name(), "tree", t.name, t.path)
90		}
91		return TreeRows(p), http.StatusOK, nil
92	}
93	if res.Msg.GetTruncated() {
94		p.Notes = append(p.Notes, "this directory has more entries than are listed here: the first "+itoa(len(entries))+" are shown.")
95	}
96	if res.Msg.GetLastCommitsTruncated() {
97		p.Notes = append(p.Notes, "the last commit is shown for the first entries only.")
98	}
99	rd, err := s.readmeIn(ctx, d.name(), res.Msg.GetCommitId(), t.name, t.path, entries)
100	if err != nil {
101		return nil, 0, err
102	}
103	if rd != nil {
104		p.Readme = rd.readme
105	}
106
107	l := s.repoLayout(ctx, d, "tree")
108	// The finder searches the commit this page lists, whichever way the URL
109	// spelled it (an abbreviated id included): the listing knows it.
110	t.commit = res.Msg.GetCommitId()
111	pinRef(&l, t)
112	l.Crumbs = pathCrumbs(d.name(), "tree", t)
113	if t.path == "" {
114		l.Title = "tree · " + d.name() + " · " + s.instance
115	} else {
116		l.Title = visibleText(t.path) + " · " + d.name() + " · " + s.instance
117	}
118	return Tree(l, p), http.StatusOK, nil
119}

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 4

Tags 45

↑↓ moveenter openesc close