initial working read only state
This commit is contained in:
238
highlight.go
Normal file
238
highlight.go
Normal file
@@ -0,0 +1,238 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/chroma/v2/quick"
|
||||
)
|
||||
|
||||
const reviewContextLines = 3
|
||||
|
||||
var hunkHeaderPattern = regexp.MustCompile(`^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@`)
|
||||
|
||||
type highlightedDiffLine struct {
|
||||
gutter string
|
||||
code string
|
||||
selected bool
|
||||
}
|
||||
|
||||
type parsedDiffLine struct {
|
||||
raw string
|
||||
oldLine int
|
||||
newLine int
|
||||
header bool
|
||||
notice bool
|
||||
selected bool
|
||||
}
|
||||
|
||||
func highlightDiff(path, hunk string, startLine, endLine int, side string) []highlightedDiffLine {
|
||||
if hunk == "" {
|
||||
return []highlightedDiffLine{{code: "(GitHub did not return a diff hunk)"}}
|
||||
}
|
||||
if endLine <= 0 {
|
||||
endLine = startLine
|
||||
}
|
||||
if startLine <= 0 {
|
||||
startLine = endLine
|
||||
}
|
||||
if startLine > endLine {
|
||||
startLine, endLine = endLine, startLine
|
||||
}
|
||||
|
||||
parsed := parseDiff(hunk, startLine, endLine, side)
|
||||
visible := reviewedWindow(parsed)
|
||||
padding := commonIndent(visible)
|
||||
lexer := lexerForPath(path)
|
||||
out := make([]highlightedDiffLine, 0, len(visible))
|
||||
for _, line := range visible {
|
||||
if line.raw == "⋯" {
|
||||
out = append(out, highlightedDiffLine{code: "\x1b[38;5;245m⋯\x1b[0m"})
|
||||
continue
|
||||
}
|
||||
if line.notice {
|
||||
out = append(out, highlightedDiffLine{code: "\x1b[38;5;245m" + line.raw + "\x1b[0m"})
|
||||
continue
|
||||
}
|
||||
if line.header {
|
||||
out = append(out, highlightedDiffLine{code: "\x1b[38;5;141m" + line.raw + "\x1b[0m"})
|
||||
continue
|
||||
}
|
||||
out = append(out, renderDiffLine(lexer, line, padding))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func parseDiff(hunk string, startLine, endLine int, side string) []parsedDiffLine {
|
||||
oldLine, newLine := 0, 0
|
||||
lines := make([]parsedDiffLine, 0, strings.Count(hunk, "\n")+1)
|
||||
for _, raw := range strings.Split(hunk, "\n") {
|
||||
if match := hunkHeaderPattern.FindStringSubmatch(raw); match != nil {
|
||||
oldLine, _ = strconv.Atoi(match[1])
|
||||
newLine, _ = strconv.Atoi(match[2])
|
||||
lines = append(lines, parsedDiffLine{raw: raw, header: true})
|
||||
continue
|
||||
}
|
||||
|
||||
line := parsedDiffLine{raw: raw}
|
||||
switch {
|
||||
case strings.HasPrefix(raw, "+"):
|
||||
line.newLine = newLine
|
||||
newLine++
|
||||
case strings.HasPrefix(raw, "-"):
|
||||
line.oldLine = oldLine
|
||||
oldLine++
|
||||
case strings.HasPrefix(raw, `\`):
|
||||
// "\ No newline at end of file" has no source coordinate.
|
||||
default:
|
||||
line.oldLine, line.newLine = oldLine, newLine
|
||||
oldLine++
|
||||
newLine++
|
||||
}
|
||||
coordinate := line.newLine
|
||||
if side == "LEFT" {
|
||||
coordinate = line.oldLine
|
||||
}
|
||||
line.selected = coordinate > 0 && coordinate >= startLine && coordinate <= endLine
|
||||
lines = append(lines, line)
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func reviewedWindow(lines []parsedDiffLine) []parsedDiffLine {
|
||||
first, last := -1, -1
|
||||
for i, line := range lines {
|
||||
if line.selected {
|
||||
if first == -1 {
|
||||
first = i
|
||||
}
|
||||
last = i
|
||||
}
|
||||
}
|
||||
if first == -1 {
|
||||
out := make([]parsedDiffLine, 0, 2)
|
||||
for _, line := range lines {
|
||||
if line.header {
|
||||
out = append(out, line)
|
||||
break
|
||||
}
|
||||
}
|
||||
return append(out, parsedDiffLine{
|
||||
raw: "(original reviewed lines unavailable in GitHub's historical diff)",
|
||||
notice: true,
|
||||
})
|
||||
}
|
||||
|
||||
start := max(0, first-reviewContextLines)
|
||||
end := min(len(lines), last+reviewContextLines+1)
|
||||
header := -1
|
||||
for i := first; i >= 0; i-- {
|
||||
if lines[i].header {
|
||||
header = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
out := make([]parsedDiffLine, 0, end-start+3)
|
||||
if header >= 0 && header < start {
|
||||
out = append(out, lines[header])
|
||||
}
|
||||
if start > 0 && start != header {
|
||||
out = append(out, parsedDiffLine{raw: "⋯"})
|
||||
}
|
||||
out = append(out, lines[start:end]...)
|
||||
if end < len(lines) {
|
||||
out = append(out, parsedDiffLine{raw: "⋯"})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func renderDiffLine(lexer string, line parsedDiffLine, padding int) highlightedDiffLine {
|
||||
marker, source := " ", line.raw
|
||||
lineNumber := ""
|
||||
markerStyle := "\x1b[38;5;245m"
|
||||
switch {
|
||||
case strings.HasPrefix(line.raw, "+"):
|
||||
marker, source = "+", strings.TrimPrefix(line.raw, "+")
|
||||
lineNumber = coordinateText(line.newLine)
|
||||
markerStyle = "\x1b[38;5;114m"
|
||||
case strings.HasPrefix(line.raw, "-"):
|
||||
marker, source = "-", strings.TrimPrefix(line.raw, "-")
|
||||
lineNumber = coordinateText(line.oldLine)
|
||||
markerStyle = "\x1b[38;5;203m"
|
||||
default:
|
||||
source = strings.TrimPrefix(line.raw, " ")
|
||||
lineNumber = coordinateText(line.newLine)
|
||||
if lineNumber == "" {
|
||||
lineNumber = coordinateText(line.oldLine)
|
||||
}
|
||||
}
|
||||
|
||||
source = strings.ReplaceAll(source, "\t", " ")
|
||||
source = trimIndent(source, padding)
|
||||
return highlightedDiffLine{
|
||||
gutter: fmt.Sprintf(
|
||||
"\x1b[38;5;245m%5s\x1b[0m %s%s\x1b[0m ",
|
||||
lineNumber, markerStyle, marker,
|
||||
),
|
||||
code: highlightedSource(lexer, source),
|
||||
selected: line.selected,
|
||||
}
|
||||
}
|
||||
|
||||
func commonIndent(lines []parsedDiffLine) int {
|
||||
padding := -1
|
||||
for _, line := range lines {
|
||||
if line.header || line.notice || line.raw == "⋯" || strings.HasPrefix(line.raw, `\`) {
|
||||
continue
|
||||
}
|
||||
source := line.raw
|
||||
if strings.HasPrefix(source, "+") || strings.HasPrefix(source, "-") || strings.HasPrefix(source, " ") {
|
||||
source = source[1:]
|
||||
}
|
||||
source = strings.ReplaceAll(source, "\t", " ")
|
||||
if strings.TrimSpace(source) == "" {
|
||||
continue
|
||||
}
|
||||
indent := len(source) - len(strings.TrimLeft(source, " "))
|
||||
if padding == -1 || indent < padding {
|
||||
padding = indent
|
||||
}
|
||||
}
|
||||
return max(0, padding)
|
||||
}
|
||||
|
||||
func trimIndent(source string, padding int) string {
|
||||
for padding > 0 && strings.HasPrefix(source, " ") {
|
||||
source = source[1:]
|
||||
padding--
|
||||
}
|
||||
return source
|
||||
}
|
||||
|
||||
func coordinateText(line int) string {
|
||||
if line <= 0 {
|
||||
return ""
|
||||
}
|
||||
return strconv.Itoa(line)
|
||||
}
|
||||
|
||||
func highlightedSource(lexer, source string) string {
|
||||
var highlighted bytes.Buffer
|
||||
if err := quick.Highlight(&highlighted, source, lexer, "terminal16m", "github-dark"); err != nil {
|
||||
return source
|
||||
}
|
||||
return strings.TrimSuffix(highlighted.String(), "\n")
|
||||
}
|
||||
|
||||
func lexerForPath(path string) string {
|
||||
ext := strings.TrimPrefix(filepath.Ext(path), ".")
|
||||
if ext == "" {
|
||||
return "plaintext"
|
||||
}
|
||||
return ext
|
||||
}
|
||||
Reference in New Issue
Block a user