2019-06-09 08:47:55 +03:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2019-07-07 19:18:29 +03:00
|
|
|
"flag"
|
2019-06-09 20:40:35 +03:00
|
|
|
"fmt"
|
2019-06-09 20:34:52 +03:00
|
|
|
"io"
|
|
|
|
"os"
|
2022-04-18 19:41:48 +03:00
|
|
|
"os/exec"
|
2022-04-18 19:49:25 +03:00
|
|
|
"path/filepath"
|
2019-07-08 07:42:48 +03:00
|
|
|
"runtime"
|
2024-01-30 01:38:20 +03:00
|
|
|
"sort"
|
2023-03-19 13:56:54 +03:00
|
|
|
"strconv"
|
2019-06-16 11:02:19 +03:00
|
|
|
"strings"
|
2021-04-06 17:22:40 +03:00
|
|
|
"time"
|
2019-06-09 20:34:52 +03:00
|
|
|
|
2022-12-29 10:28:27 +03:00
|
|
|
"github.com/alecthomas/chroma/v2"
|
|
|
|
"github.com/alecthomas/chroma/v2/formatters"
|
2024-01-02 11:39:47 +03:00
|
|
|
"github.com/alecthomas/chroma/v2/lexers"
|
2022-12-29 10:28:27 +03:00
|
|
|
"github.com/alecthomas/chroma/v2/styles"
|
2020-10-30 10:19:13 +03:00
|
|
|
log "github.com/sirupsen/logrus"
|
2021-04-15 16:16:06 +03:00
|
|
|
"golang.org/x/term"
|
2020-10-30 10:19:13 +03:00
|
|
|
|
2019-06-10 22:50:31 +03:00
|
|
|
"github.com/walles/moar/m"
|
2024-01-07 10:38:52 +03:00
|
|
|
"github.com/walles/moar/m/linenumbers"
|
2024-01-05 11:12:17 +03:00
|
|
|
"github.com/walles/moar/m/textstyles"
|
2021-04-15 16:16:06 +03:00
|
|
|
"github.com/walles/moar/twin"
|
2019-06-09 08:47:55 +03:00
|
|
|
)
|
|
|
|
|
2024-01-13 12:20:18 +03:00
|
|
|
const defaultDarkTheme = "native"
|
|
|
|
|
|
|
|
// I decided on a light theme by doing this:
|
|
|
|
//
|
|
|
|
// wc -l ../chroma/styles/*.xml|sort|cut -d/ -f4|grep xml|xargs -I XXX grep -Hi background ../chroma/styles/XXX
|
|
|
|
//
|
|
|
|
// Then I picked tango because it has a lot of lines, a bright background
|
|
|
|
// and I like the looks of it.
|
|
|
|
const defaultLightTheme = "tango"
|
|
|
|
|
2019-07-07 19:34:05 +03:00
|
|
|
var versionString = "Should be set when building, please use build.sh to build"
|
|
|
|
|
2024-08-12 19:36:49 +03:00
|
|
|
func renderLessTermcapEnvVar(envVarName string, description string, colors twin.ColorCount) string {
|
2023-12-17 09:47:14 +03:00
|
|
|
value := os.Getenv(envVarName)
|
|
|
|
if len(value) == 0 {
|
2024-01-30 01:44:08 +03:00
|
|
|
return ""
|
2023-12-17 09:47:14 +03:00
|
|
|
}
|
|
|
|
|
2024-01-14 11:41:22 +03:00
|
|
|
style, err := m.TermcapToStyle(value)
|
|
|
|
if err != nil {
|
2024-01-15 12:27:34 +03:00
|
|
|
bold := twin.StyleDefault.WithAttr(twin.AttrBold).RenderUpdateFrom(twin.StyleDefault, colors)
|
|
|
|
notBold := twin.StyleDefault.RenderUpdateFrom(twin.StyleDefault.WithAttr(twin.AttrBold), colors)
|
2024-01-30 01:44:08 +03:00
|
|
|
return fmt.Sprintf(" %s (%s): %s %s<- Error: %v%s\n",
|
2024-01-14 11:41:22 +03:00
|
|
|
envVarName,
|
|
|
|
description,
|
|
|
|
strings.ReplaceAll(value, "\x1b", "ESC"),
|
|
|
|
bold,
|
|
|
|
err,
|
|
|
|
notBold,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-01-15 12:27:34 +03:00
|
|
|
prefix := style.RenderUpdateFrom(twin.StyleDefault, colors)
|
|
|
|
suffix := twin.StyleDefault.RenderUpdateFrom(style, colors)
|
2024-01-30 01:44:08 +03:00
|
|
|
return fmt.Sprintf(" %s (%s): %s\n",
|
2023-12-17 09:47:14 +03:00
|
|
|
envVarName,
|
|
|
|
description,
|
2024-01-14 11:41:22 +03:00
|
|
|
prefix+strings.ReplaceAll(value, "\x1b", "ESC")+suffix,
|
2023-12-17 09:47:14 +03:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-08-12 19:36:49 +03:00
|
|
|
func renderPagerEnvVar(name string, colors twin.ColorCount) string {
|
2024-01-30 01:38:20 +03:00
|
|
|
bold := twin.StyleDefault.WithAttr(twin.AttrBold).RenderUpdateFrom(twin.StyleDefault, colors)
|
|
|
|
notBold := twin.StyleDefault.RenderUpdateFrom(twin.StyleDefault.WithAttr(twin.AttrBold), colors)
|
|
|
|
|
|
|
|
value, isSet := os.LookupEnv(name)
|
|
|
|
if value == "" {
|
|
|
|
what := "unset"
|
|
|
|
if isSet {
|
|
|
|
what = "empty"
|
|
|
|
}
|
|
|
|
|
2024-01-30 01:44:08 +03:00
|
|
|
return fmt.Sprintf(" %s is %s %s<- Should be %s%s\n",
|
2024-01-30 01:38:20 +03:00
|
|
|
name,
|
|
|
|
what,
|
|
|
|
bold,
|
|
|
|
getMoarPath(),
|
|
|
|
notBold,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
absMoarPath, err := absLookPath(os.Args[0])
|
|
|
|
if err != nil {
|
|
|
|
log.Warn("Unable to find absolute moar path: ", err)
|
2024-01-30 01:44:08 +03:00
|
|
|
return ""
|
2024-01-30 01:38:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
absEnvValue, err := absLookPath(value)
|
|
|
|
if err != nil {
|
|
|
|
// This can happen if this is set to some outdated value
|
|
|
|
absEnvValue = value
|
|
|
|
}
|
|
|
|
|
|
|
|
if absEnvValue == absMoarPath {
|
2024-01-30 01:44:08 +03:00
|
|
|
return fmt.Sprintf(" %s=%s\n", name, value)
|
2024-01-30 01:38:20 +03:00
|
|
|
}
|
|
|
|
|
2024-01-30 01:44:08 +03:00
|
|
|
return fmt.Sprintf(" %s=%s %s<- Should be %s%s\n",
|
2024-01-30 01:38:20 +03:00
|
|
|
name,
|
|
|
|
value,
|
|
|
|
bold,
|
|
|
|
getMoarPath(),
|
|
|
|
notBold,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-02-10 21:00:10 +03:00
|
|
|
// If the environment variable is set, render it as APA=bepa indented two
|
|
|
|
// spaces, plus a newline at the end. Otherwise, return an empty string.
|
|
|
|
func renderPlainEnvVar(envVarName string) string {
|
|
|
|
value := os.Getenv(envVarName)
|
|
|
|
if value == "" {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf(" %s=%s\n", envVarName, value)
|
|
|
|
}
|
|
|
|
|
2024-01-11 00:49:46 +03:00
|
|
|
func printCommandline(output io.Writer) {
|
2024-01-15 12:18:09 +03:00
|
|
|
fmt.Fprintln(output, "Commandline: moar", strings.Join(os.Args[1:], " "))
|
|
|
|
fmt.Fprintf(output, "Environment: MOAR=\"%v\"\n", os.Getenv("MOAR"))
|
|
|
|
fmt.Fprintln(output)
|
2024-01-11 00:49:46 +03:00
|
|
|
}
|
|
|
|
|
2024-08-12 19:36:49 +03:00
|
|
|
func heading(text string, colors twin.ColorCount) string {
|
2024-01-15 12:47:59 +03:00
|
|
|
style := twin.StyleDefault.WithAttr(twin.AttrItalic)
|
|
|
|
prefix := style.RenderUpdateFrom(twin.StyleDefault, colors)
|
|
|
|
suffix := twin.StyleDefault.RenderUpdateFrom(style, colors)
|
|
|
|
return prefix + text + suffix
|
|
|
|
}
|
|
|
|
|
2024-08-12 19:36:49 +03:00
|
|
|
func printUsage(flagSet *flag.FlagSet, colors twin.ColorCount) {
|
2019-07-15 23:14:36 +03:00
|
|
|
// This controls where PrintDefaults() prints, see below
|
2024-01-14 20:25:10 +03:00
|
|
|
flagSet.SetOutput(os.Stdout)
|
2019-07-15 23:14:36 +03:00
|
|
|
|
2021-05-13 20:00:53 +03:00
|
|
|
// FIXME: Log if any printouts fail?
|
2024-01-11 00:49:46 +03:00
|
|
|
|
2024-01-15 12:47:59 +03:00
|
|
|
fmt.Println(heading("Usage", colors))
|
2024-01-15 12:18:09 +03:00
|
|
|
fmt.Println(" moar [options] <file>")
|
|
|
|
fmt.Println(" ... | moar")
|
|
|
|
fmt.Println(" moar < file")
|
|
|
|
fmt.Println()
|
|
|
|
fmt.Println("Shows file contents. Compressed files will be transparently decompressed.")
|
|
|
|
fmt.Println("Input is expected to be (possibly compressed) UTF-8 encoded text. Invalid /")
|
|
|
|
fmt.Println("non-printable characters are by default rendered as '?'.")
|
|
|
|
fmt.Println()
|
|
|
|
fmt.Println("More information + source code:")
|
|
|
|
fmt.Println(" <https://github.com/walles/moar#readme>")
|
|
|
|
fmt.Println()
|
2024-01-15 12:47:59 +03:00
|
|
|
fmt.Println(heading("Environment", colors))
|
2024-01-11 00:49:46 +03:00
|
|
|
|
|
|
|
moarEnv := os.Getenv("MOAR")
|
2021-06-04 07:15:09 +03:00
|
|
|
if len(moarEnv) == 0 {
|
2024-01-15 12:18:09 +03:00
|
|
|
fmt.Println(" Additional options are read from the MOAR environment variable if set.")
|
|
|
|
fmt.Println(" But currently, the MOAR environment variable is not set.")
|
2021-06-04 07:15:09 +03:00
|
|
|
} else {
|
2024-01-15 12:18:09 +03:00
|
|
|
fmt.Println(" Additional options are read from the MOAR environment variable.")
|
|
|
|
fmt.Printf(" Current setting: MOAR=\"%s\"\n", moarEnv)
|
2021-06-04 07:15:09 +03:00
|
|
|
}
|
2019-07-15 23:14:36 +03:00
|
|
|
|
2024-01-30 01:44:08 +03:00
|
|
|
envSection := ""
|
2024-02-10 21:00:10 +03:00
|
|
|
envSection += renderLessTermcapEnvVar("LESS_TERMCAP_md", "man page bold style", colors)
|
|
|
|
envSection += renderLessTermcapEnvVar("LESS_TERMCAP_us", "man page underline style", colors)
|
|
|
|
envSection += renderLessTermcapEnvVar("LESS_TERMCAP_so", "search hits and footer style", colors)
|
2023-12-17 09:47:14 +03:00
|
|
|
|
2024-01-30 01:44:08 +03:00
|
|
|
envSection += renderPagerEnvVar("PAGER", colors)
|
2024-01-30 01:38:20 +03:00
|
|
|
envVars := os.Environ()
|
|
|
|
sort.Strings(envVars)
|
|
|
|
for _, env := range envVars {
|
|
|
|
split := strings.SplitN(env, "=", 2)
|
|
|
|
if len(split) != 2 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
name := split[0]
|
|
|
|
if name == "PAGER" {
|
|
|
|
// Already done above
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if !strings.HasSuffix(name, "PAGER") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-01-30 01:44:08 +03:00
|
|
|
envSection += renderPagerEnvVar(name, colors)
|
|
|
|
}
|
|
|
|
|
2024-02-10 21:00:10 +03:00
|
|
|
envSection += renderPlainEnvVar("TERM")
|
|
|
|
envSection += renderPlainEnvVar("TERM_PROGRAM")
|
2024-02-10 21:09:42 +03:00
|
|
|
envSection += renderPlainEnvVar("COLORTERM")
|
2024-02-10 21:00:10 +03:00
|
|
|
|
|
|
|
// Requested here: https://github.com/walles/moar/issues/170#issuecomment-1891154661
|
|
|
|
envSection += renderPlainEnvVar("MANROFFOPT")
|
|
|
|
|
2024-01-30 01:44:08 +03:00
|
|
|
if envSection != "" {
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
// Not Println since the section already ends with a newline
|
|
|
|
fmt.Print(envSection)
|
2024-01-30 01:38:20 +03:00
|
|
|
}
|
|
|
|
|
2022-04-18 19:49:25 +03:00
|
|
|
absMoarPath, err := absLookPath(os.Args[0])
|
2019-07-15 23:27:41 +03:00
|
|
|
if err == nil {
|
2022-04-18 19:49:25 +03:00
|
|
|
absPagerValue, err := absLookPath(os.Getenv("PAGER"))
|
2019-07-15 23:47:36 +03:00
|
|
|
if err != nil {
|
2022-04-18 19:41:48 +03:00
|
|
|
absPagerValue = ""
|
2019-07-15 23:47:36 +03:00
|
|
|
}
|
2022-04-18 19:41:48 +03:00
|
|
|
if absPagerValue != absMoarPath {
|
2019-07-15 23:43:27 +03:00
|
|
|
// We're not the default pager
|
2024-01-15 12:18:09 +03:00
|
|
|
fmt.Println()
|
2024-01-15 12:47:59 +03:00
|
|
|
fmt.Println(heading("Making moar Your Default Pager", colors))
|
2024-01-15 12:18:09 +03:00
|
|
|
fmt.Println(" Put the following line in your ~/.bashrc, ~/.bash_profile or ~/.zshrc")
|
|
|
|
fmt.Println(" and moar will be used as the default pager in all new terminal windows:")
|
|
|
|
fmt.Println()
|
|
|
|
fmt.Printf(" export PAGER=%s\n", getMoarPath())
|
2019-07-15 23:43:27 +03:00
|
|
|
}
|
2019-07-15 23:27:41 +03:00
|
|
|
} else {
|
2021-04-19 21:29:05 +03:00
|
|
|
log.Warn("Unable to find moar binary ", err)
|
2019-07-15 23:27:41 +03:00
|
|
|
}
|
2022-04-20 08:09:42 +03:00
|
|
|
|
2024-01-15 12:18:09 +03:00
|
|
|
fmt.Println()
|
2024-01-15 12:47:59 +03:00
|
|
|
fmt.Println(heading("Options", colors))
|
2022-04-20 08:09:42 +03:00
|
|
|
|
|
|
|
flagSet.PrintDefaults()
|
2023-09-27 21:23:27 +03:00
|
|
|
|
2024-01-15 12:18:09 +03:00
|
|
|
fmt.Println(" +1234")
|
|
|
|
fmt.Println(" \tImmediately scroll to line 1234")
|
2019-07-15 23:14:36 +03:00
|
|
|
}
|
|
|
|
|
2022-04-18 20:05:49 +03:00
|
|
|
// "moar" if we're in the $PATH, otherwise an absolute path
|
|
|
|
func getMoarPath() string {
|
|
|
|
moarPath := os.Args[0]
|
|
|
|
if filepath.IsAbs(moarPath) {
|
|
|
|
return moarPath
|
|
|
|
}
|
|
|
|
|
|
|
|
if strings.Contains(moarPath, string(os.PathSeparator)) {
|
|
|
|
// Relative path
|
|
|
|
moarPath, err := filepath.Abs(moarPath)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return moarPath
|
|
|
|
}
|
|
|
|
|
|
|
|
// Neither absolute nor relative, try PATH
|
|
|
|
_, err := exec.LookPath(moarPath)
|
|
|
|
if err != nil {
|
|
|
|
panic("Unable to find in $PATH: " + moarPath)
|
|
|
|
}
|
|
|
|
return moarPath
|
|
|
|
}
|
|
|
|
|
2022-04-18 19:49:25 +03:00
|
|
|
func absLookPath(path string) (string, error) {
|
|
|
|
lookedPath, err := exec.LookPath(path)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
absLookedPath, err := filepath.Abs(lookedPath)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return absLookedPath, err
|
|
|
|
}
|
|
|
|
|
2020-12-30 00:57:44 +03:00
|
|
|
// printProblemsHeader prints bug reporting information to stderr
|
|
|
|
func printProblemsHeader() {
|
Print bug reporting info on log messages
diff --git moar.go moar.go
index 46b2898..906b310 100644
--- moar.go
+++ moar.go
@@ -58,6 +58,23 @@ func _PrintUsage(output io.Writer) {
}
}
+// PrintProblemsHeader prints bug reporting information to stderr
+func PrintProblemsHeader() {
+ fmt.Fprintln(os.Stderr, "Please post the following report at <https://github.com/walles/moar/issues>,")
+ fmt.Fprintln(os.Stderr, "or e-mail it to johan.walles@gmail.com.")
+ fmt.Fprintln(os.Stderr)
+ fmt.Fprintln(os.Stderr, "Version:", versionString)
+ fmt.Fprintln(os.Stderr, "LANG :", os.Getenv("LANG"))
+ fmt.Fprintln(os.Stderr, "TERM :", os.Getenv("TERM"))
+ fmt.Fprintln(os.Stderr)
+ fmt.Fprintln(os.Stderr, "GOOS :", runtime.GOOS)
+ fmt.Fprintln(os.Stderr, "GOARCH :", runtime.GOARCH)
+ fmt.Fprintln(os.Stderr, "Compiler:", runtime.Compiler)
+ fmt.Fprintln(os.Stderr, "NumCPU :", runtime.NumCPU())
+
+ fmt.Fprintln(os.Stderr)
+}
+
func main() {
// FIXME: If we get a CTRL-C, get terminal back into a useful state before terminating
@@ -67,21 +84,7 @@ func main() {
return
}
- // On any panic or warnings, also print system info and how to report bugs
- fmt.Fprintln(os.Stderr, "Please post the following crash report at <https://github.com/walles/moar/issues>,")
- fmt.Fprintln(os.Stderr, "or e-mail it to johan.walles@gmail.com.")
- fmt.Fprintln(os.Stderr)
- fmt.Fprintln(os.Stderr, "Version:", versionString)
- fmt.Fprintln(os.Stderr, "LANG :", os.Getenv("LANG"))
- fmt.Fprintln(os.Stderr, "TERM :", os.Getenv("TERM"))
- fmt.Fprintln(os.Stderr)
- fmt.Fprintln(os.Stderr, "GOOS :", runtime.GOOS)
- fmt.Fprintln(os.Stderr, "GOARCH :", runtime.GOARCH)
- fmt.Fprintln(os.Stderr, "Compiler:", runtime.Compiler)
- fmt.Fprintln(os.Stderr, "NumCPU :", runtime.NumCPU())
-
- fmt.Fprintln(os.Stderr)
-
+ PrintProblemsHeader()
panic(err)
}()
@@ -161,6 +164,8 @@ func _StartPaging(reader *m.Reader) {
}
if len(loglines.String()) > 0 {
+ PrintProblemsHeader()
+
// FIXME: Don't print duplicate log messages more than once,
// maybe invent our own logger for this?
fmt.Fprintf(os.Stderr, "%s", loglines.String())
Change-Id: If41c17e98daf9f05909ab7e3c31dc84e946cbbf5
2019-11-19 17:33:00 +03:00
|
|
|
fmt.Fprintln(os.Stderr, "Please post the following report at <https://github.com/walles/moar/issues>,")
|
|
|
|
fmt.Fprintln(os.Stderr, "or e-mail it to johan.walles@gmail.com.")
|
|
|
|
fmt.Fprintln(os.Stderr)
|
|
|
|
fmt.Fprintln(os.Stderr, "Version:", versionString)
|
|
|
|
fmt.Fprintln(os.Stderr, "LANG :", os.Getenv("LANG"))
|
|
|
|
fmt.Fprintln(os.Stderr, "TERM :", os.Getenv("TERM"))
|
2024-06-24 08:26:42 +03:00
|
|
|
fmt.Fprintln(os.Stderr, "MOAR :", os.Getenv("MOAR"))
|
2024-06-29 09:36:13 +03:00
|
|
|
fmt.Fprintln(os.Stderr, "EDITOR :", os.Getenv("EDITOR"))
|
Print bug reporting info on log messages
diff --git moar.go moar.go
index 46b2898..906b310 100644
--- moar.go
+++ moar.go
@@ -58,6 +58,23 @@ func _PrintUsage(output io.Writer) {
}
}
+// PrintProblemsHeader prints bug reporting information to stderr
+func PrintProblemsHeader() {
+ fmt.Fprintln(os.Stderr, "Please post the following report at <https://github.com/walles/moar/issues>,")
+ fmt.Fprintln(os.Stderr, "or e-mail it to johan.walles@gmail.com.")
+ fmt.Fprintln(os.Stderr)
+ fmt.Fprintln(os.Stderr, "Version:", versionString)
+ fmt.Fprintln(os.Stderr, "LANG :", os.Getenv("LANG"))
+ fmt.Fprintln(os.Stderr, "TERM :", os.Getenv("TERM"))
+ fmt.Fprintln(os.Stderr)
+ fmt.Fprintln(os.Stderr, "GOOS :", runtime.GOOS)
+ fmt.Fprintln(os.Stderr, "GOARCH :", runtime.GOARCH)
+ fmt.Fprintln(os.Stderr, "Compiler:", runtime.Compiler)
+ fmt.Fprintln(os.Stderr, "NumCPU :", runtime.NumCPU())
+
+ fmt.Fprintln(os.Stderr)
+}
+
func main() {
// FIXME: If we get a CTRL-C, get terminal back into a useful state before terminating
@@ -67,21 +84,7 @@ func main() {
return
}
- // On any panic or warnings, also print system info and how to report bugs
- fmt.Fprintln(os.Stderr, "Please post the following crash report at <https://github.com/walles/moar/issues>,")
- fmt.Fprintln(os.Stderr, "or e-mail it to johan.walles@gmail.com.")
- fmt.Fprintln(os.Stderr)
- fmt.Fprintln(os.Stderr, "Version:", versionString)
- fmt.Fprintln(os.Stderr, "LANG :", os.Getenv("LANG"))
- fmt.Fprintln(os.Stderr, "TERM :", os.Getenv("TERM"))
- fmt.Fprintln(os.Stderr)
- fmt.Fprintln(os.Stderr, "GOOS :", runtime.GOOS)
- fmt.Fprintln(os.Stderr, "GOARCH :", runtime.GOARCH)
- fmt.Fprintln(os.Stderr, "Compiler:", runtime.Compiler)
- fmt.Fprintln(os.Stderr, "NumCPU :", runtime.NumCPU())
-
- fmt.Fprintln(os.Stderr)
-
+ PrintProblemsHeader()
panic(err)
}()
@@ -161,6 +164,8 @@ func _StartPaging(reader *m.Reader) {
}
if len(loglines.String()) > 0 {
+ PrintProblemsHeader()
+
// FIXME: Don't print duplicate log messages more than once,
// maybe invent our own logger for this?
fmt.Fprintf(os.Stderr, "%s", loglines.String())
Change-Id: If41c17e98daf9f05909ab7e3c31dc84e946cbbf5
2019-11-19 17:33:00 +03:00
|
|
|
fmt.Fprintln(os.Stderr)
|
|
|
|
fmt.Fprintln(os.Stderr, "GOOS :", runtime.GOOS)
|
|
|
|
fmt.Fprintln(os.Stderr, "GOARCH :", runtime.GOARCH)
|
|
|
|
fmt.Fprintln(os.Stderr, "Compiler:", runtime.Compiler)
|
|
|
|
fmt.Fprintln(os.Stderr, "NumCPU :", runtime.NumCPU())
|
|
|
|
}
|
|
|
|
|
2024-01-02 11:39:47 +03:00
|
|
|
func parseLexerOption(lexerOption string) (chroma.Lexer, error) {
|
|
|
|
byMimeType := lexers.MatchMimeType(lexerOption)
|
|
|
|
if byMimeType != nil {
|
|
|
|
return byMimeType, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use Chroma's built-in fuzzy lexer picker
|
|
|
|
lexer := lexers.Get(lexerOption)
|
|
|
|
if lexer != nil {
|
|
|
|
return lexer, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, fmt.Errorf(
|
2024-01-02 17:36:45 +03:00
|
|
|
"Look here for inspiration: https://github.com/alecthomas/chroma/tree/master/lexers/embedded",
|
2024-01-02 11:39:47 +03:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-01-13 11:20:32 +03:00
|
|
|
func parseStyleOption(styleOption string) (*chroma.Style, error) {
|
2021-05-13 08:37:34 +03:00
|
|
|
style, ok := styles.Registry[styleOption]
|
|
|
|
if !ok {
|
2024-01-13 11:20:32 +03:00
|
|
|
return &chroma.Style{}, fmt.Errorf(
|
2024-01-01 15:42:42 +03:00
|
|
|
"Pick a style from here: https://xyproto.github.io/splash/docs/longer/all.html")
|
2021-05-13 08:37:34 +03:00
|
|
|
}
|
|
|
|
|
2024-01-13 11:20:32 +03:00
|
|
|
return style, nil
|
2021-05-13 08:37:34 +03:00
|
|
|
}
|
|
|
|
|
2024-08-12 19:36:49 +03:00
|
|
|
func parseColorsOption(colorsOption string) (twin.ColorCount, error) {
|
2022-04-01 21:24:00 +03:00
|
|
|
if strings.ToLower(colorsOption) == "auto" {
|
|
|
|
colorsOption = "16M"
|
2024-02-10 03:56:00 +03:00
|
|
|
if os.Getenv("COLORTERM") != "truecolor" && strings.Contains(os.Getenv("TERM"), "256") {
|
2022-04-01 21:24:00 +03:00
|
|
|
// Covers "xterm-256color" as used by the macOS Terminal
|
|
|
|
colorsOption = "256"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-13 08:37:34 +03:00
|
|
|
switch strings.ToUpper(colorsOption) {
|
|
|
|
case "8":
|
2024-08-12 19:36:49 +03:00
|
|
|
return twin.ColorCount8, nil
|
2021-05-13 08:37:34 +03:00
|
|
|
case "16":
|
2024-08-12 19:36:49 +03:00
|
|
|
return twin.ColorCount16, nil
|
2021-05-13 08:37:34 +03:00
|
|
|
case "256":
|
2024-08-12 19:36:49 +03:00
|
|
|
return twin.ColorCount256, nil
|
2021-05-13 08:37:34 +03:00
|
|
|
case "16M":
|
2024-08-12 19:36:49 +03:00
|
|
|
return twin.ColorCount24bit, nil
|
2021-05-13 08:37:34 +03:00
|
|
|
}
|
|
|
|
|
2024-08-12 19:36:49 +03:00
|
|
|
var noColor twin.ColorCount
|
2024-01-01 15:42:42 +03:00
|
|
|
return noColor, fmt.Errorf("Valid counts are 8, 16, 256, 16M or auto")
|
2021-05-13 08:37:34 +03:00
|
|
|
}
|
|
|
|
|
2023-12-19 10:47:40 +03:00
|
|
|
func parseStatusBarStyle(styleOption string) (m.StatusBarOption, error) {
|
2022-02-18 21:09:05 +03:00
|
|
|
if styleOption == "inverse" {
|
2023-03-02 08:50:45 +03:00
|
|
|
return m.STATUSBAR_STYLE_INVERSE, nil
|
2022-02-18 21:09:05 +03:00
|
|
|
}
|
|
|
|
if styleOption == "plain" {
|
2023-03-02 08:50:45 +03:00
|
|
|
return m.STATUSBAR_STYLE_PLAIN, nil
|
2022-02-18 21:09:05 +03:00
|
|
|
}
|
|
|
|
if styleOption == "bold" {
|
2023-03-02 08:50:45 +03:00
|
|
|
return m.STATUSBAR_STYLE_BOLD, nil
|
2022-02-18 21:09:05 +03:00
|
|
|
}
|
|
|
|
|
2024-01-02 17:36:45 +03:00
|
|
|
return 0, fmt.Errorf("Good ones are inverse, plain and bold")
|
2022-02-18 21:09:05 +03:00
|
|
|
}
|
|
|
|
|
2024-01-05 08:45:03 +03:00
|
|
|
func parseUnprintableStyle(styleOption string) (textstyles.UnprintableStyleT, error) {
|
2022-02-27 11:39:16 +03:00
|
|
|
if styleOption == "highlight" {
|
2024-01-05 09:02:58 +03:00
|
|
|
return textstyles.UnprintableStyleHighlight, nil
|
2022-02-27 11:39:16 +03:00
|
|
|
}
|
|
|
|
if styleOption == "whitespace" {
|
2024-01-05 09:02:58 +03:00
|
|
|
return textstyles.UnprintableStyleWhitespace, nil
|
2022-02-27 11:39:16 +03:00
|
|
|
}
|
|
|
|
|
2023-03-02 08:56:52 +03:00
|
|
|
return 0, fmt.Errorf("Good ones are highlight or whitespace")
|
2022-02-27 11:39:16 +03:00
|
|
|
}
|
|
|
|
|
2023-03-02 09:03:39 +03:00
|
|
|
func parseScrollHint(scrollHint string) (twin.Cell, error) {
|
2022-08-07 18:43:29 +03:00
|
|
|
scrollHint = strings.ReplaceAll(scrollHint, "ESC", "\x1b")
|
2023-10-06 20:03:48 +03:00
|
|
|
hintAsLine := m.NewLine(scrollHint)
|
2023-11-13 10:50:37 +03:00
|
|
|
parsedTokens := hintAsLine.HighlightedTokens("", nil, nil).Cells
|
2022-08-07 18:43:29 +03:00
|
|
|
if len(parsedTokens) == 1 {
|
2023-03-02 09:03:39 +03:00
|
|
|
return parsedTokens[0], nil
|
2022-08-07 18:43:29 +03:00
|
|
|
}
|
|
|
|
|
2023-03-02 09:03:39 +03:00
|
|
|
return twin.Cell{}, fmt.Errorf("Expected exactly one (optionally highlighted) character. For example: 'ESC[2m…'")
|
2022-08-07 18:43:29 +03:00
|
|
|
}
|
|
|
|
|
2023-03-19 13:56:54 +03:00
|
|
|
func parseShiftAmount(shiftAmount string) (uint, error) {
|
|
|
|
value, err := strconv.ParseUint(shiftAmount, 10, 32)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if value < 1 {
|
2024-01-02 17:36:45 +03:00
|
|
|
return 0, fmt.Errorf("Shift amount must be at least 1")
|
2023-03-19 13:56:54 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Let's add an upper bound as well if / when requested
|
|
|
|
|
|
|
|
return uint(value), nil
|
|
|
|
}
|
|
|
|
|
2023-12-15 23:36:29 +03:00
|
|
|
func parseMouseMode(mouseMode string) (twin.MouseMode, error) {
|
|
|
|
switch mouseMode {
|
|
|
|
case "auto":
|
|
|
|
return twin.MouseModeAuto, nil
|
2023-12-19 11:20:02 +03:00
|
|
|
case "select", "mark":
|
|
|
|
return twin.MouseModeSelect, nil
|
2023-12-15 23:36:29 +03:00
|
|
|
case "scroll":
|
|
|
|
return twin.MouseModeScroll, nil
|
|
|
|
}
|
|
|
|
|
2023-12-19 11:20:02 +03:00
|
|
|
return twin.MouseModeAuto, fmt.Errorf("Valid modes are auto, select and scroll")
|
2023-12-15 23:36:29 +03:00
|
|
|
}
|
|
|
|
|
2024-01-16 18:35:27 +03:00
|
|
|
func pumpToStdout(inputFilenames ...string) error {
|
|
|
|
if len(inputFilenames) > 0 {
|
|
|
|
// If we get both redirected stdin and an input filenames, should only
|
|
|
|
// copy the files and ignore stdin, because that's how less works.
|
|
|
|
for _, inputFilename := range inputFilenames {
|
2024-04-06 10:03:27 +03:00
|
|
|
inputFile, _, err := m.ZOpen(inputFilename)
|
2024-01-16 18:35:27 +03:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to open %s: %w", inputFilename, err)
|
|
|
|
}
|
2023-08-27 15:52:08 +03:00
|
|
|
|
2024-01-16 18:35:27 +03:00
|
|
|
_, err = io.Copy(os.Stdout, inputFile)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to copy %s to stdout: %w", inputFilename, err)
|
|
|
|
}
|
2023-08-27 15:52:08 +03:00
|
|
|
}
|
2024-01-16 18:35:27 +03:00
|
|
|
|
2023-08-27 15:52:08 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-01-16 18:35:27 +03:00
|
|
|
// No input filenames, pump stdin to stdout
|
2023-08-27 15:52:08 +03:00
|
|
|
_, err := io.Copy(os.Stdout, os.Stdin)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to copy stdin to stdout: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Duplicate of m/reader.go:tryOpen
|
|
|
|
func tryOpen(filename string) error {
|
|
|
|
// Try opening the file
|
|
|
|
tryMe, err := os.Open(filename)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try reading a byte
|
|
|
|
buffer := make([]byte, 1)
|
|
|
|
_, err = tryMe.Read(buffer)
|
|
|
|
|
|
|
|
if err != nil && err.Error() == "EOF" {
|
|
|
|
// Empty file, this is fine
|
|
|
|
err = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
closeErr := tryMe.Close()
|
|
|
|
if err == nil && closeErr != nil {
|
|
|
|
// Everything worked up until Close(), report the Close() error
|
|
|
|
return closeErr
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-09-27 21:23:27 +03:00
|
|
|
// Parses an argument like "+123" anywhere on the command line into a one-based
|
|
|
|
// line number, and returns the remaining args.
|
|
|
|
//
|
2024-01-07 10:49:44 +03:00
|
|
|
// Returns nil on no target line number specified.
|
|
|
|
func getTargetLineNumber(args []string) (*linenumbers.LineNumber, []string) {
|
2023-09-27 21:23:27 +03:00
|
|
|
for i, arg := range args {
|
|
|
|
if !strings.HasPrefix(arg, "+") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
lineNumber, err := strconv.ParseInt(arg[1:], 10, 32)
|
|
|
|
if err != nil {
|
2023-09-27 21:29:29 +03:00
|
|
|
// Let's pretend this is a file name
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if lineNumber < 1 {
|
|
|
|
// Pretend this is a file name
|
2023-09-27 21:23:27 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove the target line number from the args
|
|
|
|
//
|
|
|
|
// Ref: https://stackoverflow.com/a/57213476/473672
|
|
|
|
remainingArgs := make([]string, 0)
|
|
|
|
remainingArgs = append(remainingArgs, args[:i]...)
|
|
|
|
remainingArgs = append(remainingArgs, args[i+1:]...)
|
|
|
|
|
2024-01-07 10:49:44 +03:00
|
|
|
returnMe := linenumbers.LineNumberFromOneBased(int(lineNumber))
|
|
|
|
return &returnMe, remainingArgs
|
2023-09-27 21:23:27 +03:00
|
|
|
}
|
|
|
|
|
2024-01-07 10:49:44 +03:00
|
|
|
return nil, args
|
2023-09-27 21:23:27 +03:00
|
|
|
}
|
|
|
|
|
2024-03-10 14:19:39 +03:00
|
|
|
// On man pages, disable line numbers by default.
|
|
|
|
//
|
|
|
|
// Before paging, "man" first checks the terminal width and formats the man page
|
|
|
|
// to fit that width.
|
|
|
|
//
|
|
|
|
// Then, if moar adds line numbers, the rightmost part of the man page won't be
|
|
|
|
// visible.
|
|
|
|
//
|
|
|
|
// So we try to detect showing man pages, and in that case disable line numbers
|
|
|
|
// so that the rightmost part of the page is visible by default.
|
|
|
|
func noLineNumbersDefault() bool {
|
|
|
|
if os.Getenv("MANPATH") != "" {
|
|
|
|
// Set by "man" on macOS, skip line numbers in this case
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
if os.Getenv("MAN_PN") != "" {
|
|
|
|
// Set by "man" on Ubuntu 22.04.4 when I tested it inside of Docker,
|
|
|
|
// skip line numbers in this case
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Default to not skipping line numbers
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2024-06-21 09:14:18 +03:00
|
|
|
// Can return a nil pager on --help or --version, or if pumping to stdout.
|
2024-01-17 11:03:45 +03:00
|
|
|
func pagerFromArgs(
|
|
|
|
args []string,
|
2024-08-12 19:36:49 +03:00
|
|
|
newScreen func(mouseMode twin.MouseMode, terminalColorCount twin.ColorCount) (twin.Screen, error),
|
2024-01-17 11:03:45 +03:00
|
|
|
stdinIsRedirected bool,
|
|
|
|
stdoutIsRedirected bool,
|
|
|
|
) (
|
|
|
|
*m.Pager, twin.Screen, chroma.Style, *chroma.Formatter, error,
|
|
|
|
) {
|
2019-07-11 19:52:20 +03:00
|
|
|
// FIXME: If we get a CTRL-C, get terminal back into a useful state before terminating
|
|
|
|
|
2023-03-02 09:22:32 +03:00
|
|
|
flagSet := flag.NewFlagSet("",
|
|
|
|
flag.ContinueOnError, // We want to do our own error handling
|
|
|
|
)
|
2023-03-01 09:36:49 +03:00
|
|
|
flagSet.SetOutput(io.Discard) // We want to do our own printing
|
2023-03-02 09:22:32 +03:00
|
|
|
|
2021-05-13 20:00:53 +03:00
|
|
|
printVersion := flagSet.Bool("version", false, "Prints the moar version number")
|
|
|
|
debug := flagSet.Bool("debug", false, "Print debug logs after exiting")
|
|
|
|
trace := flagSet.Bool("trace", false, "Print trace logs after exiting")
|
2023-03-19 13:56:54 +03:00
|
|
|
|
2021-05-22 16:56:55 +03:00
|
|
|
wrap := flagSet.Bool("wrap", false, "Wrap long lines")
|
2022-11-28 21:09:17 +03:00
|
|
|
follow := flagSet.Bool("follow", false, "Follow piped input just like \"tail -f\"")
|
2024-01-13 11:20:32 +03:00
|
|
|
styleOption := flagSetFunc(flagSet,
|
|
|
|
"style", nil,
|
2024-01-15 12:47:59 +03:00
|
|
|
"Highlighting `style` from https://xyproto.github.io/splash/docs/longer/all.html", parseStyleOption)
|
2024-01-02 11:39:47 +03:00
|
|
|
lexer := flagSetFunc(flagSet,
|
|
|
|
"lang", nil,
|
2024-01-02 17:43:33 +03:00
|
|
|
"File contents, used for highlighting. Mime type or file extension (\"html\"). Default is to guess by filename.", parseLexerOption)
|
2023-10-06 23:13:26 +03:00
|
|
|
|
|
|
|
defaultFormatter, err := parseColorsOption("auto")
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Errorf("Failed parsing default formatter: %w", err))
|
|
|
|
}
|
2023-12-17 18:09:29 +03:00
|
|
|
terminalColorsCount := flagSetFunc(flagSet,
|
2023-10-06 23:13:26 +03:00
|
|
|
"colors", defaultFormatter, "Highlighting palette size: 8, 16, 256, 16M, auto", parseColorsOption)
|
|
|
|
|
2024-03-10 14:19:39 +03:00
|
|
|
noLineNumbers := flagSet.Bool("no-linenumbers", noLineNumbersDefault(), "Hide line numbers on startup, press left arrow key to show")
|
2022-07-20 23:16:33 +03:00
|
|
|
noStatusBar := flagSet.Bool("no-statusbar", false, "Hide the status bar, toggle with '='")
|
2023-01-01 14:31:18 +03:00
|
|
|
quitIfOneScreen := flagSet.Bool("quit-if-one-screen", false, "Don't page if contents fits on one screen")
|
2021-11-09 20:56:02 +03:00
|
|
|
noClearOnExit := flagSet.Bool("no-clear-on-exit", false, "Retain screen contents when exiting moar")
|
2023-03-02 08:56:52 +03:00
|
|
|
statusBarStyle := flagSetFunc(flagSet, "statusbar", m.STATUSBAR_STYLE_INVERSE,
|
2024-01-15 12:47:59 +03:00
|
|
|
"Status bar `style`: inverse, plain or bold", parseStatusBarStyle)
|
2024-01-05 09:02:58 +03:00
|
|
|
unprintableStyle := flagSetFunc(flagSet, "render-unprintable", textstyles.UnprintableStyleHighlight,
|
2023-03-02 08:56:52 +03:00
|
|
|
"How unprintable characters are rendered: highlight or whitespace", parseUnprintableStyle)
|
2023-03-02 09:03:39 +03:00
|
|
|
scrollLeftHint := flagSetFunc(flagSet, "scroll-left-hint",
|
|
|
|
twin.NewCell('<', twin.StyleDefault.WithAttr(twin.AttrReverse)),
|
|
|
|
"Shown when view can scroll left. One character with optional ANSI highlighting.", parseScrollHint)
|
2023-03-02 09:06:10 +03:00
|
|
|
scrollRightHint := flagSetFunc(flagSet, "scroll-right-hint",
|
2023-03-02 09:03:39 +03:00
|
|
|
twin.NewCell('>', twin.StyleDefault.WithAttr(twin.AttrReverse)),
|
|
|
|
"Shown when view can scroll right. One character with optional ANSI highlighting.", parseScrollHint)
|
2024-01-15 12:47:59 +03:00
|
|
|
shift := flagSetFunc(flagSet, "shift", 16, "Horizontal scroll `amount` >=1, defaults to 16", parseShiftAmount)
|
2023-12-15 23:36:29 +03:00
|
|
|
mouseMode := flagSetFunc(
|
|
|
|
flagSet,
|
|
|
|
"mousemode",
|
|
|
|
twin.MouseModeAuto,
|
2024-01-15 12:47:59 +03:00
|
|
|
"Mouse `mode`: auto, select or scroll: https://github.com/walles/moar/blob/master/MOUSE.md",
|
2023-12-15 23:36:29 +03:00
|
|
|
parseMouseMode,
|
|
|
|
)
|
2021-05-13 20:00:53 +03:00
|
|
|
|
|
|
|
// Combine flags from environment and from command line
|
2024-01-17 11:03:45 +03:00
|
|
|
flags := args[1:]
|
2021-05-13 20:00:53 +03:00
|
|
|
moarEnv := strings.Trim(os.Getenv("MOAR"), " ")
|
|
|
|
if len(moarEnv) > 0 {
|
|
|
|
// FIXME: It would be nice if we could debug log that we're doing this,
|
|
|
|
// but logging is not yet set up and depends on command line parameters.
|
2023-03-01 09:25:01 +03:00
|
|
|
flags = append(strings.Fields(moarEnv), flags...)
|
2021-05-13 20:00:53 +03:00
|
|
|
}
|
|
|
|
|
2024-01-07 10:49:44 +03:00
|
|
|
targetLineNumber, remainingArgs := getTargetLineNumber(flags)
|
2023-09-29 23:42:41 +03:00
|
|
|
|
2023-10-06 23:13:26 +03:00
|
|
|
err = flagSet.Parse(remainingArgs)
|
2021-05-13 20:00:53 +03:00
|
|
|
if err != nil {
|
2023-03-01 09:36:49 +03:00
|
|
|
if err == flag.ErrHelp {
|
2024-01-15 12:31:14 +03:00
|
|
|
printUsage(flagSet, *terminalColorsCount)
|
2024-01-17 11:03:45 +03:00
|
|
|
return nil, nil, chroma.Style{}, nil, nil
|
2023-03-01 09:36:49 +03:00
|
|
|
}
|
|
|
|
|
2024-01-11 00:49:46 +03:00
|
|
|
errorText := err.Error()
|
|
|
|
if strings.HasPrefix(errorText, "invalid value") {
|
|
|
|
errorText = strings.Replace(errorText, ": ", "\n\n", 1)
|
|
|
|
}
|
2024-01-14 20:25:10 +03:00
|
|
|
|
2024-01-11 00:49:46 +03:00
|
|
|
boldErrorMessage := "\x1b[1m" + errorText + "\x1b[m"
|
2023-03-02 09:17:05 +03:00
|
|
|
fmt.Fprintln(os.Stderr, "ERROR:", boldErrorMessage)
|
2023-03-01 09:36:49 +03:00
|
|
|
fmt.Fprintln(os.Stderr)
|
2024-01-11 00:49:46 +03:00
|
|
|
printCommandline(os.Stderr)
|
|
|
|
fmt.Fprintln(os.Stderr, "For help, run: \x1b[1mmoar --help\x1b[m")
|
|
|
|
|
2023-03-01 09:36:49 +03:00
|
|
|
os.Exit(1)
|
2019-07-15 23:14:36 +03:00
|
|
|
}
|
2019-07-07 19:18:29 +03:00
|
|
|
|
2019-07-07 19:34:05 +03:00
|
|
|
if *printVersion {
|
|
|
|
fmt.Println(versionString)
|
2024-01-17 11:03:45 +03:00
|
|
|
return nil, nil, chroma.Style{}, nil, nil
|
2019-07-07 19:18:29 +03:00
|
|
|
}
|
|
|
|
|
2024-06-23 13:20:03 +03:00
|
|
|
log.SetLevel(log.WarnLevel)
|
2021-04-15 16:16:06 +03:00
|
|
|
if *trace {
|
|
|
|
log.SetLevel(log.TraceLevel)
|
|
|
|
} else if *debug {
|
2019-12-06 21:36:31 +03:00
|
|
|
log.SetLevel(log.DebugLevel)
|
|
|
|
}
|
|
|
|
|
2021-04-06 17:22:40 +03:00
|
|
|
log.SetFormatter(&log.TextFormatter{
|
2023-05-20 08:00:05 +03:00
|
|
|
TimestampFormat: time.StampMicro,
|
2021-04-06 17:22:40 +03:00
|
|
|
})
|
|
|
|
|
2024-01-16 18:35:27 +03:00
|
|
|
if len(flagSet.Args()) > 1 && !stdoutIsRedirected {
|
2024-01-14 20:25:10 +03:00
|
|
|
fmt.Fprintln(os.Stderr, "ERROR: Expected exactly one filename, or data piped from stdin")
|
2021-04-20 10:18:02 +03:00
|
|
|
fmt.Fprintln(os.Stderr)
|
2024-01-14 20:25:10 +03:00
|
|
|
printCommandline(os.Stderr)
|
|
|
|
fmt.Fprintln(os.Stderr, "For help, run: \x1b[1mmoar --help\x1b[m")
|
2021-04-20 10:18:02 +03:00
|
|
|
|
|
|
|
os.Exit(1)
|
2019-06-09 20:34:52 +03:00
|
|
|
}
|
|
|
|
|
2024-01-16 18:35:27 +03:00
|
|
|
for _, inputFilename := range flagSet.Args() {
|
2024-01-17 11:03:45 +03:00
|
|
|
// Need to check before newScreen() below, otherwise the screen
|
2023-08-27 15:52:08 +03:00
|
|
|
// will be cleared before we print the "No such file" error.
|
2024-01-16 18:35:27 +03:00
|
|
|
err := tryOpen(inputFilename)
|
2023-08-27 15:52:08 +03:00
|
|
|
if err != nil {
|
2024-01-17 11:03:45 +03:00
|
|
|
return nil, nil, chroma.Style{}, nil, err
|
2023-08-27 15:52:08 +03:00
|
|
|
}
|
2019-06-09 22:58:12 +03:00
|
|
|
}
|
|
|
|
|
2024-01-16 18:35:27 +03:00
|
|
|
if len(flagSet.Args()) == 0 && !stdinIsRedirected {
|
2021-04-20 10:18:02 +03:00
|
|
|
fmt.Fprintln(os.Stderr, "ERROR: Filename or input pipe required")
|
2022-07-19 18:07:11 +03:00
|
|
|
fmt.Fprintln(os.Stderr)
|
2024-01-14 20:25:10 +03:00
|
|
|
printCommandline(os.Stderr)
|
|
|
|
fmt.Fprintln(os.Stderr, "For help, run: \x1b[1mmoar --help\x1b[m")
|
2019-06-09 20:40:35 +03:00
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
2023-08-27 15:52:08 +03:00
|
|
|
if stdoutIsRedirected {
|
2024-01-16 18:35:27 +03:00
|
|
|
err := pumpToStdout(flagSet.Args()...)
|
2019-06-09 22:52:27 +03:00
|
|
|
if err != nil {
|
2024-01-17 11:03:45 +03:00
|
|
|
return nil, nil, chroma.Style{}, nil, err
|
2019-06-09 22:52:27 +03:00
|
|
|
}
|
2024-01-17 11:03:45 +03:00
|
|
|
return nil, nil, chroma.Style{}, nil, nil
|
2019-06-09 20:55:49 +03:00
|
|
|
}
|
2019-06-09 22:58:12 +03:00
|
|
|
|
2023-08-27 10:02:31 +03:00
|
|
|
// INVARIANT: At this point, stdout is a terminal and we should proceed with
|
|
|
|
// paging.
|
|
|
|
stdoutIsTerminal := !stdoutIsRedirected
|
|
|
|
if !stdoutIsTerminal {
|
|
|
|
panic("Invariant broken: stdout is not a terminal")
|
|
|
|
}
|
2021-04-20 10:18:02 +03:00
|
|
|
|
2024-01-16 18:35:27 +03:00
|
|
|
if len(flagSet.Args()) > 1 {
|
|
|
|
fmt.Fprintln(os.Stderr, "ERROR: Expected exactly one filename, or data piped from stdin")
|
|
|
|
fmt.Fprintln(os.Stderr)
|
|
|
|
printCommandline(os.Stderr)
|
|
|
|
fmt.Fprintln(os.Stderr, "For help, run: \x1b[1mmoar --help\x1b[m")
|
|
|
|
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
2023-12-17 18:09:29 +03:00
|
|
|
formatter := formatters.TTY256
|
2024-08-12 19:36:49 +03:00
|
|
|
if *terminalColorsCount == twin.ColorCount8 {
|
2023-12-17 18:09:29 +03:00
|
|
|
formatter = formatters.TTY8
|
2024-08-12 19:36:49 +03:00
|
|
|
} else if *terminalColorsCount == twin.ColorCount16 {
|
2023-12-17 18:09:29 +03:00
|
|
|
formatter = formatters.TTY16
|
2024-08-12 19:36:49 +03:00
|
|
|
} else if *terminalColorsCount == twin.ColorCount24bit {
|
2024-01-10 18:48:42 +03:00
|
|
|
formatter = formatters.TTY16m
|
2023-12-17 18:09:29 +03:00
|
|
|
}
|
|
|
|
|
2024-03-17 02:33:54 +03:00
|
|
|
var reader *m.Reader
|
|
|
|
if stdinIsRedirected {
|
|
|
|
// Display input pipe contents
|
|
|
|
reader = m.NewReaderFromStreamWithoutStyle("", os.Stdin, formatter, *lexer)
|
|
|
|
} else {
|
|
|
|
// Display the input file contents
|
|
|
|
if len(flagSet.Args()) != 1 {
|
|
|
|
panic("Invariant broken: Expected exactly one filename")
|
|
|
|
}
|
|
|
|
reader, err = m.NewReaderFromFilenameWithoutStyle(flagSet.Args()[0], formatter, *lexer)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, chroma.Style{}, nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-17 21:04:34 +03:00
|
|
|
// If the user is doing "sudo something | moar" we can't show the UI until
|
|
|
|
// we start getting data, otherwise we'll mess up sudo's password prompt.
|
2024-03-17 03:04:49 +03:00
|
|
|
reader.AwaitFirstByte()
|
|
|
|
|
|
|
|
// We got the first byte, this means sudo is done (if it was used) and we
|
2024-03-17 21:04:34 +03:00
|
|
|
// can set up the UI.
|
2024-03-17 03:04:49 +03:00
|
|
|
screen, err := newScreen(*mouseMode, *terminalColorsCount)
|
|
|
|
if err != nil {
|
|
|
|
// Ref: https://github.com/walles/moar/issues/149
|
|
|
|
log.Debug("Failed to set up screen for paging, pumping to stdout instead: ", err)
|
2024-03-17 21:04:34 +03:00
|
|
|
|
|
|
|
reader.PumpToStdout()
|
|
|
|
|
2024-03-17 03:04:49 +03:00
|
|
|
return nil, nil, chroma.Style{}, nil, nil
|
|
|
|
}
|
|
|
|
|
2024-04-29 21:35:06 +03:00
|
|
|
var style chroma.Style = *styles.Get(defaultDarkTheme)
|
2024-01-13 13:52:01 +03:00
|
|
|
if *styleOption == nil {
|
2024-01-13 14:36:26 +03:00
|
|
|
t0 := time.Now()
|
2024-01-20 10:34:59 +03:00
|
|
|
screen.RequestTerminalBackgroundColor()
|
2024-01-13 14:36:26 +03:00
|
|
|
select {
|
|
|
|
case event := <-screen.Events():
|
|
|
|
// Event received, let's see if it's the one we want
|
|
|
|
switch ev := event.(type) {
|
|
|
|
|
|
|
|
case twin.EventTerminalBackgroundDetected:
|
|
|
|
log.Debug("Terminal background color detected as ", ev.Color, " after ", time.Since(t0))
|
|
|
|
|
|
|
|
distanceToBlack := ev.Color.Distance(twin.NewColor24Bit(0, 0, 0))
|
|
|
|
distanceToWhite := ev.Color.Distance(twin.NewColor24Bit(255, 255, 255))
|
|
|
|
if distanceToBlack < distanceToWhite {
|
|
|
|
style = *styles.Get(defaultDarkTheme)
|
|
|
|
} else {
|
|
|
|
style = *styles.Get(defaultLightTheme)
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
log.Debug("Expected terminal background color event but got ", ev, " after ", time.Since(t0), " putting back and giving up")
|
|
|
|
screen.Events() <- event
|
|
|
|
}
|
|
|
|
|
2024-01-20 10:41:44 +03:00
|
|
|
// The worst number I have measured was around 15ms, in GNOME Terminal
|
|
|
|
// running inside of VirtualBox. 3x that should be enough for everyone
|
|
|
|
// (TM).
|
2024-01-19 12:54:17 +03:00
|
|
|
case <-time.After(50 * time.Millisecond):
|
2024-01-13 14:39:49 +03:00
|
|
|
log.Debug("Terminal background color still not detected after ", time.Since(t0), ", giving up")
|
2024-01-13 13:52:01 +03:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
style = **styleOption
|
|
|
|
}
|
2024-04-29 21:35:06 +03:00
|
|
|
log.Debug("Using style <", style.Name, ">")
|
2024-03-17 02:33:54 +03:00
|
|
|
reader.SetStyleForHighlighting(style)
|
2021-04-20 10:18:02 +03:00
|
|
|
|
2023-01-03 01:07:04 +03:00
|
|
|
pager := m.NewPager(reader)
|
|
|
|
pager.WrapLongLines = *wrap
|
|
|
|
pager.ShowLineNumbers = !*noLineNumbers
|
|
|
|
pager.ShowStatusBar = !*noStatusBar
|
|
|
|
pager.DeInit = !*noClearOnExit
|
2023-01-01 14:31:18 +03:00
|
|
|
pager.QuitIfOneScreen = *quitIfOneScreen
|
2023-03-02 08:50:45 +03:00
|
|
|
pager.StatusBarStyle = *statusBarStyle
|
2023-03-02 08:56:52 +03:00
|
|
|
pager.UnprintableStyle = *unprintableStyle
|
2023-03-02 09:03:39 +03:00
|
|
|
pager.ScrollLeftHint = *scrollLeftHint
|
|
|
|
pager.ScrollRightHint = *scrollRightHint
|
2023-03-19 13:56:54 +03:00
|
|
|
pager.SideScrollAmount = int(*shift)
|
2023-09-27 21:23:27 +03:00
|
|
|
|
2024-01-07 10:49:44 +03:00
|
|
|
pager.TargetLineNumber = targetLineNumber
|
|
|
|
if *follow && pager.TargetLineNumber == nil {
|
|
|
|
reallyHigh := linenumbers.LineNumberMax()
|
|
|
|
pager.TargetLineNumber = &reallyHigh
|
2023-09-27 21:23:27 +03:00
|
|
|
}
|
|
|
|
|
2024-01-17 11:03:45 +03:00
|
|
|
return pager, screen, style, &formatter, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
var loglines strings.Builder
|
|
|
|
log.SetOutput(&loglines)
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
err := recover()
|
|
|
|
if len(loglines.String()) == 0 && err == nil {
|
|
|
|
// No problems
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
printProblemsHeader()
|
|
|
|
|
|
|
|
if len(loglines.String()) > 0 {
|
|
|
|
fmt.Fprintln(os.Stderr)
|
|
|
|
// Consider not printing duplicate log messages more than once
|
|
|
|
fmt.Fprintf(os.Stderr, "%s", loglines.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintln(os.Stderr)
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
os.Exit(1)
|
|
|
|
}()
|
|
|
|
|
|
|
|
stdinIsRedirected := !term.IsTerminal(int(os.Stdin.Fd()))
|
|
|
|
stdoutIsRedirected := !term.IsTerminal(int(os.Stdout.Fd()))
|
|
|
|
|
|
|
|
pager, screen, style, formatter, err := pagerFromArgs(
|
|
|
|
os.Args,
|
2024-08-12 19:53:44 +03:00
|
|
|
twin.NewScreenWithMouseModeAndColorCount,
|
2024-01-17 11:03:45 +03:00
|
|
|
stdinIsRedirected,
|
|
|
|
stdoutIsRedirected,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintln(os.Stderr, "ERROR:", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
2024-06-21 09:14:18 +03:00
|
|
|
if pager == nil {
|
|
|
|
// No pager, we're done
|
|
|
|
return
|
2024-01-17 11:03:45 +03:00
|
|
|
}
|
2024-06-21 09:14:18 +03:00
|
|
|
|
|
|
|
startPaging(pager, screen, &style, formatter)
|
2019-06-16 11:02:19 +03:00
|
|
|
}
|
2019-06-15 09:10:56 +03:00
|
|
|
|
2023-03-01 09:25:01 +03:00
|
|
|
// Define a generic flag with specified name, default value, and usage string.
|
|
|
|
// The return value is the address of a variable that stores the parsed value of
|
|
|
|
// the flag.
|
|
|
|
func flagSetFunc[T any](flagSet *flag.FlagSet, name string, defaultValue T, usage string, parser func(valueString string) (T, error)) *T {
|
|
|
|
parsed := defaultValue
|
|
|
|
|
|
|
|
flagSet.Func(name, usage, func(valueString string) error {
|
|
|
|
parseResult, err := parser(valueString)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
parsed = parseResult
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
return &parsed
|
|
|
|
}
|
|
|
|
|
2023-12-17 18:09:29 +03:00
|
|
|
func startPaging(pager *m.Pager, screen twin.Screen, chromaStyle *chroma.Style, chromaFormatter *chroma.Formatter) {
|
2019-06-29 23:27:18 +03:00
|
|
|
defer func() {
|
|
|
|
// Restore screen...
|
2023-08-27 15:52:08 +03:00
|
|
|
screen.Close()
|
2019-06-29 23:27:18 +03:00
|
|
|
|
2023-08-27 15:52:08 +03:00
|
|
|
// ... before printing any panic() output, otherwise the output will
|
|
|
|
// have broken linefeeds and be hard to follow.
|
2019-06-29 23:27:18 +03:00
|
|
|
if err := recover(); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2023-01-03 01:07:04 +03:00
|
|
|
if !pager.DeInit {
|
2021-11-09 20:56:02 +03:00
|
|
|
err := pager.ReprintAfterExit()
|
|
|
|
if err != nil {
|
2024-06-21 10:17:15 +03:00
|
|
|
log.Error("Failed reprinting pager view after exit: ", err)
|
2021-11-09 20:56:02 +03:00
|
|
|
}
|
|
|
|
}
|
2024-06-21 09:14:18 +03:00
|
|
|
|
|
|
|
if pager.AfterExit != nil {
|
|
|
|
err := pager.AfterExit()
|
|
|
|
if err != nil {
|
2024-06-21 10:17:15 +03:00
|
|
|
log.Error("Failed running AfterExit hook: ", err)
|
2024-06-21 09:14:18 +03:00
|
|
|
}
|
|
|
|
}
|
2019-06-30 23:11:26 +03:00
|
|
|
}()
|
2019-06-15 09:10:56 +03:00
|
|
|
|
2023-12-17 18:09:29 +03:00
|
|
|
pager.StartPaging(screen, chromaStyle, chromaFormatter)
|
2019-06-09 08:47:55 +03:00
|
|
|
}
|