mirror of
https://github.com/makew0rld/amfora.git
synced 2024-06-01 18:31:08 +00:00
Merge branch 'commands' of github.com:mntn-xyz/amfora into commands
This commit is contained in:
commit
416f4b300c
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
- Syntax highlighting for preformatted text blocks with alt text (#252, #263, [wiki page](https://github.com/makeworld-the-better-one/amfora/wiki/Source-Code-Highlighting))
|
||||
|
||||
### Changed
|
||||
- Center text automatically, removing `left_margin` from the config (#233)
|
||||
- `max_width` defaults to 80 columns instead of 100 (#233)
|
||||
|
||||
### Fixed
|
||||
- Modal can't be closed when opening non-gemini text URLs from the commandline (#283, #284)
|
||||
- External programs started by Amfora remain as zombie processes (#219)
|
||||
- Prevent link lines (and other types) from being wider than the `max_width` setting (#280)
|
||||
|
||||
|
||||
## [1.9.2] - 2021-12-10
|
||||
### Fixed
|
||||
- Preformatted text color showing even when `color = false` (bug since v1.8.0 at least) (#278)
|
||||
|
|
|
@ -142,7 +142,8 @@ Features in *italics* are in the master branch, but not in the latest release.
|
|||
- So is subscribing to a page, to know when it changes
|
||||
- [x] Open non-text files in another application
|
||||
- [x] Ability to stream content instead of downloading it first
|
||||
- [x] Run custom commands using the current or selected URL as an argument
|
||||
- [x] *Highlighting of preformatted code blocks that list a language in the alt text*
|
||||
- [x] *Run custom commands using the current or selected URL as an argument*
|
||||
- [ ] Stream support
|
||||
- [ ] Table of contents for pages
|
||||
- [ ] Search in pages with <kbd>Ctrl-F</kbd>
|
||||
|
@ -163,6 +164,7 @@ Amfora ❤️ open source!
|
|||
- [progressbar](https://github.com/schollz/progressbar)
|
||||
- [go-humanize](https://github.com/dustin/go-humanize)
|
||||
- [gofeed](https://github.com/mmcdole/gofeed)
|
||||
- [chroma](https://github.com/alecthomas/chroma) for source code syntax highlighting
|
||||
- [clipboard](https://github.com/atotto/clipboard)
|
||||
- [termenv](https://github.com/muesli/termenv)
|
||||
|
||||
|
|
|
@ -25,3 +25,4 @@ Thank you to the following contributors, who have helped make Amfora great. FOSS
|
|||
* Michael McDonagh (@m-mcdonagh)
|
||||
* mooff (@awfulcooking)
|
||||
* Josias (@justjosias)
|
||||
* mntn (@mntn-xyz)
|
||||
|
|
|
@ -196,10 +196,11 @@ func Init() error {
|
|||
viper.SetDefault("a-general.search", "gemini://geminispace.info/search")
|
||||
viper.SetDefault("a-general.color", true)
|
||||
viper.SetDefault("a-general.ansi", true)
|
||||
viper.SetDefault("a-general.highlight_code", true)
|
||||
viper.SetDefault("a-general.highlight_style", "monokai")
|
||||
viper.SetDefault("a-general.bullets", true)
|
||||
viper.SetDefault("a-general.show_link", false)
|
||||
viper.SetDefault("a-general.left_margin", 0.15)
|
||||
viper.SetDefault("a-general.max_width", 100)
|
||||
viper.SetDefault("a-general.max_width", 80)
|
||||
viper.SetDefault("a-general.downloads", "")
|
||||
viper.SetDefault("a-general.temp_downloads", "")
|
||||
viper.SetDefault("a-general.page_max_size", 2097152)
|
||||
|
|
|
@ -58,17 +58,20 @@ color = true
|
|||
# Whether ANSI color codes from the page content should be rendered
|
||||
ansi = true
|
||||
|
||||
# Whether or not to support source code highlighting in preformatted blocks based on alt text
|
||||
highlight_code = true
|
||||
|
||||
# Which highlighting style to use (see https://xyproto.github.io/splash/docs/)
|
||||
highlight_style = "monokai"
|
||||
|
||||
# Whether to replace list asterisks with unicode bullets
|
||||
bullets = true
|
||||
|
||||
# Whether to show link after link text
|
||||
show_link = false
|
||||
|
||||
# A number from 0 to 1, indicating what percentage of the terminal width the left margin should take up.
|
||||
left_margin = 0.15
|
||||
|
||||
# The max number of columns to wrap a page's text to. Preformatted blocks are not wrapped.
|
||||
max_width = 100
|
||||
max_width = 80
|
||||
|
||||
# 'downloads' is the path to a downloads folder.
|
||||
# An empty value means the code will find the default downloads folder for your system.
|
||||
|
|
|
@ -55,17 +55,20 @@ color = true
|
|||
# Whether ANSI color codes from the page content should be rendered
|
||||
ansi = true
|
||||
|
||||
# Whether or not to support source code highlighting in preformatted blocks based on alt text
|
||||
highlight_code = true
|
||||
|
||||
# Which highlighting style to use (see https://xyproto.github.io/splash/docs/)
|
||||
highlight_style = "monokai"
|
||||
|
||||
# Whether to replace list asterisks with unicode bullets
|
||||
bullets = true
|
||||
|
||||
# Whether to show link after link text
|
||||
show_link = false
|
||||
|
||||
# A number from 0 to 1, indicating what percentage of the terminal width the left margin should take up.
|
||||
left_margin = 0.15
|
||||
|
||||
# The max number of columns to wrap a page's text to. Preformatted blocks are not wrapped.
|
||||
max_width = 100
|
||||
max_width = 80
|
||||
|
||||
# 'downloads' is the path to a downloads folder.
|
||||
# An empty value means the code will find the default downloads folder for your system.
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/makeworld-the-better-one/amfora/renderer"
|
||||
"github.com/makeworld-the-better-one/amfora/structs"
|
||||
"github.com/makeworld-the-better-one/go-gemini"
|
||||
"github.com/muesli/termenv"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
|
@ -60,6 +61,18 @@ var App = cview.NewApplication()
|
|||
func Init(version, commit, builtBy string) {
|
||||
aboutInit(version, commit, builtBy)
|
||||
|
||||
// Detect terminal colors for syntax highlighting
|
||||
switch termenv.ColorProfile() {
|
||||
case termenv.TrueColor:
|
||||
renderer.TermColor = "terminal16m"
|
||||
case termenv.ANSI256:
|
||||
renderer.TermColor = "terminal256"
|
||||
case termenv.ANSI:
|
||||
renderer.TermColor = "terminal16"
|
||||
case termenv.Ascii:
|
||||
renderer.TermColor = ""
|
||||
}
|
||||
|
||||
App.EnableMouse(false)
|
||||
App.SetRoot(layout, true)
|
||||
App.SetAfterResizeFunc(func(width int, height int) {
|
||||
|
@ -228,7 +241,7 @@ func Init(version, commit, builtBy string) {
|
|||
}
|
||||
if i <= len(tabs[tab].page.Links) && i > 0 {
|
||||
// It's a valid link number
|
||||
followLink(tabs[tab], tabs[tab].page.URL, tabs[tab].page.Links[i-1])
|
||||
go followLink(tabs[tab], tabs[tab].page.URL, tabs[tab].page.Links[i-1])
|
||||
return
|
||||
}
|
||||
// Invalid link number, don't do anything
|
||||
|
|
|
@ -191,6 +191,8 @@ func open(u string, resp *gemini.Response) {
|
|||
Error("File Opening Error", "Error executing custom command: "+err.Error())
|
||||
return
|
||||
}
|
||||
//nolint:errcheck
|
||||
go proc.Wait() // Prevent zombies, see #219
|
||||
Info("Opened with " + cmd[0])
|
||||
return
|
||||
}
|
||||
|
@ -214,11 +216,14 @@ func open(u string, resp *gemini.Response) {
|
|||
Info("Opened in default system viewer")
|
||||
} else {
|
||||
cmd := mediaHandler.Cmd
|
||||
err := exec.Command(cmd[0], append(cmd[1:], path)...).Start()
|
||||
proc := exec.Command(cmd[0], append(cmd[1:], path)...)
|
||||
err := proc.Start()
|
||||
if err != nil {
|
||||
Error("File Opening Error", "Error executing custom command: "+err.Error())
|
||||
return
|
||||
}
|
||||
//nolint:errcheck
|
||||
go proc.Wait() // Prevent zombies, see #219
|
||||
Info("Opened with " + cmd[0])
|
||||
}
|
||||
App.Draw()
|
||||
|
|
|
@ -48,16 +48,19 @@ func handleHTTP(u string, showInfo bool) bool {
|
|||
}
|
||||
|
||||
// Custom command
|
||||
var err error
|
||||
var proc *exec.Cmd
|
||||
if len(config.HTTPCommand) > 1 {
|
||||
err = exec.Command(config.HTTPCommand[0], append(config.HTTPCommand[1:], u)...).Start()
|
||||
proc = exec.Command(config.HTTPCommand[0], append(config.HTTPCommand[1:], u)...)
|
||||
} else {
|
||||
err = exec.Command(config.HTTPCommand[0], u).Start()
|
||||
proc = exec.Command(config.HTTPCommand[0], u)
|
||||
}
|
||||
err := proc.Start()
|
||||
if err != nil {
|
||||
Error("HTTP Error", "Error executing custom browser command: "+err.Error())
|
||||
return false
|
||||
}
|
||||
//nolint:errcheck
|
||||
go proc.Wait() // Prevent zombies, see #219
|
||||
Info("Opened with: " + config.HTTPCommand[0])
|
||||
|
||||
App.Draw()
|
||||
|
@ -104,15 +107,18 @@ func handleOther(u string) {
|
|||
|
||||
// Custom application command
|
||||
|
||||
var err error
|
||||
var proc *exec.Cmd
|
||||
if len(handler) > 1 {
|
||||
err = exec.Command(handler[0], append(handler[1:], u)...).Start()
|
||||
proc = exec.Command(handler[0], append(handler[1:], u)...)
|
||||
} else {
|
||||
err = exec.Command(handler[0], u).Start()
|
||||
proc = exec.Command(handler[0], u)
|
||||
}
|
||||
err := proc.Start()
|
||||
if err != nil {
|
||||
Error("URL Error", "Error executing custom command: "+err.Error())
|
||||
}
|
||||
//nolint:errcheck
|
||||
go proc.Wait() // Prevent zombies, see #219
|
||||
Info("Opened with: " + handler[0])
|
||||
App.Draw()
|
||||
}
|
||||
|
@ -352,7 +358,7 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||
// Disable read timeout and go back to start
|
||||
res.SetReadTimeout(0) //nolint: errcheck
|
||||
res.Body.(*rr.RestartReader).Restart()
|
||||
go dlChoice("That page is too large. What would you like to do?", u, res)
|
||||
dlChoice("That page is too large. What would you like to do?", u, res)
|
||||
return ret("", false)
|
||||
}
|
||||
if errors.Is(err, renderer.ErrTimedOut) {
|
||||
|
@ -360,7 +366,7 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||
// Disable read timeout and go back to start
|
||||
res.SetReadTimeout(0) //nolint: errcheck
|
||||
res.Body.(*rr.RestartReader).Restart()
|
||||
go dlChoice("Loading that page timed out. What would you like to do?", u, res)
|
||||
dlChoice("Loading that page timed out. What would you like to do?", u, res)
|
||||
return ret("", false)
|
||||
}
|
||||
if err != nil {
|
||||
|
@ -493,7 +499,7 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||
// Disable read timeout and go back to start
|
||||
res.SetReadTimeout(0) //nolint: errcheck
|
||||
res.Body.(*rr.RestartReader).Restart()
|
||||
go dlChoice("That file could not be displayed. What would you like to do?", u, res)
|
||||
dlChoice("That file could not be displayed. What would you like to do?", u, res)
|
||||
}
|
||||
}()
|
||||
return ret("", false)
|
||||
|
@ -503,6 +509,6 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||
// Disable read timeout and go back to start
|
||||
res.SetReadTimeout(0) //nolint: errcheck
|
||||
res.Body.(*rr.RestartReader).Restart()
|
||||
go dlChoice("That file could not be displayed. What would you like to do?", u, res)
|
||||
dlChoice("That file could not be displayed. What would you like to do?", u, res)
|
||||
return ret("", false)
|
||||
}
|
||||
|
|
|
@ -16,24 +16,21 @@ import (
|
|||
// The bookmark modal is in bookmarks.go
|
||||
|
||||
var infoModal = cview.NewModal()
|
||||
|
||||
var errorModal = cview.NewModal()
|
||||
var errorModalDone = make(chan struct{})
|
||||
|
||||
var inputModal = cview.NewModal()
|
||||
var inputCh = make(chan string)
|
||||
var inputModalText string // The current text of the input field in the modal
|
||||
|
||||
var yesNoModal = cview.NewModal()
|
||||
|
||||
// Channel to receive yesNo answer on
|
||||
var inputCh = make(chan string)
|
||||
var yesNoCh = make(chan bool)
|
||||
|
||||
var inputModalText string // The current text of the input field in the modal
|
||||
|
||||
// Internal channel used to know when a modal has been dismissed
|
||||
var modalDone = make(chan struct{})
|
||||
|
||||
func modalInit() {
|
||||
infoModal.AddButtons([]string{"Ok"})
|
||||
|
||||
errorModal.AddButtons([]string{"Ok"})
|
||||
|
||||
yesNoModal.AddButtons([]string{"Yes", "No"})
|
||||
|
||||
panels.AddPanel(PanelInfoModal, infoModal, false, false)
|
||||
|
@ -145,6 +142,7 @@ func modalInit() {
|
|||
panels.HidePanel(PanelInfoModal)
|
||||
App.SetFocus(tabs[curTab].view)
|
||||
App.Draw()
|
||||
modalDone <- struct{}{}
|
||||
})
|
||||
|
||||
errorModal.SetBorder(true)
|
||||
|
@ -153,7 +151,7 @@ func modalInit() {
|
|||
panels.HidePanel(PanelErrorModal)
|
||||
App.SetFocus(tabs[curTab].view)
|
||||
App.Draw()
|
||||
errorModalDone <- struct{}{}
|
||||
modalDone <- struct{}{}
|
||||
})
|
||||
|
||||
inputModal.SetBorder(true)
|
||||
|
@ -183,7 +181,7 @@ func modalInit() {
|
|||
dlInit()
|
||||
}
|
||||
|
||||
// Error displays an error on the screen in a modal.
|
||||
// Error displays an error on the screen in a modal, and blocks until dismissed by the user.
|
||||
func Error(title, text string) {
|
||||
if text == "" {
|
||||
text = "No additional information."
|
||||
|
@ -203,19 +201,21 @@ func Error(title, text string) {
|
|||
App.SetFocus(errorModal)
|
||||
App.Draw()
|
||||
|
||||
<-errorModalDone
|
||||
<-modalDone
|
||||
}
|
||||
|
||||
// Info displays some info on the screen in a modal.
|
||||
// Info displays some info on the screen in a modal, and blocks until dismissed by the user.
|
||||
func Info(s string) {
|
||||
infoModal.SetText(s)
|
||||
panels.ShowPanel(PanelInfoModal)
|
||||
panels.SendToFront(PanelInfoModal)
|
||||
App.SetFocus(infoModal)
|
||||
App.Draw()
|
||||
|
||||
<-modalDone
|
||||
}
|
||||
|
||||
// Input pulls up a modal that asks for input, and returns the user's input.
|
||||
// Input pulls up a modal that asks for input, waits for that input, and returns it.
|
||||
// It returns an bool indicating if the user chose to send input or not.
|
||||
func Input(prompt string, sensitive bool) (string, bool) {
|
||||
// Remove elements and re-add them - to clear input text and keep input in focus
|
||||
|
@ -257,7 +257,7 @@ func Input(prompt string, sensitive bool) (string, bool) {
|
|||
return resp, true
|
||||
}
|
||||
|
||||
// YesNo displays a modal asking a yes-or-no question.
|
||||
// YesNo displays a modal asking a yes-or-no question, waits for an answer, then returns it as a bool.
|
||||
func YesNo(prompt string) bool {
|
||||
if viper.GetBool("a-general.color") {
|
||||
m := yesNoModal
|
||||
|
@ -289,7 +289,7 @@ func YesNo(prompt string) bool {
|
|||
}
|
||||
|
||||
// Tofu displays the TOFU warning modal.
|
||||
// It returns a bool indicating whether the user wants to continue.
|
||||
// It blocks then returns a bool indicating whether the user wants to continue.
|
||||
func Tofu(host string, expiry time.Time) bool {
|
||||
// Reuses yesNoModal, with error color
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package display
|
||||
|
||||
// This file contains the functions that aren't part of the public API.
|
||||
// The funcs are for network and displaying.
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
@ -9,17 +12,20 @@ import (
|
|||
"github.com/makeworld-the-better-one/amfora/structs"
|
||||
)
|
||||
|
||||
// This file contains the functions that aren't part of the public API.
|
||||
// The funcs are for network and displaying.
|
||||
|
||||
// followLink should be used when the user "clicks" a link on a page.
|
||||
// Not when a URL is opened on a new tab for the first time.
|
||||
// It will handle setting the bottomBar.
|
||||
// followLink should be used when the user "clicks" a link on a page,
|
||||
// but not when a URL is opened on a new tab for the first time.
|
||||
//
|
||||
// It will handle updating the bottomBar.
|
||||
//
|
||||
// It should be called with the `go` keyword to spawn a new goroutine if
|
||||
// it would otherwise block the UI loop, such as when called from an input
|
||||
// handler.
|
||||
//
|
||||
// It blocks until navigation is finished, and we've completed any user
|
||||
// interaction related to loading the URL (such as info, error modals)
|
||||
func followLink(t *tab, prev, next string) {
|
||||
if strings.HasPrefix(next, "about:") {
|
||||
if final, ok := handleAbout(t, next); ok {
|
||||
t.addToHistory(final)
|
||||
}
|
||||
goURL(t, next)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -29,7 +35,7 @@ func followLink(t *tab, prev, next string) {
|
|||
Error("URL Error", err.Error())
|
||||
return
|
||||
}
|
||||
go goURL(t, nextURL)
|
||||
goURL(t, nextURL)
|
||||
return
|
||||
}
|
||||
// No content on current tab, so the "prev" URL is not valid.
|
||||
|
@ -39,7 +45,7 @@ func followLink(t *tab, prev, next string) {
|
|||
Error("URL Error", "Link URL could not be parsed")
|
||||
return
|
||||
}
|
||||
go goURL(t, next)
|
||||
goURL(t, next)
|
||||
}
|
||||
|
||||
// reformatPage will take the raw page content and reformat it according to the current terminal dimensions.
|
||||
|
|
|
@ -86,7 +86,7 @@ func makeNewTab() *tab {
|
|||
linkN, _ := strconv.Atoi(currentSelection[0])
|
||||
tabs[tab].page.Selected = tabs[tab].page.Links[linkN]
|
||||
tabs[tab].page.SelectedID = currentSelection[0]
|
||||
followLink(tabs[tab], tabs[tab].page.URL, tabs[tab].page.Links[linkN])
|
||||
go followLink(tabs[tab], tabs[tab].page.URL, tabs[tab].page.Links[linkN])
|
||||
return
|
||||
}
|
||||
if len(currentSelection) == 0 && (key == tcell.KeyEnter || key == tcell.KeyTab) {
|
||||
|
@ -156,12 +156,12 @@ func makeNewTab() *tab {
|
|||
if t.hasContent() {
|
||||
savePath, err := downloadPage(t.page)
|
||||
if err != nil {
|
||||
Error("Download Error", fmt.Sprintf("Error saving page content: %v", err))
|
||||
go Error("Download Error", fmt.Sprintf("Error saving page content: %v", err))
|
||||
} else {
|
||||
Info(fmt.Sprintf("Page content saved to %s. ", savePath))
|
||||
go Info(fmt.Sprintf("Page content saved to %s. ", savePath))
|
||||
}
|
||||
} else {
|
||||
Info("The current page has no content, so it couldn't be downloaded.")
|
||||
go Info("The current page has no content, so it couldn't be downloaded.")
|
||||
}
|
||||
return nil
|
||||
case config.CmdBack:
|
||||
|
@ -178,7 +178,7 @@ func makeNewTab() *tab {
|
|||
currentURL := tabs[curTab].page.URL
|
||||
err := clipboard.WriteAll(currentURL)
|
||||
if err != nil {
|
||||
Error("Copy Error", err.Error())
|
||||
go Error("Copy Error", err.Error())
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
|
@ -193,14 +193,14 @@ func makeNewTab() *tab {
|
|||
if err != nil {
|
||||
err := clipboard.WriteAll(selectedURL)
|
||||
if err != nil {
|
||||
Error("Copy Error", err.Error())
|
||||
go Error("Copy Error", err.Error())
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
err = clipboard.WriteAll(copiedURL.String())
|
||||
if err != nil {
|
||||
Error("Copy Error", err.Error())
|
||||
go Error("Copy Error", err.Error())
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
|
@ -209,7 +209,7 @@ func makeNewTab() *tab {
|
|||
if cmd >= config.CmdLink1 && cmd <= config.CmdLink0 {
|
||||
if int(cmd) <= len(t.page.Links) {
|
||||
// It's a valid link number
|
||||
followLink(&t, t.page.URL, t.page.Links[cmd-1])
|
||||
go followLink(&t, t.page.URL, t.page.Links[cmd-1])
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,4 +29,5 @@ Thank you to the following contributors, who have helped make Amfora great. FOSS
|
|||
* Michael McDonagh (@m-mcdonagh)
|
||||
* mooff (@awfulcooking)
|
||||
* Josias (@justjosias)
|
||||
* mntn (@mntn-xyz)
|
||||
`)
|
||||
|
|
|
@ -63,7 +63,14 @@ func isValidTab(t *tab) bool {
|
|||
}
|
||||
|
||||
func leftMargin() int {
|
||||
return int(float64(termW) * viper.GetFloat64("a-general.left_margin"))
|
||||
// Return the left margin size that centers the text, assuming it's the max width
|
||||
// https://github.com/makeworld-the-better-one/amfora/issues/233
|
||||
|
||||
lm := (termW - viper.GetInt("a-general.max_width")) / 2
|
||||
if lm < 0 {
|
||||
return 0
|
||||
}
|
||||
return lm
|
||||
}
|
||||
|
||||
func textWidth() int {
|
||||
|
@ -73,13 +80,11 @@ func textWidth() int {
|
|||
return viper.GetInt("a-general.max_width")
|
||||
}
|
||||
|
||||
rightMargin := leftMargin()
|
||||
if leftMargin() > 10 {
|
||||
// 10 is the max right margin
|
||||
rightMargin = 10
|
||||
}
|
||||
// Subtract left and right margin from total width to get text width
|
||||
// Left and right margin are equal because text is automatically centered, see:
|
||||
// https://github.com/makeworld-the-better-one/amfora/issues/233
|
||||
|
||||
max := termW - leftMargin() - rightMargin
|
||||
max := termW - leftMargin()*2
|
||||
if max < viper.GetInt("a-general.max_width") {
|
||||
return max
|
||||
}
|
||||
|
|
1
go.mod
1
go.mod
|
@ -4,6 +4,7 @@ go 1.15
|
|||
|
||||
require (
|
||||
code.rocketnine.space/tslocum/cview v1.5.6-0.20210530175404-7e8817f20bdc
|
||||
github.com/alecthomas/chroma v0.9.2
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
|
|
20
go.sum
20
go.sum
|
@ -21,6 +21,15 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
|
|||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
|
||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
||||
github.com/alecthomas/chroma v0.9.2 h1:yU1sE2+TZbLIQPMk30SolL2Hn53SR/Pv750f7qZ/XMs=
|
||||
github.com/alecthomas/chroma v0.9.2/go.mod h1:eMuEnpA18XbG/WhOWtCzJHS7WqEtDAI+HxdwoW0nVSk=
|
||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
|
||||
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 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
|
||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
|
||||
|
@ -42,11 +51,15 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
|
|||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
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.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
|
@ -141,7 +154,9 @@ github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
|
|||
github.com/makeworld-the-better-one/go-gemini v0.12.1 h1:cWHvCHL31Caq3Rm9elCFFoQeyrn92Kv7KummsVxCOFg=
|
||||
github.com/makeworld-the-better-one/go-gemini v0.12.1/go.mod h1:F+3x+R1xeYK90jMtBq+U+8Sh64r2dHleDZ/en3YgSmg=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
|
||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
|
@ -183,6 +198,7 @@ github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMF
|
|||
github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
|
@ -207,6 +223,8 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
|
|||
github.com/schollz/progressbar/v3 v3.8.0 h1:BKyefEMgFBDbo+JaeqHcm/9QdSj8qG8sUY+6UppGpnw=
|
||||
github.com/schollz/progressbar/v3 v3.8.0/go.mod h1:Y9mmL2knZj3LUaBDyBEzFdPrymIr08hnlFMZmfxwbx4=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
|
@ -314,6 +332,8 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210223095934-7937bea0104d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
package renderer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
urlPkg "net/url"
|
||||
"regexp"
|
||||
|
@ -12,13 +13,25 @@ import (
|
|||
"strings"
|
||||
|
||||
"code.rocketnine.space/tslocum/cview"
|
||||
"github.com/alecthomas/chroma/formatters"
|
||||
"github.com/alecthomas/chroma/lexers"
|
||||
"github.com/alecthomas/chroma/styles"
|
||||
"github.com/makeworld-the-better-one/amfora/config"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Terminal color information, set during display initialization by display/display.go
|
||||
var TermColor string
|
||||
|
||||
// Regex for identifying ANSI color codes
|
||||
var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*m`)
|
||||
|
||||
// Regex for identifying possible language string, based on RFC 6838 and lexers used by Chroma
|
||||
var langRegex = regexp.MustCompile(`^([a-zA-Z0-9]+/)?[a-zA-Z0-9]+([a-zA-Z0-9!_\#\$\&\-\^\.\+]+)*`)
|
||||
|
||||
// Regex for removing trailing newline (without disturbing ANSI codes) from code formatted with Chroma
|
||||
var trailingNewline = regexp.MustCompile(`(\r?\n)(?:\x1b\[[0-9;]*m)*$`)
|
||||
|
||||
// RenderANSI renders plain text pages containing ANSI codes.
|
||||
// Practically, it is used for the text/x-ansi.
|
||||
func RenderANSI(s string) string {
|
||||
|
@ -45,6 +58,10 @@ func RenderPlainText(s string) string {
|
|||
//
|
||||
// Set includeFirst to true if the prefix and suffix should be applied to the first wrapped line as well
|
||||
func wrapLine(line string, width int, prefix, suffix string, includeFirst bool) []string {
|
||||
if width < 1 {
|
||||
width = 1
|
||||
}
|
||||
|
||||
// Anonymous function to allow recovery from potential WordWrap panic
|
||||
var ret []string
|
||||
func() {
|
||||
|
@ -183,7 +200,7 @@ func convertRegularGemini(s string, numLinks, width int, proxied bool) (string,
|
|||
// Add the link text in blue (in a region), and a gray link number to the left of it
|
||||
// Those are the default colors, anyway
|
||||
|
||||
wrappedLink = wrapLine(linkText, width,
|
||||
wrappedLink = wrapLine(linkText, width-indent,
|
||||
strings.Repeat(" ", indent)+
|
||||
`["`+strconv.Itoa(num-1)+`"][`+config.GetColorString("amfora_link")+`]`,
|
||||
`[-][""]`,
|
||||
|
@ -198,8 +215,8 @@ func convertRegularGemini(s string, numLinks, width int, proxied bool) (string,
|
|||
} else {
|
||||
// No color
|
||||
|
||||
wrappedLink = wrapLine(linkText, width,
|
||||
strings.Repeat(" ", len(strconv.Itoa(num))+4)+ // +4 for spaces and brackets
|
||||
wrappedLink = wrapLine(linkText, width-indent,
|
||||
strings.Repeat(" ", indent)+ // +4 for spaces and brackets
|
||||
`["`+strconv.Itoa(num-1)+`"]`,
|
||||
`[""]`,
|
||||
false, // Don't indent the first line, it's the one with link number
|
||||
|
@ -215,7 +232,7 @@ func convertRegularGemini(s string, numLinks, width int, proxied bool) (string,
|
|||
if viper.GetBool("a-general.color") {
|
||||
// Color
|
||||
|
||||
wrappedLink = wrapLine(linkText, width,
|
||||
wrappedLink = wrapLine(linkText, width-indent,
|
||||
strings.Repeat(" ", indent)+
|
||||
`["`+strconv.Itoa(num-1)+`"]`+linkTag,
|
||||
`[-::-][""]`,
|
||||
|
@ -229,7 +246,7 @@ func convertRegularGemini(s string, numLinks, width int, proxied bool) (string,
|
|||
} else {
|
||||
// No color
|
||||
|
||||
wrappedLink = wrapLine(linkText, width,
|
||||
wrappedLink = wrapLine(linkText, width-indent,
|
||||
strings.Repeat(" ", indent)+
|
||||
`["`+strconv.Itoa(num-1)+`"]`,
|
||||
`[::-][""]`,
|
||||
|
@ -248,7 +265,8 @@ func convertRegularGemini(s string, numLinks, width int, proxied bool) (string,
|
|||
} else if strings.HasPrefix(lines[i], "* ") {
|
||||
if viper.GetBool("a-general.bullets") {
|
||||
// Wrap list item, and indent wrapped lines past the bullet
|
||||
wrappedItem := wrapLine(lines[i][1:], width,
|
||||
wrappedItem := wrapLine(lines[i][1:],
|
||||
width-4, // Subtract the 4 indent spaces
|
||||
fmt.Sprintf(" [%s]", config.GetColorString("list_text")),
|
||||
"[-]", false)
|
||||
// Add bullet
|
||||
|
@ -256,7 +274,8 @@ func convertRegularGemini(s string, numLinks, width int, proxied bool) (string,
|
|||
wrappedItem[0] + "[-]"
|
||||
wrappedLines = append(wrappedLines, wrappedItem...)
|
||||
} else {
|
||||
wrappedItem := wrapLine(lines[i][1:], width,
|
||||
wrappedItem := wrapLine(lines[i][1:],
|
||||
width-4, // Subtract the 4 indent spaces
|
||||
fmt.Sprintf(" [%s]", config.GetColorString("list_text")),
|
||||
"[-]", false)
|
||||
// Add "*"
|
||||
|
@ -277,7 +296,9 @@ func convertRegularGemini(s string, numLinks, width int, proxied bool) (string,
|
|||
lines[i] = strings.TrimPrefix(lines[i], ">")
|
||||
lines[i] = strings.TrimPrefix(lines[i], " ")
|
||||
wrappedLines = append(wrappedLines,
|
||||
wrapLine(lines[i], width, fmt.Sprintf("[%s::i]> ", config.GetColorString("quote_text")),
|
||||
wrapLine(lines[i],
|
||||
width-2, // Subtract 2 for width of prefix string
|
||||
fmt.Sprintf("[%s::i]> ", config.GetColorString("quote_text")),
|
||||
"[-::-]", true)...,
|
||||
)
|
||||
}
|
||||
|
@ -315,11 +336,46 @@ func RenderGemini(s string, width int, proxied bool) (string, []string) {
|
|||
pre := false
|
||||
buf := "" // Block of regular or preformatted lines
|
||||
|
||||
// Language, formatter, and style for syntax highlighting
|
||||
lang := ""
|
||||
formatterName := TermColor
|
||||
styleName := viper.GetString("a-general.highlight_style")
|
||||
|
||||
// processPre is for rendering preformatted blocks
|
||||
processPre := func() {
|
||||
|
||||
syntaxHighlighted := false
|
||||
|
||||
// Perform syntax highlighting if language is set
|
||||
if lang != "" {
|
||||
style := styles.Get(styleName)
|
||||
if style == nil {
|
||||
style = styles.Fallback
|
||||
}
|
||||
formatter := formatters.Get(formatterName)
|
||||
if formatter == nil {
|
||||
formatter = formatters.Fallback
|
||||
}
|
||||
lexer := lexers.Get(lang)
|
||||
if lexer == nil {
|
||||
lexer = lexers.Fallback
|
||||
}
|
||||
|
||||
// Tokenize and format the text after stripping ANSI codes, replacing buffer if there are no errors
|
||||
iterator, err := lexer.Tokenise(nil, ansiRegex.ReplaceAllString(buf, ""))
|
||||
if err == nil {
|
||||
formattedBuffer := new(bytes.Buffer)
|
||||
if formatter.Format(formattedBuffer, style, iterator) == nil {
|
||||
// Strip extra newline added by Chroma and replace buffer
|
||||
buf = string(trailingNewline.ReplaceAll(formattedBuffer.Bytes(), []byte{}))
|
||||
}
|
||||
syntaxHighlighted = true
|
||||
}
|
||||
}
|
||||
|
||||
// Support ANSI color codes in preformatted blocks - see #59
|
||||
if viper.GetBool("a-general.color") && viper.GetBool("a-general.ansi") {
|
||||
// This will also execute if code highlighting was successful for this block
|
||||
if viper.GetBool("a-general.color") && (viper.GetBool("a-general.ansi") || syntaxHighlighted) {
|
||||
buf = cview.TranslateANSI(buf)
|
||||
// The TranslateANSI function will reset the colors when it encounters
|
||||
// an ANSI reset code, injecting a full reset tag: [-:-:-]
|
||||
|
@ -366,9 +422,21 @@ func RenderGemini(s string, width int, proxied bool) (string, []string) {
|
|||
// Don't add the current line with backticks
|
||||
processPre()
|
||||
|
||||
// Clear the language
|
||||
lang = ""
|
||||
} else {
|
||||
// Not preformatted, regular text
|
||||
processRegular()
|
||||
|
||||
if viper.GetBool("a-general.highlight_code") {
|
||||
// Check for alt text indicating a language that Chroma can highlight
|
||||
alt := strings.TrimSpace(strings.TrimPrefix(lines[i], "```"))
|
||||
if matches := langRegex.FindStringSubmatch(alt); matches != nil {
|
||||
if lexers.Get(matches[0]) != nil {
|
||||
lang = matches[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
buf = "" // Clear buffer for next block
|
||||
pre = !pre
|
||||
|
|
|
@ -7,9 +7,12 @@ import "os/exec"
|
|||
|
||||
// Open opens `path` in default system viewer.
|
||||
func Open(path string) (string, error) {
|
||||
err := exec.Command("open", path).Start()
|
||||
proc := exec.Command("open", path)
|
||||
err := proc.Start()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
//nolint:errcheck
|
||||
go proc.Wait() // Prevent zombies, see #219
|
||||
return "Opened in default system viewer", nil
|
||||
}
|
||||
|
|
|
@ -25,9 +25,12 @@ func Open(path string) (string, error) {
|
|||
case xdgOpenNotFoundErr == nil:
|
||||
// Use start rather than run or output in order
|
||||
// to make application run in background.
|
||||
if err := exec.Command(xdgOpenPath, path).Start(); err != nil {
|
||||
proc := exec.Command(xdgOpenPath, path)
|
||||
if err := proc.Start(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
//nolint:errcheck
|
||||
go proc.Wait() // Prevent zombies, see #219
|
||||
return "Opened in default system viewer", nil
|
||||
default:
|
||||
return "", fmt.Errorf("could not determine default system viewer. " +
|
||||
|
|
|
@ -8,9 +8,12 @@ import "os/exec"
|
|||
|
||||
// Open opens `path` in default system vierwer.
|
||||
func Open(path string) (string, error) {
|
||||
err := exec.Command("rundll32", "url.dll,FileProtocolHandler", path).Start()
|
||||
proc := exec.Command("rundll32", "url.dll,FileProtocolHandler", path)
|
||||
err := proc.Start()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
//nolint:errcheck
|
||||
go proc.Wait() // Prevent zombies, see #219
|
||||
return "Opened in default system viewer", nil
|
||||
}
|
||||
|
|
|
@ -7,9 +7,12 @@ import "os/exec"
|
|||
|
||||
// Open opens `url` in default system browser.
|
||||
func Open(url string) (string, error) {
|
||||
err := exec.Command("open", url).Start()
|
||||
proc := exec.Command("open", url)
|
||||
err := proc.Start()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
//nolint:errcheck
|
||||
go proc.Wait() // Prevent zombies, see #219
|
||||
return "Opened in system default web browser", nil
|
||||
}
|
||||
|
|
|
@ -34,14 +34,20 @@ func Open(url string) (string, error) {
|
|||
case xdgOpenNotFoundErr == nil: // Prefer xdg-open over $BROWSER
|
||||
// Use start rather than run or output in order
|
||||
// to make browser running in background.
|
||||
if err := exec.Command(xdgOpenPath, url).Start(); err != nil {
|
||||
proc := exec.Command(xdgOpenPath, url)
|
||||
if err := proc.Start(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
//nolint:errcheck
|
||||
go proc.Wait() // Prevent zombies, see #219
|
||||
return "Opened in system default web browser", nil
|
||||
case envBrowser != "":
|
||||
if err := exec.Command(envBrowser, url).Start(); err != nil {
|
||||
proc := exec.Command(envBrowser, url)
|
||||
if err := proc.Start(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
//nolint:errcheck
|
||||
go proc.Wait() // Prevent zombies, see #219
|
||||
return "Opened in system default web browser", nil
|
||||
default:
|
||||
return "", fmt.Errorf("could not determine system browser")
|
||||
|
|
|
@ -8,9 +8,12 @@ import "os/exec"
|
|||
|
||||
// Open opens `url` in default system browser.
|
||||
func Open(url string) (string, error) {
|
||||
err := exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
|
||||
proc := exec.Command("rundll32", "url.dll,FileProtocolHandler", url)
|
||||
err := proc.Start()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
//nolint:errcheck
|
||||
go proc.Wait() // Prevent zombies, see #219
|
||||
return "Opened in system default web browser", nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user