Author | Jakob Wakeling <[email protected]> |
Date | 2023-12-28 08:15:51 |
Commit | d737f58666affab00577c1433ca7d04fd54d9ee2 |
Parent | d6784d5fe91e45dad537cd5ae4a85898d1e07d24 |
Add syntax highlighting
Diffstat
M | README.md | | | 2 | +- |
M | go.mod | | | 2 | ++ |
M | go.sum | | | 4 | ++++ |
M | res/repo/file.html | | | 6 | ++---- |
M | src/repo/file.go | | | 18 | +++++++++++++----- |
A | src/repo/highlight.go | | | 78 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
6 files changed, 100 insertions, 10 deletions
diff --git a/README.md b/README.md index 1b75d1d..9954bd5 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ A simple and lightweight Git web server. - Git Smart HTTP protocol (v2 only) - Git SSH protocol (planned) - Repository log, tree, refs, and commit viewers -- File viewer with (planned) syntax highlighting +- File viewer with syntax highlighting - File log, blame (planned), and raw views - Public and private repositories - Read and write permissions for non owners (planned) diff --git a/go.mod b/go.mod index 6778b5a..03063dd 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.21.0 require ( github.com/adrg/xdg v0.4.0 + github.com/alecthomas/chroma v0.10.0 github.com/buildkite/terminal-to-html/v3 v3.10.1 github.com/dustin/go-humanize v1.0.1 github.com/go-chi/chi/v5 v5.0.11 @@ -19,6 +20,7 @@ require ( github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect github.com/cloudflare/circl v1.3.6 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/dlclark/regexp2 v1.4.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect diff --git a/go.sum b/go.sum index 03ce2bb..4527859 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= +github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= +github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -22,6 +24,8 @@ github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxG github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= diff --git a/res/repo/file.html b/res/repo/file.html index cfa6f52..716f6b7 100644 --- a/res/repo/file.html +++ b/res/repo/file.html @@ -1,5 +1,5 @@ <!DOCTYPE html> -<head lang="en-GB">{{template "base/head" .}}</head> +<head lang="en-GB">{{template "base/head" .}}{{.BodyCss}}</head> <body> <header> {{template "repo/header" .}}<hr> @@ -12,9 +12,7 @@ <td class="lnum" style="text-align: right;"> <pre>{{range $i, $l := .Lines}}<a id="{{$i}}" href="#{{$i}}">{{$i}}</a>{{end}}</pre> </td> - <td class="line"> - <pre>{{.Body}}</pre> - </td> + <td class="line">{{.HtmlBody}}</td> </tr> {{else}} <tr><td>Binary file</td></tr> diff --git a/src/repo/file.go b/src/repo/file.go index f45935a..5a4ba8d 100644 --- a/src/repo/file.go +++ b/src/repo/file.go @@ -44,10 +44,9 @@ func HandleFile(w http.ResponseWriter, r *http.Request) { HeaderFields Title, Path, LineC, Size, Mode string Lines []string - Body string - HtmlPath template.HTML + HtmlBody, HtmlPath, BodyCss template.HTML }{ - Title: repo.Name + " - File", + Title: repo.Name + " - " + tpath, HeaderFields: GetHeaderFields(auth, user, repo, r.Host), } @@ -129,8 +128,17 @@ func HandleFile(w http.ResponseWriter, r *http.Request) { return } - data.Body = string(append(buf, buf2...)) - data.Lines = strings.Split(data.Body, "\n") + body := string(append(buf, buf2...)) + buf, css, err := Highlight(file.Name, body) + if err != nil { + log.Println("[/repo/file]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } + + data.HtmlBody = template.HTML(buf) + data.BodyCss = template.HTML("<style>" + css + "</style>") + data.Lines = strings.Split(body, "\n") } rc.Close() diff --git a/src/repo/highlight.go b/src/repo/highlight.go new file mode 100644 index 0000000..16e75ca --- /dev/null +++ b/src/repo/highlight.go @@ -0,0 +1,78 @@ +// Copyright (C) 2023, Jakob Wakeling +// All rights reserved. + +package repo + +import ( + "bytes" + + "github.com/alecthomas/chroma" + "github.com/alecthomas/chroma/formatters/html" + "github.com/alecthomas/chroma/lexers" + "github.com/alecthomas/chroma/styles" +) + +func Highlight(name, input string) (string, string, error) { + var buf, css bytes.Buffer + + lexer := lexers.Match(name) + if lexer == nil { + lexer = lexers.Fallback + } + + formatter := html.New(html.WithClasses(true)) + + iter, err := lexer.Tokenise(nil, input) + if err != nil { + return "", "", err + } + + if err := formatter.Format(&buf, goitStyle, iter); err != nil { + return "", "", err + } + + if err := formatter.WriteCSS(&css, goitStyle); err != nil { + return "", "", err + } + + return buf.String(), css.String(), nil +} + +var goitStyle = styles.Register(chroma.MustNewStyle("goit", chroma.StyleEntries{ + chroma.Background: "#888888", + chroma.Comment: "italic #666666", + chroma.CommentPreproc: "noinherit #8ec07c", + chroma.CommentPreprocFile: "noinherit #b8bb26", + chroma.GenericDeleted: "#d65d0e", + chroma.GenericEmph: "italic", + chroma.GenericError: "bold bg:#fb4934", + chroma.GenericHeading: "bold #fabd2f", + chroma.GenericInserted: "#b8bb26", + chroma.GenericOutput: "#504945", + chroma.GenericPrompt: "#ebdbb2", + chroma.GenericStrong: "bold", + chroma.GenericSubheading: "bold #fabd2f", + chroma.GenericTraceback: "bold bg:#fb4934", + chroma.GenericUnderline: "underline", + chroma.Keyword: "#fb4934", + chroma.KeywordNamespace: "#d3869b", + chroma.KeywordType: "#fabd2f", + chroma.LiteralNumber: "#d3869b", + chroma.LiteralString: "#b8bb26", + chroma.LiteralStringEscape: "#d3869b", + chroma.LiteralStringInterpol: "#8ec07c", + chroma.LiteralStringRegex: "#fe8019", + chroma.LiteralStringSymbol: "#83a598", + chroma.Name: "#ebdbb2", + chroma.NameAttribute: "#fabd2f", + chroma.NameBuiltin: "#fabd2f", + chroma.NameClass: "#fabd2f", + chroma.NameConstant: "#d3869b", + chroma.NameEntity: "#fabd2f", + chroma.NameException: "#fb4934", + chroma.NameFunction: "#fabd2f", + chroma.NameLabel: "#fb4934", + chroma.NameTag: "#8ec07c", + chroma.NameVariable: "#83a598", + chroma.Operator: "#8ec07c", +}))