snapshot of eee7b5cfc0776c68816d7fdaa8af9d7a0e0da2e6 Annotations about the code that implements koment.

internal/ui/view.go

1 package ui
2
3 import (
4 "net/url"
5 "strings"
6
7 "github.com/janpuc/koment/internal/anchor"
8 "github.com/janpuc/koment/internal/application"
9 "github.com/janpuc/koment/internal/store"
10 )
11
12 const sourceURL = "https://github.com/janpuc/koment"
13
14 type view struct {
15 Total int
16 Tally []tallyEntry
17 Tree []treeNode
18 Loose []entry
19 Repositories []repositoryLink
20 Repository string
21 Current string
22 File *fileView
23 Empty bool
24 NotFound bool
25 Home string
26 Stylesheet string
27 Script string
28 LogoSVG string
29 LogoPNG string
30 SourceURL string
31 Snapshot *snapshot
32 WriteToken string
33 CanWrite bool
34 CreatedID string
35 WriteWarning string
36 ReviewURL string
37 }
38
39 type snapshot struct {
40 Commit string
41 CommitURL string
42 Banner string
43 BannerHref string
44 }
45
46 type repositoryLink struct {
47 ID string
48 Name string
49 Href string
50 Current bool
51 }
52
53 type links struct {
54 file func(target string) string
55 home string
56 stylesheet string
57 script string
58 logoSVG string
59 logoPNG string
60 }
61
62 func servedLinks(repositoryID string) links {
63 base := repositoryPrefix + repositoryID + "/"
64 return links{
65 file: func(target string) string { return base + "f/" + escapedFilePath(target) },
66 home: base,
67 stylesheet: "/assets/style.css",
68 script: "/assets/koment.js",
69 logoSVG: "/assets/koment-logo.svg",
70 logoPNG: "/assets/koment-logo.png",
71 }
72 }
73
74 func escapedFilePath(file string) string {
75 parts := strings.Split(file, "/")
76 for index, part := range parts {
77 parts[index] = url.PathEscape(part)
78 }
79 return strings.Join(parts, "/")
80 }
81
82 type tallyEntry struct {
83 Status anchor.Status
84 Count int
85 }
86
87 type entry struct {
88 Path string
89 Name string
90 Href string
91 Count int
92 Worst anchor.Status
93 Current bool
94 Search string
95 }
96
97 type fileView struct {
98 Path string
99 Lines []line
100 Notes []note
101 Detached []note
102 Missing bool
103 }
104
105 type line struct {
106 Number int
107 Text string
108 Marker anchor.Status
109 }
110
111 type note struct {
112 ID string
113 Kind string
114 Status anchor.Status
115 Line int
116 Body []string
117 Created string
118 Excerpt string
119 Stale bool
120 Warning string
121 }
122
123 var statusOrder = []anchor.Status{
124 anchor.StatusOK, anchor.StatusMoved, anchor.StatusAmbiguous, anchor.StatusDrifted, anchor.StatusOrphaned,
125 }
126
127 func build(repositorySnapshot *application.RepositorySnapshot, requested string, how links) (*view, error) {
128 if len(repositorySnapshot.Files) == 0 {
129 return &view{
130 Empty: true, Stylesheet: how.stylesheet, Script: how.script,
131 Home: how.home, LogoSVG: how.logoSVG, LogoPNG: how.logoPNG, SourceURL: sourceURL,
132 }, nil
133 }
134
135 current := requested
136 if current == "" {
137 current = repositorySnapshot.Files[0].Path
138 }
139
140 built := &view{
141 Current: current,
142 Home: how.home,
143 Stylesheet: how.stylesheet,
144 Script: how.script,
145 LogoSVG: how.logoSVG,
146 LogoPNG: how.logoPNG,
147 SourceURL: sourceURL,
148 }
149 counts := map[anchor.Status]int{}
150 listed := make([]entry, 0, len(repositorySnapshot.Files))
151
152 for _, file := range repositorySnapshot.Files {
153 worst := anchor.StatusOK
154 var searchable strings.Builder
155 searchable.WriteString(file.Path)
156 for _, annotation := range file.Annotations {
157 counts[annotation.Status]++
158 built.Total++
159 if statusSeverity[annotation.Status] > statusSeverity[worst] {
160 worst = annotation.Status
161 }
162 searchable.WriteString("\n" + string(annotation.Record.Kind) + "\n" + annotation.Record.Body + "\n" + annotation.Record.Author.Name)
163 }
164
165 listed = append(listed, entry{
166 Path: file.Path,
167 Name: baseName(file.Path),
168 Href: how.file(file.Path),
169 Count: len(file.Annotations),
170 Worst: worst,
171 Current: file.Path == current,
172 Search: strings.ToLower(searchable.String()),
173 })
174
175 if file.Path == current {
176 built.File = buildFile(file)
177 }
178 }
179
180 if built.File == nil {
181 built.NotFound = true
182 }
183 built.Tally = tallyOf(counts)
184 built.Tree, built.Loose = buildTree(listed, current)
185 return built, nil
186 }
187
188 func baseName(file string) string {
189 if cut := strings.LastIndex(file, "/"); cut >= 0 {
190 return file[cut+1:]
191 }
192 return file
193 }
194
195 func tallyOf(counts map[anchor.Status]int) []tallyEntry {
196 var tally []tallyEntry
197 for _, status := range statusOrder {
198 if counts[status] > 0 {
199 tally = append(tally, tallyEntry{Status: status, Count: counts[status]})
200 }
201 }
202 return tally
203 }
204
205 func buildFile(file application.FileSnapshot) *fileView {
206 built := &fileView{Path: file.Path}
207 if !file.Exists {
208 built.Missing = true
209 for _, annotation := range file.Annotations {
210 built.Detached = append(built.Detached, describe(annotation))
211 }
212 return built
213 }
214
215 marked := map[int]anchor.Status{}
216 for _, annotation := range file.Annotations {
217 described := describe(annotation)
218 if annotation.Line == 0 {
219 built.Detached = append(built.Detached, described)
220 continue
221 }
222 built.Notes = append(built.Notes, described)
223 worst, seen := marked[annotation.Line]
224 if !seen || statusSeverity[annotation.Status] > statusSeverity[worst] {
225 marked[annotation.Line] = annotation.Status
226 }
227 }
228
229 for i, text := range strings.Split(strings.TrimSuffix(string(file.Content), "\n"), "\n") {
230 number := i + 1
231 built.Lines = append(built.Lines, line{Number: number, Text: text, Marker: marked[number]})
232 }
233 return built
234 }
235
236 func describe(annotation application.AnnotationView) note {
237 stale := annotation.Status.IsFailure()
238 return note{
239 ID: annotation.Record.ID,
240 Kind: string(annotation.Record.Kind),
241 Status: annotation.Status,
242 Line: annotation.Line,
243 Body: store.Paragraphs(annotation.Record.Body),
244 Created: annotation.Record.Created.Format("2006-01-02"),
245 Excerpt: annotation.Record.Anchor.Excerpt,
246 Stale: stale,
247 Warning: annotation.Warning,
248 }
249 }

Find an annotation

Search file paths, rationale, kinds, and authors.

moveEnter openEsc close