fork of https://github.com/sourcegraph/zoekt
1// Copyright 2016 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package web
16
17import (
18 "html/template"
19 "log"
20)
21
22// Top provides the standard templates in parsed form
23var Top = template.New("top").Funcs(Funcmap)
24
25// TemplateText contains the text of the standard templates.
26var TemplateText = map[string]string{
27 "head": `
28<head>
29<meta charset="utf-8">
30<meta http-equiv="X-UA-Compatible" content="IE=edge">
31<meta name="viewport" content="width=device-width, initial-scale=1">
32<!-- Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) -->
33<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
34<style>
35 #navsearchbox { width: 350px !important; }
36 #maxhits { width: 100px !important; }
37 #context { width: 70px !important; }
38 .label-dup {
39 border-width: 1px !important;
40 border-style: solid !important;
41 border-color: #aaa !important;
42 color: black;
43 }
44 .noselect {
45 color: #999;
46 user-select: none;
47 }
48 a.label-dup:hover {
49 color: black;
50 background: #ddd;
51 }
52 .result {
53 display: block;
54 content: " ";
55 visibility: hidden;
56 }
57 .container-results {
58 overflow: auto;
59 max-height: calc(100% - 72px);
60 }
61 .inline-pre {
62 border: unset;
63 background-color: unset;
64 margin: unset;
65 padding: unset;
66 overflow: unset;
67 }
68 :target { background-color: #ccf; }
69 table tbody tr td { border: none !important; padding: 2px !important; }
70</style>
71</head>
72 `,
73
74 "jsdep": `
75<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
76<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
77`,
78
79 // the template for the search box.
80 "searchbox": `
81<form action="search">
82 <div class="form-group form-group-lg">
83 <div class="input-group input-group-lg">
84 <input class="form-control" placeholder="Search for some code..." autofocus
85 {{if .Query}}
86 value={{.Query}}
87 {{end}}
88 id="searchbox" type="text" name="q">
89 <div class="input-group-btn">
90 <button class="btn btn-primary">Search</button>
91 </div>
92 </div>
93 </div>
94</form>
95`,
96
97 "navbar": `
98<nav class="navbar navbar-default">
99 <div class="container-fluid">
100 <div class="navbar-header">
101 <a class="navbar-brand" href="/">Zoekt</a>
102 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse" aria-expanded="false">
103 <span class="sr-only">Toggle navigation</span>
104 <span class="icon-bar"></span>
105 <span class="icon-bar"></span>
106 <span class="icon-bar"></span>
107 </button>
108 </div>
109 <div class="navbar-collapse collapse" id="navbar-collapse" aria-expanded="false" style="height: 1px;">
110 <form class="navbar-form navbar-left" action="search">
111 <div class="form-group">
112 <input class="form-control"
113 placeholder="Search for some code..." role="search"
114 id="navsearchbox" type="text" name="q" autofocus
115 {{if .Query}}
116 value={{.Query}}
117 {{end}}>
118 <div class="input-group">
119 <div class="input-group-addon">Max Results</div>
120 <input class="form-control" type="number" id="maxhits" name="num" value="{{.Num}}">
121 </div>
122 <div class="input-group">
123 <div class="input-group-addon">Context Lines</div>
124 <input class="form-control" id="context" name="ctx" type="number" value="{{.Ctx}}">
125 </div>
126 <button class="btn btn-primary">Search</button>
127 <!--Hack: we use a hidden form field to keep track of the debug flag across searches-->
128 {{if .Debug}}<input id="debug" name="debug" type="hidden" value="{{.Debug}}">{{end}}
129 </div>
130 </form>
131 </div>
132 </div>
133</nav>
134<script>
135document.onkeydown=function(e){
136 var e = e || window.event;
137 if (e.key == "/") {
138 var navbox = document.getElementById("navsearchbox");
139 if (document.activeElement !== navbox) {
140 navbox.focus();
141 return false;
142 }
143 }
144};
145</script>
146`,
147 // search box for the entry page.
148 "search": `
149<html>
150{{template "head"}}
151<title>Zoekt, en gij zult spinazie eten</title>
152<body>
153 <div class="jumbotron">
154 <div class="container">
155 {{template "searchbox" .Last}}
156 </div>
157 </div>
158
159 <div class="container">
160 <div class="row">
161 <div class="col-md-8">
162 <h3>Search examples:</h3>
163 <dl class="dl-horizontal">
164 <dt><a href="search?q=needle">needle</a></dt><dd>search for "needle"</dd>
165 <dt><a href="search?q=thread+or+needle">thread or needle</a></dt><dd>search for either "thread" or "needle"</dd>
166 <dt><a href="search?q=class+needle">class needle</a></span></dt><dd>search for files containing both "class" and "needle"</dd>
167 <dt><a href="search?q=class+Needle">class Needle</a></dt><dd>search for files containing both "class" (case insensitive) and "Needle" (case sensitive)</dd>
168 <dt><a href="search?q=class+Needle+case:yes">class Needle case:yes</a></dt><dd>search for files containing "class" and "Needle", both case sensitively</dd>
169 <dt><a href="search?q=%22class Needle%22">"class Needle"</a></dt><dd>search for files with the phrase "class Needle"</dd>
170 <dt><a href="search?q=needle+-hay">needle -hay</a></dt><dd>search for files with the word "needle" but not the word "hay"</dd>
171 <dt><a href="search?q=path+file:java">path file:java</a></dt><dd>search for the word "path" in files whose name contains "java"</dd>
172 <dt><a href="search?q=needle+lang%3Apython&num=50">needle lang:python</a></dt><dd>search for "needle" in Python source code</dd>
173 <dt><a href="search?q=f:%5C.c%24">f:\.c$</a></dt><dd>search for files whose name ends with ".c"</dd>
174 <dt><a href="search?q=path+-file:java">path -file:java</a></dt><dd>search for the word "path" excluding files whose name contains "java"</dd>
175 <dt><a href="search?q=foo.*bar">foo.*bar</a></dt><dd>search for the regular expression "foo.*bar"</dd>
176 <dt><a href="search?q=-%28Path File%29 Stream">-(Path File) Stream</a></dt><dd>search "Stream", but exclude files containing both "Path" and "File"</dd>
177 <dt><a href="search?q=-Path%5c+file+Stream">-Path\ file Stream</a></dt><dd>search "Stream", but exclude files containing "Path File"</dd>
178 <dt><a href="search?q=sym:data">sym:data</a></span></dt><dd>search for symbol definitions containing "data"</dd>
179 <dt><a href="search?q=phone+r:droid">phone r:droid</a></dt><dd>search for "phone" in repositories whose name contains "droid"</dd>
180 <dt><a href="search?q=phone+archived:no">phone archived:no</a></dt><dd>search for "phone" in repositories that are not archived</dd>
181 <dt><a href="search?q=phone+fork:no">phone fork:no</a></dt><dd>search for "phone" in repositories that are not forks</dd>
182 <dt><a href="search?q=phone+public:no">phone public:no</a></dt><dd>search for "phone" in repositories that are not public</dd>
183 <dt><a href="search?q=phone+b:master">phone b:master</a></dt><dd>for Git repos, find "phone" in files in branches whose name contains "master".</dd>
184 <dt><a href="search?q=phone+b:HEAD">phone b:HEAD</a></dt><dd>for Git repos, find "phone" in the default ('HEAD') branch.</dd>
185 </dl>
186 </div>
187 <div class="col-md-4">
188 <h3>To list repositories, try:</h3>
189 <dl class="dl-horizontal">
190 <dt><a href="search?q=r:droid">r:droid</a></dt><dd>list repositories whose name contains "droid".</dd>
191 <dt><a href="search?q=r:go+-r:google">r:go -r:google</a></dt><dd>list repositories whose name contains "go" but not "google".</dd>
192 </dl>
193 </div>
194 </div>
195 </div>
196 <nav class="navbar navbar-default navbar-bottom">
197 <div class="container">
198 {{template "footerBoilerplate"}}
199 <p class="navbar-text navbar-right">
200 Used {{HumanUnit .Stats.IndexBytes}} mem for
201 {{.Stats.Documents}} documents ({{HumanUnit .Stats.ContentBytes}})
202 from {{.Stats.Repos}} repositories.
203 </p>
204 </div>
205 </nav>
206</body>
207</html>
208`,
209 "footerBoilerplate": `<a class="navbar-text" href="about">About</a>`,
210 "results": `
211<html>
212{{template "head"}}
213<title>Results for {{.QueryStr}}</title>
214<script>
215 function zoektAddQ(atom) {
216 window.location.href = "/search?q=" + escape("{{.QueryStr}}" + " " + atom) +
217 "&" + "num=" + {{.Last.Num}};
218 }
219</script>
220<body id="results">
221 {{template "navbar" .Last}}
222 <div class="container-fluid container-results">
223 <h5>
224 {{if .Stats.Crashes}}<br><b>{{.Stats.Crashes}} shards crashed</b><br>{{end}}
225 {{ $fileCount := len .FileMatches }}
226 Found {{.Stats.MatchCount}} results in {{.Stats.FileCount}} files{{if or (lt $fileCount .Stats.FileCount) (or (gt .Stats.ShardsSkipped 0) (gt .Stats.FilesSkipped 0)) }},
227 showing top {{ $fileCount }} files (<a rel="nofollow"
228 href="search?q={{.Last.Query}}&num={{More .Last.Num}}">show more</a>).
229 {{else}}.{{end}}
230 </h5>
231 {{range .FileMatches}}
232 <table class="table table-hover table-condensed">
233 <thead>
234 <tr>
235 <th colspan="2">
236 {{if .URL}}<a name="{{.ResultID}}" class="result"></a><a href="{{.URL}}" >{{else}}<a name="{{.ResultID}}">{{end}}
237 <small>
238 {{.Repo}}:{{.FileName}} {{if .ScoreDebug}}<i>({{.ScoreDebug}})</i>{{end}}</a>:
239 <span style="font-weight: normal">[ {{if .Branches}}{{range .Branches}}<span class="label label-default">{{.}}</span>,{{end}}{{end}} ]</span>
240 {{if .Language}}<button
241 title="restrict search to files written in {{.Language}}"
242 onclick="zoektAddQ('lang:"{{.Language}}"')" class="label label-primary">language {{.Language}}</button></span>{{end}}
243 {{if .DuplicateID}}<a class="label label-dup" href="#{{.DuplicateID}}">Duplicate result</a>{{end}}
244 </small>
245 </th>
246 </tr>
247 </thead>
248 {{if not .DuplicateID}}
249 <tbody>
250 {{range .Matches}}
251 {{if gt .LineNum 0}}
252 <tr>
253 <td style="width: 1%; white-space: nowrap; background-color: rgba(238, 238, 255, 0.6);">
254<pre class="inline-pre"><p style="margin: 0px;">{{$beforeLines := AddLineNumbers .Before .LineNum true}}{{range $line := $beforeLines}}<span class="noselect"><u>{{$line.LineNum}}</u>:</span>
255{{end}}<span class="noselect">{{if .URL}}<a href="{{.URL}}">{{end}}<u>{{.LineNum}}</u>{{if .URL}}</a>{{end}}:</span>
256{{$afterLines := AddLineNumbers .After .LineNum false}}{{range $line := $afterLines}}<span class="noselect"><u>{{$line.LineNum}}</u>:</span>
257{{end}}</p></pre>
258 </td>
259 <td style="background-color: rgba(238, 238, 255, 0.6);">
260<pre class="inline-pre"><p style="margin: 0px;">{{range $line := $beforeLines}} {{$line.Content}}
261{{end}}</p> {{range .Fragments}}{{LimitPre 100 .Pre}}<b>{{.Match}}</b>{{LimitPost 100 (TrimTrailingNewline .Post)}}{{end}}<p style="margin: 0px;">{{range $line := $afterLines}} {{$line.Content}}
262{{end}}</p>{{if .ScoreDebug}}<i>({{.ScoreDebug}})</i>{{end}}</pre>
263 </td>
264 </tr>
265 {{end}}
266 </tbody>
267 {{end}}
268 {{end}}
269 </table>
270 {{end}}
271
272 <nav class="navbar navbar-default navbar-bottom">
273 <div class="container">
274 {{template "footerBoilerplate"}}
275 <p class="navbar-text navbar-right">
276 Took {{.Stats.Duration}}{{if .Stats.Wait}} (queued: {{.Stats.Wait}}){{end}} for
277 {{HumanUnit .Stats.IndexBytesLoaded}}B index data,
278 {{.Stats.NgramMatches}} ngram matches,
279 {{.Stats.FilesConsidered}} docs considered,
280 {{.Stats.FilesLoaded}} docs ({{HumanUnit .Stats.ContentBytesLoaded}}B) loaded,
281 {{.Stats.ShardsScanned}} shards scanned,
282 {{.Stats.ShardsSkippedFilter}} shards filtered
283 {{- if or .Stats.FilesSkipped .Stats.ShardsSkipped -}}
284 , {{.Stats.FilesSkipped}} docs skipped, {{.Stats.ShardsSkipped}} shards skipped
285 {{- end -}}
286 .
287 </p>
288 </div>
289 </nav>
290 </div>
291 {{ template "jsdep"}}
292</body>
293</html>
294`,
295
296 "repolist": `
297<html>
298{{template "head"}}
299<body id="results">
300 <div class="container">
301 {{template "navbar" .Last}}
302 <div><b>
303 Found {{.Stats.Repos}} repositories ({{.Stats.Documents}} files, {{HumanUnit .Stats.ContentBytes}}B content)
304 </b></div>
305 <table class="table table-hover table-condensed">
306 <thead>
307 <tr>
308 {{- define "q"}}q={{.Last.Query}}{{if (gt .Last.Num 0)}}&num={{.Last.Num}}{{end}}{{end}}
309 <th>Name <a href="/search?{{template "q" .}}&order=name">▼</a><a href="/search?{{template "q" .}}&order=revname">▲</a></th>
310 <th>Last updated <a href="/search?{{template "q" .}}&order=revtime">▼</a><a href="/search?{{template "q" .}}&order=time">▲</a></th>
311 <th>Branches</th>
312 <th>Size <a href="/search?{{template "q" .}}&order=revsize">▼</a><a href="/search?{{template "q" .}}&order=size">▲</a></th>
313 <th>RAM <a href="/search?{{template "q" .}}&order=revram">▼</a><a href="/search?{{template "q" .}}&order=ram">▲</a></th>
314 </tr>
315 </thead>
316 <tbody>
317 {{range .Repos -}}
318 <tr>
319 <td>{{if .URL}}<a href="{{.URL}}">{{end}}{{.Name}}{{if .URL}}</a>{{end}}</td>
320 <td><small>{{.IndexTime.Format "Jan 02, 2006 15:04"}}</small></td>
321 <td style="vertical-align: middle;">
322 {{- range .Branches -}}
323 {{if .URL}}<tt><a class="label label-default small" href="{{.URL}}">{{end}}{{.Name}}{{if .URL}}</a> </tt>{{end}}
324 {{- end -}}
325 </td>
326 <td><small>{{HumanUnit .Files}} files ({{HumanUnit .Size}}B)</small></td>
327 <td><small>{{HumanUnit .MemorySize}}B</td>
328 </tr>
329 {{end}}
330 </tbody>
331 </table>
332 </div>
333
334 <nav class="navbar navbar-default navbar-bottom">
335 <div class="container">
336 {{template "footerBoilerplate"}}
337 <p class="navbar-text navbar-right">
338 </p>
339 </div>
340 </nav>
341
342 {{ template "jsdep"}}
343</body>
344</html>
345`,
346
347 "print": `
348<html>
349 {{template "head"}}
350 <title>{{.Repo}}:{{.Name}}</title>
351<body id="results">
352 {{template "navbar" .Last}}
353 <div class="container-fluid container-results" >
354 <div><b>{{.Name}}</b></div>
355 <div class="table table-hover table-condensed" style="overflow:auto; background: #eef;">
356 {{ range $index, $ln := .Lines}}
357 <pre id="l{{Inc $index}}" class="inline-pre"><span class="noselect"><a href="#l{{Inc $index}}">{{Inc $index}}</a>: </span>{{$ln}}</pre>
358 {{end}}
359 </div>
360 <nav class="navbar navbar-default navbar-bottom">
361 <div class="container">
362 {{template "footerBoilerplate"}}
363 <p class="navbar-text navbar-right">
364 </p>
365 </div>
366 </nav>
367 </div>
368 {{ template "jsdep"}}
369</body>
370</html>
371`,
372
373 "about": `
374
375<html>
376 {{template "head"}}
377 <title>About <em>zoekt</em></title>
378<body>
379
380
381 <div class="jumbotron">
382 <div class="container">
383 {{template "searchbox" .Last}}
384 </div>
385 </div>
386
387 <div class="container">
388 <p>
389 This is <a href="http://github.com/sourcegraph/zoekt"><em>zoekt</em> (IPA: /zukt/)</a>,
390 an open-source full text search engine. It's pronounced roughly as you would
391 pronounce "zooked" in English.
392 </p>
393 <p>
394 {{if .Version}}<em>Zoekt</em> version {{.Version}}, uptime{{else}}Uptime{{end}} {{.Uptime}}
395 </p>
396
397 <p>
398 Used {{HumanUnit .Stats.IndexBytes}} memory for
399 {{.Stats.Documents}} documents ({{HumanUnit .Stats.ContentBytes}})
400 from {{.Stats.Repos}} repositories.
401 </p>
402 </div>
403
404 <nav class="navbar navbar-default navbar-bottom">
405 <div class="container">
406 {{template "footerBoilerplate"}}
407 <p class="navbar-text navbar-right">
408 </p>
409 </div>
410 </nav>
411`,
412 "robots": `
413user-agent: *
414disallow: /search
415`,
416}
417
418func init() {
419 for k, v := range TemplateText {
420 _, err := Top.New(k).Parse(v)
421 if err != nil {
422 log.Panicf("parse(%s): %v:", k, err)
423 }
424 }
425}