From 6f3d886de33e2ef13e3611c80a8b6c4bd792f257 Mon Sep 17 00:00:00 2001 From: Stefan VanBuren Date: Fri, 5 Apr 2024 08:31:03 -0400 Subject: [PATCH] Add syntax highlighting Using chroma. Still a few TODOs to investigate later, but this works for now. Closes #24. --- go.mod | 2 ++ go.sum | 10 ++++++++++ main.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 744864c..8e9452e 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( buf.build/gen/go/bufbuild/registry/connectrpc/go v1.16.0-20240327205414-2f1854093b57.1 buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.33.0-20240327205414-2f1854093b57.1 connectrpc.com/connect v1.16.0 + github.com/alecthomas/chroma/v2 v2.13.0 github.com/bufbuild/httplb v0.1.0 github.com/charmbracelet/bubbles v0.18.0 github.com/charmbracelet/bubbletea v0.25.0 @@ -19,6 +20,7 @@ require ( github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect + github.com/dlclark/regexp2 v1.11.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.18 // indirect github.com/mattn/go-localereader v0.0.1 // indirect diff --git a/go.sum b/go.sum index bb16e1c..a64ae20 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,12 @@ buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.33.0-20240327205414-2f1 buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.33.0-20240327205414-2f1854093b57.1/go.mod h1:Cw3zAf/W6/Y6BxHo6RWlVCE9FeNnbcL8Fq45qalol2I= connectrpc.com/connect v1.16.0 h1:rdtfQjZ0OyFkWPTegBNcH7cwquGAN1WzyJy80oFNibg= connectrpc.com/connect v1.16.0/go.mod h1:XpZAduBQUySsb4/KO5JffORVkDI4B6/EYPi7N8xpNZw= +github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU= +github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI= +github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= @@ -22,10 +28,14 @@ github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2 github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= 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.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/jdx/go-netrc v1.0.0 h1:QbLMLyCZGj0NA8glAhxUpf1zDg6cxnWgMBbjq40W0gQ= github.com/jdx/go-netrc v1.0.0/go.mod h1:Gh9eFQJnoTNIRHXl2j5bJXA1u84hQWJWgGh569zF3v8= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= diff --git a/main.go b/main.go index 22c0b73..aaf15b6 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "context" "encoding/base64" "fmt" @@ -13,6 +14,9 @@ import ( modulev1 "buf.build/gen/go/bufbuild/registry/protocolbuffers/go/buf/registry/module/v1" ownerv1 "buf.build/gen/go/bufbuild/registry/protocolbuffers/go/buf/registry/owner/v1" "connectrpc.com/connect" + "github.com/alecthomas/chroma/v2/formatters" + "github.com/alecthomas/chroma/v2/lexers" + "github.com/alecthomas/chroma/v2/styles" "github.com/bufbuild/httplb" "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" @@ -470,11 +474,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.commitsTable, cmd = m.commitsTable.Update(msg) case modelStateBrowsingCommitContents: m.commitFilesTable, cmd = m.commitFilesTable.Update(msg) - for _, file := range m.currentCommitFiles { - if file.Path == m.commitFilesTable.SelectedRow()[0] { - m.fileViewport.SetContent(string(file.Content)) - } - } case modelStateBrowsingCommitFileContents: m.fileViewport, cmd = m.fileViewport.Update(msg) case modelStateSearching: @@ -512,6 +511,20 @@ func (m model) View() string { case modelStateLoadingCommitContents: view = m.spinner.View() case modelStateBrowsingCommitContents, modelStateBrowsingCommitFileContents: + selectedFileName := m.commitFilesTable.SelectedRow()[0] + var fileContents string + for _, file := range m.currentCommitFiles { + if file.Path == selectedFileName { + fileContents = string(file.Content) + break + } + } + highlightedFile, err := highlightFile(selectedFileName, fileContents) + if err != nil { + // TODO: Log and use the unhighlighted file contents? + return fmt.Sprintf("highlighting selected file %s: %s", selectedFileName, err) + } + m.fileViewport.SetContent(highlightedFile) fileView := m.fileViewport.View() if m.state == modelStateBrowsingCommitFileContents { fileViewStyle := lipgloss.NewStyle(). @@ -661,3 +674,38 @@ func getUserTokenFromNetrc(hostname string) (username string, token string, err token = parsedNetrc.Machine(hostname).Get("password") return username, token, nil } + +func highlightFile(filename, fileContents string) (string, error) { + // TODO: There are only a few filetypes that can actually exist in a module: + // - Licenses + // - README (markdown/text(?)) + // - protobuf + lexer := lexers.Match(filename) + if lexer == nil { + // This happens for LICENSE files. + lexer = lexers.Fallback + } + // TODO: Make this configurable? + // Probably not ;) + style := styles.Get("algol_nu") + if style == nil { + style = styles.Fallback + } + // TODO: This seemingly works on my terminal, but we may need + // to select a different one based on terminal type. + // I think we should be able to figure that out from + // tea/termenv, somehow. + formatter := formatters.TTY256 + if formatter == nil { + formatter = formatters.Fallback + } + iterator, err := lexer.Tokenise(nil, fileContents) + if err != nil { + return "", fmt.Errorf("tokenizing file: %w", err) + } + var buffer bytes.Buffer + if err := formatter.Format(&buffer, style, iterator); err != nil { + return "", fmt.Errorf("formatting file: %w", err) + } + return buffer.String(), nil +}