diff --git a/.travis.yml b/.travis.yml index e39e4ef..5476b15 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,9 +8,5 @@ language: go go: - 1.13.x -before_install: - # Needed for test suite to pass - - sudo apt-get install -y highlight - script: - ./test.sh diff --git a/README.md b/README.md index 73909fc..cb1af51 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,8 @@ doesn't work that way, Doing the right thing includes: -* **Syntax highlight** source code by default if - [Highlight](http://www.andre-simon.de/doku/highlight/en/highlight.php) - is installed. +* **Syntax highlight** source code by default using + [Chroma](https://github.com/alecthomas/chroma) * **Search is incremental** / find-as-you-type just like in [Chrome](http://www.google.com/chrome) or [Emacs](http://www.gnu.org/software/emacs/) @@ -106,14 +105,7 @@ The `m.Page()` parameter can also be initialized using `NewReaderFromText()` or Developing ---------- -First, install [Highlight](http://www.andre-simon.de/zip/download.php), -otherwise the test suite won't pass: - -* On macOS: [`brew install highlight`](https://brew.sh/) -* On Ubuntu: [`sudo apt-get install highlight`](https://packages.ubuntu.com/search?suite=all&searchon=names&keywords=highlight) -* Elsewhere, follow [instructions](http://www.andre-simon.de/zip/download.php) - -Also, you need the [go tools](https://golang.org/doc/install). +You need the [go tools](https://golang.org/doc/install). Run tests: diff --git a/go.mod b/go.mod index e128708..fd71b43 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,9 @@ module github.com/walles/moar go 1.12 require ( + github.com/alecthomas/chroma v0.8.2 github.com/gdamore/tcell/v2 v2.0.0 github.com/google/go-cmp v0.3.0 // indirect - github.com/pkg/errors v0.8.1 // indirect github.com/sirupsen/logrus v1.4.2 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 gotest.tools v2.2.0+incompatible diff --git a/go.sum b/go.sum index f974349..4eda007 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,16 @@ github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= +github.com/alecthomas/chroma v0.8.2 h1:x3zkuE2lUk/RIekyAJ3XRqSCP4zwWDfcw/YJCuCAACg= +github.com/alecthomas/chroma v0.8.2/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM= +github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= +github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= +github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= +github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= +github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= +github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell/v2 v2.0.0 h1:GRWG8aLfWAlekj9Q6W29bVvkHENc6hp79XOqG4AWDOs= @@ -13,19 +24,26 @@ github.com/lucasb-eyer/go-colorful v1.0.2 h1:mCMFu6PgSozg9tDNMMK3g18oJBX7oYGrC09 github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -41,6 +59,10 @@ golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeo golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY= +golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 h1:HlFl4V6pEMziuLXyRkm5BIYq1y1GAbb02pRlWvI54OM= golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8= diff --git a/m/pager_test.go b/m/pager_test.go index 937e00c..45a6b19 100644 --- a/m/pager_test.go +++ b/m/pager_test.go @@ -156,8 +156,6 @@ func TestTabHandling(t *testing.T) { assertIndexOfFirstX(t, "\x09Johan\x09x", 12) } -// This test assumes highlight is installed: -// http://www.andre-simon.de/zip/download.php func TestCodeHighlighting(t *testing.T) { // From: https://coderwall.com/p/_fmbug/go-get-path-to-current-file _, filename, _, ok := runtime.Caller(0) diff --git a/m/reader.go b/m/reader.go index 7091f6a..82fed9a 100644 --- a/m/reader.go +++ b/m/reader.go @@ -5,17 +5,21 @@ import ( "bytes" "fmt" "io" + "io/ioutil" "math" "os" "os/exec" "path" - "path/filepath" - "regexp" "strings" "sync" "time" log "github.com/sirupsen/logrus" + + "github.com/alecthomas/chroma" + "github.com/alecthomas/chroma/formatters" + "github.com/alecthomas/chroma/lexers" + "github.com/alecthomas/chroma/styles" ) // Reader reads a file into an array of strings. @@ -247,54 +251,6 @@ func newReaderFromCommand(filename string, filterCommand ...string) (*Reader, er return reader, nil } -func canHighlight(filename string) bool { - extension := filepath.Ext(filename) - if len(extension) <= 1 { - // No extension or a single "." - return false - } - - if extension == ".txt" { - // Highlighting text files won't be of much use. - // - // Also: https://github.com/walles/moar/issues/29 - return false - } - - // Remove leading dot from the extension - extension = extension[1:] - - // Check file extension vs "highlight --list-scripts=langs" before calling - // highlight, otherwise files with unsupported extensions (like .log) get - // messed upp. - highlight := exec.Command("highlight", "--list-scripts=langs") - outBytes, err := highlight.CombinedOutput() - if err != nil { - return false - } - - extensionMatcher := regexp.MustCompile("[^() ]+") - - outString := string(outBytes) - outLines := strings.Split(outString, "\n") - for _, line := range outLines { - parts := strings.Split(line, ": ") - if len(parts) < 2 { - continue - } - - // Pick out all extensions from this line - for _, supportedExtension := range extensionMatcher.FindAllString(parts[1], -1) { - if extension == supportedExtension { - return true - } - } - } - - // No match - return false -} - func tryOpen(filename string) error { // Try opening the file tryMe, err := os.Open(filename) @@ -307,7 +263,10 @@ func tryOpen(filename string) error { buffer := make([]byte, 1) _, err = tryMe.Read(buffer) - if err != nil && err.Error() == "EOF" { + if err == nil { + return nil + } + if err.Error() == "EOF" { // Empty file, this is fine return nil } @@ -318,8 +277,8 @@ func tryOpen(filename string) error { // NewReaderFromFilename creates a new file reader. // // The Reader will try to uncompress various compressed file format, and also -// apply highlighting to the file using highlight: -// http://www.andre-simon.de/doku/highlight/en/highlight.php +// apply highlighting to the file using Chroma: +// https://github.com/alecthomas/chroma func NewReaderFromFilename(filename string) (*Reader, error) { fileError := tryOpen(filename) if fileError != nil { @@ -336,21 +295,38 @@ func NewReaderFromFilename(filename string) (*Reader, error) { return newReaderFromCommand(filename, "xz", "-d", "-c") } - // Highlight input file using highlight: - // http://www.andre-simon.de/doku/highlight/en/highlight.php - if canHighlight(filename) { - highlighted, err := newReaderFromCommand(filename, "highlight", "--out-format=esc", "-i") - if err == nil { - return highlighted, err - } + // Highlight input file using Chroma: + // https://github.com/alecthomas/chroma + lexer := lexers.Match(filename) + if lexer == nil { + lexer = lexers.Fallback } - stream, err := os.Open(filename) + // See: https://github.com/alecthomas/chroma#identifying-the-language + // FIXME: Do we actually need this? We should profile our reader performance + // with and without. + lexer = chroma.Coalesce(lexer) + + formatter := formatters.Get("terminal16m") + if formatter == nil { + formatter = formatters.Fallback + } + + contents, err := ioutil.ReadFile(filename) if err != nil { return nil, err } - reader := NewReaderFromStream(filename, stream) + iterator, err := lexer.Tokenise(nil, string(contents)) + if err != nil { + return nil, err + } + + var stringBuffer bytes.Buffer + err = formatter.Format(&stringBuffer, styles.Native, iterator) + + // FIXME: Do basename(filename) first? + reader := NewReaderFromText(filename, stringBuffer.String()) return reader, nil } diff --git a/moar.go b/moar.go index c2103f1..edbd1ec 100644 --- a/moar.go +++ b/moar.go @@ -5,7 +5,6 @@ import ( "fmt" "io" "os" - "os/exec" "path/filepath" "runtime" "strings" @@ -32,14 +31,6 @@ func printUsage(output io.Writer) { flag.PrintDefaults() - _, err := exec.LookPath("highlight") - if err != nil { - // Highlight not installed - fmt.Fprintln(output) - fmt.Fprintln(output, "To enable syntax highlighting when viewing source code, install") - fmt.Fprintln(output, "Highlight (http://www.andre-simon.de/zip/download.php).") - } - moarPath, err := filepath.Abs(os.Args[0]) if err == nil { pagerValue, err := filepath.Abs(os.Getenv("PAGER"))