diff --git a/docs/Custom_Pagers.md b/docs/Custom_Pagers.md index f1b01b382dc..4324f77dc21 100644 --- a/docs/Custom_Pagers.md +++ b/docs/Custom_Pagers.md @@ -26,6 +26,8 @@ git: ![](https://i.imgur.com/QJpQkF3.png) +A cool feature of delta is --hyperlinks, which renders clickable links for the line numbers in the left margin, and lazygit supports these. To use them, set the `pager:` config to `delta --dark --paging=never --line-numbers --hyperlinks --hyperlinks-file-link-format="lazygit-edit://{path}:{line}`; this allows you to click on an underlined line number in the diff to jump right to that same line in your editor. + ## Diff-so-fancy ```yaml diff --git a/go.mod b/go.mod index 59ea178ece7..73a312f9997 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/integrii/flaggy v1.4.0 github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d - github.com/jesseduffield/gocui v0.3.1-0.20240824081936-a3adeb73f602 + github.com/jesseduffield/gocui v0.3.1-0.20240824083442-15b7fbca7ae9 github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e diff --git a/go.sum b/go.sum index 1605df6054e..caeb5acf75a 100644 --- a/go.sum +++ b/go.sum @@ -188,8 +188,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk= github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE= github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o= -github.com/jesseduffield/gocui v0.3.1-0.20240824081936-a3adeb73f602 h1:nzGt/sRT0WCancALG5Q9e4DlQWGo7QUMc35rApdt+aM= -github.com/jesseduffield/gocui v0.3.1-0.20240824081936-a3adeb73f602/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8= +github.com/jesseduffield/gocui v0.3.1-0.20240824083442-15b7fbca7ae9 h1:1muwCO0cmCGHpOvNz1qTOrCFPECnBAV87yDE9Fgwy6U= +github.com/jesseduffield/gocui v0.3.1-0.20240824083442-15b7fbca7ae9/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8= github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0= github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo= github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY= diff --git a/pkg/gui/controllers/helpers/confirmation_helper.go b/pkg/gui/controllers/helpers/confirmation_helper.go index 25f05906b95..8b5919c3c78 100644 --- a/pkg/gui/controllers/helpers/confirmation_helper.go +++ b/pkg/gui/controllers/helpers/confirmation_helper.go @@ -259,11 +259,7 @@ func underlineLinks(text string) string { } else { linkEnd += linkStart } - underlinedLink := style.AttrUnderline.Sprint(remaining[linkStart:linkEnd]) - if strings.HasSuffix(underlinedLink, "\x1b[0m") { - // Replace the "all styles off" code with "underline off" code - underlinedLink = underlinedLink[:len(underlinedLink)-2] + "24m" - } + underlinedLink := style.PrintSimpleHyperlink(remaining[linkStart:linkEnd]) result += remaining[:linkStart] + underlinedLink remaining = remaining[linkEnd:] } diff --git a/pkg/gui/controllers/helpers/confirmation_helper_test.go b/pkg/gui/controllers/helpers/confirmation_helper_test.go index 488c72710ef..76f4329fd3c 100644 --- a/pkg/gui/controllers/helpers/confirmation_helper_test.go +++ b/pkg/gui/controllers/helpers/confirmation_helper_test.go @@ -27,27 +27,27 @@ func Test_underlineLinks(t *testing.T) { { name: "entire string is a link", text: "https://example.com", - expectedResult: "\x1b[4mhttps://example.com\x1b[24m", + expectedResult: "\x1b]8;;https://example.com\x1b\\https://example.com\x1b]8;;\x1b\\", }, { name: "link preceeded and followed by text", text: "bla https://example.com xyz", - expectedResult: "bla \x1b[4mhttps://example.com\x1b[24m xyz", + expectedResult: "bla \x1b]8;;https://example.com\x1b\\https://example.com\x1b]8;;\x1b\\ xyz", }, { name: "more than one link", text: "bla https://link1 blubb https://link2 xyz", - expectedResult: "bla \x1b[4mhttps://link1\x1b[24m blubb \x1b[4mhttps://link2\x1b[24m xyz", + expectedResult: "bla \x1b]8;;https://link1\x1b\\https://link1\x1b]8;;\x1b\\ blubb \x1b]8;;https://link2\x1b\\https://link2\x1b]8;;\x1b\\ xyz", }, { name: "link in angle brackets", text: "See for details", - expectedResult: "See <\x1b[4mhttps://example.com\x1b[24m> for details", + expectedResult: "See <\x1b]8;;https://example.com\x1b\\https://example.com\x1b]8;;\x1b\\> for details", }, { name: "link followed by newline", text: "URL: https://example.com\nNext line", - expectedResult: "URL: \x1b[4mhttps://example.com\x1b[24m\nNext line", + expectedResult: "URL: \x1b]8;;https://example.com\x1b\\https://example.com\x1b]8;;\x1b\\\nNext line", }, } diff --git a/pkg/gui/controllers/status_controller.go b/pkg/gui/controllers/status_controller.go index 05b3181c144..ab7a6a0d507 100644 --- a/pkg/gui/controllers/status_controller.go +++ b/pkg/gui/controllers/status_controller.go @@ -71,11 +71,6 @@ func (self *StatusController) GetKeybindings(opts types.KeybindingsOpts) []*type func (self *StatusController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { return []*gocui.ViewMouseBinding{ - { - ViewName: "main", - Key: gocui.MouseLeft, - Handler: self.onClickMain, - }, { ViewName: self.Context().GetViewName(), Key: gocui.MouseLeft, @@ -84,10 +79,6 @@ func (self *StatusController) GetMouseKeybindings(opts types.KeybindingsOpts) [] } } -func (self *StatusController) onClickMain(opts gocui.ViewMouseBindingOpts) error { - return self.c.HandleGenericClick(self.c.Views().Main) -} - func (self *StatusController) GetOnRenderToMain() func() error { return func() error { switch self.c.UserConfig().Gui.StatusPanelView { @@ -219,12 +210,12 @@ func (self *StatusController) showDashboard() error { []string{ lazygitTitle(), fmt.Sprintf("Copyright %d Jesse Duffield", time.Now().Year()), - fmt.Sprintf("Keybindings: %s", style.AttrUnderline.Sprint(fmt.Sprintf(constants.Links.Docs.Keybindings, versionStr))), - fmt.Sprintf("Config Options: %s", style.AttrUnderline.Sprint(fmt.Sprintf(constants.Links.Docs.Config, versionStr))), - fmt.Sprintf("Tutorial: %s", style.AttrUnderline.Sprint(constants.Links.Docs.Tutorial)), - fmt.Sprintf("Raise an Issue: %s", style.AttrUnderline.Sprint(constants.Links.Issues)), - fmt.Sprintf("Release Notes: %s", style.AttrUnderline.Sprint(constants.Links.Releases)), - style.FgMagenta.Sprintf("Become a sponsor: %s", style.AttrUnderline.Sprint(constants.Links.Donate)), // caffeine ain't free + fmt.Sprintf("Keybindings: %s", style.PrintSimpleHyperlink(fmt.Sprintf(constants.Links.Docs.Keybindings, versionStr))), + fmt.Sprintf("Config Options: %s", style.PrintSimpleHyperlink(fmt.Sprintf(constants.Links.Docs.Config, versionStr))), + fmt.Sprintf("Tutorial: %s", style.PrintSimpleHyperlink(constants.Links.Docs.Tutorial)), + fmt.Sprintf("Raise an Issue: %s", style.PrintSimpleHyperlink(constants.Links.Issues)), + fmt.Sprintf("Release Notes: %s", style.PrintSimpleHyperlink(constants.Links.Releases)), + style.FgMagenta.Sprintf("Become a sponsor: %s", style.PrintSimpleHyperlink(constants.Links.Donate)), // caffeine ain't free }, "\n\n") + "\n" return self.c.RenderToMainViews(types.RefreshMainOpts{ diff --git a/pkg/gui/global_handlers.go b/pkg/gui/global_handlers.go index e75dfb8f550..9721b4b2ab0 100644 --- a/pkg/gui/global_handlers.go +++ b/pkg/gui/global_handlers.go @@ -109,14 +109,6 @@ func (gui *Gui) scrollDownConfirmationPanel() error { return nil } -func (gui *Gui) handleConfirmationClick() error { - if gui.Views.Confirmation.Editable { - return nil - } - - return gui.handleGenericClick(gui.Views.Confirmation) -} - func (gui *Gui) handleCopySelectedSideContextItemToClipboard() error { return gui.handleCopySelectedSideContextItemToClipboardWithTruncation(-1) } diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index c26774fd0f2..d608fb3a434 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "reflect" + "regexp" "sort" "strings" "sync" @@ -359,6 +360,28 @@ func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, contextKey types.Context return nil }) + gui.g.SetOpenHyperlinkFunc(func(url string) error { + if strings.HasPrefix(url, "lazygit-edit:") { + re := regexp.MustCompile(`^lazygit-edit://(.+?)(?::(\d+))?$`) + matches := re.FindStringSubmatch(url) + if matches == nil { + return fmt.Errorf(gui.Tr.InvalidLazygitEditURL, url) + } + filepath := matches[1] + if matches[2] != "" { + lineNumber := utils.MustConvertToInt(matches[2]) + return gui.helpers.Files.EditFileAtLine(filepath, lineNumber) + } + return gui.helpers.Files.EditFiles([]string{filepath}) + } + + if err := gui.os.OpenLink(url); err != nil { + return fmt.Errorf(gui.Tr.FailedToOpenURL, url, err) + } + + return nil + }) + // if a context key has been given, push that instead, and set its index to 0 if contextKey != context.NO_CONTEXT { contextToPush = gui.c.ContextForKey(contextKey) diff --git a/pkg/gui/gui_common.go b/pkg/gui/gui_common.go index 2b132c3d1e0..1c56301d977 100644 --- a/pkg/gui/gui_common.go +++ b/pkg/gui/gui_common.go @@ -33,10 +33,6 @@ func (self *guiCommon) PostRefreshUpdate(context types.Context) error { return self.gui.postRefreshUpdate(context) } -func (self *guiCommon) HandleGenericClick(view *gocui.View) error { - return self.gui.handleGenericClick(view) -} - func (self *guiCommon) RunSubprocessAndRefresh(cmdObj oscommands.ICmdObj) error { return self.gui.runSubprocessWithSuspenseAndRefresh(cmdObj) } diff --git a/pkg/gui/information_panel.go b/pkg/gui/information_panel.go index 3eac1e77cf4..03e4dd8788e 100644 --- a/pkg/gui/information_panel.go +++ b/pkg/gui/information_panel.go @@ -14,8 +14,8 @@ func (gui *Gui) informationStr() string { } if gui.g.Mouse { - donate := style.FgMagenta.SetUnderline().Sprint(gui.c.Tr.Donate) - askQuestion := style.FgYellow.SetUnderline().Sprint(gui.c.Tr.AskQuestion) + donate := style.FgMagenta.Sprint(style.PrintHyperlink(gui.c.Tr.Donate, constants.Links.Donate)) + askQuestion := style.FgYellow.Sprint(style.PrintHyperlink(gui.c.Tr.AskQuestion, constants.Links.Discussions)) return fmt.Sprintf("%s %s %s", donate, askQuestion, gui.Config.GetVersion()) } else { return gui.Config.GetVersion() @@ -39,28 +39,5 @@ func (gui *Gui) handleInfoClick() error { return activeMode.Reset() } - var title, url string - - // if we're not in an active mode we show the donate button - if cx <= utils.StringWidth(gui.c.Tr.Donate) { - url = constants.Links.Donate - title = gui.c.Tr.Donate - } else if cx <= utils.StringWidth(gui.c.Tr.Donate)+1+utils.StringWidth(gui.c.Tr.AskQuestion) { - url = constants.Links.Discussions - title = gui.c.Tr.AskQuestion - } - err := gui.os.OpenLink(url) - if err != nil { - // Opening the link via the OS failed for some reason. (For example, this - // can happen if the `os.openLink` config key references a command that - // doesn't exist, or that errors when called.) - // - // In that case, rather than crash the app, fall back to simply showing a - // dialog asking the user to visit the URL. - placeholders := map[string]string{"url": url} - message := utils.ResolvePlaceholderString(gui.c.Tr.PleaseGoToURL, placeholders) - return gui.c.Alert(title, message) - } - return nil } diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 357c752aa56..a3da576596c 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -248,12 +248,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi Modifier: gocui.ModNone, Handler: self.scrollDownConfirmationPanel, }, - { - ViewName: "confirmation", - Key: gocui.MouseLeft, - Modifier: gocui.ModNone, - Handler: self.handleConfirmationClick, - }, { ViewName: "confirmation", Key: gocui.MouseWheelUp, diff --git a/pkg/gui/style/hyperlink.go b/pkg/gui/style/hyperlink.go new file mode 100644 index 00000000000..0585e89a957 --- /dev/null +++ b/pkg/gui/style/hyperlink.go @@ -0,0 +1,13 @@ +package style + +import "fmt" + +// Render the given text as an OSC 8 hyperlink +func PrintHyperlink(text string, link string) string { + return fmt.Sprintf("\033]8;;%s\033\\%s\033]8;;\033\\", link, text) +} + +// Render a link where the text is the same as a link +func PrintSimpleHyperlink(link string) string { + return fmt.Sprintf("\033]8;;%s\033\\%s\033]8;;\033\\", link, link) +} diff --git a/pkg/gui/types/common.go b/pkg/gui/types/common.go index b4a731c5952..9b7ccf34ff9 100644 --- a/pkg/gui/types/common.go +++ b/pkg/gui/types/common.go @@ -35,10 +35,6 @@ type IGuiCommon interface { // case would be overkill, although refresh will internally call 'PostRefreshUpdate' PostRefreshUpdate(Context) error - // a generic click handler that can be used for any view; it handles opening - // URLs in the browser when the user clicks on one - HandleGenericClick(view *gocui.View) error - // renders string to a view without resetting its origin SetViewContent(view *gocui.View, content string) // resets cursor and origin of view. Often used before calling SetViewContent diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 19eb3778301..1ae6251e192 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -1,7 +1,6 @@ package gui import ( - "regexp" "time" "github.com/jesseduffield/gocui" @@ -149,28 +148,3 @@ func (gui *Gui) postRefreshUpdate(c types.Context) error { return nil } - -// handleGenericClick is a generic click handler that can be used for any view. -// It handles opening URLs in the browser when the user clicks on one. -func (gui *Gui) handleGenericClick(view *gocui.View) error { - cx, cy := view.Cursor() - word, err := view.Word(cx, cy) - if err != nil { - return nil - } - - // Allow URLs to be wrapped in angle brackets, and the closing bracket to - // be followed by punctuation: - re := regexp.MustCompile(`^[,.;!]*)?$`) - matches := re.FindStringSubmatch(word) - if matches == nil { - return nil - } - - // Ignore errors (opening the link via the OS can fail if the - // `os.openLink` config key references a command that doesn't exist, or - // that errors when called.) - _ = gui.c.OS().OpenLink(matches[1]) - - return nil -} diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index f4520a3ee0d..b1eff335e42 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -784,7 +784,8 @@ type TranslationSet struct { MarkAsBaseCommit string MarkAsBaseCommitTooltip string MarkedCommitMarker string - PleaseGoToURL string + FailedToOpenURL string + InvalidLazygitEditURL string NoCopiedCommits string DisabledMenuItemPrefix string QuickStartInteractiveRebase string @@ -1770,7 +1771,8 @@ func EnglishTranslationSet() *TranslationSet { MarkAsBaseCommit: "Mark as base commit for rebase", MarkAsBaseCommitTooltip: "Select a base commit for the next rebase. When you rebase onto a branch, only commits above the base commit will be brought across. This uses the `git rebase --onto` command.", MarkedCommitMarker: "↑↑↑ Will rebase from here ↑↑↑", - PleaseGoToURL: "Please go to {{.url}}", + FailedToOpenURL: "Failed to open URL %s\n\nError: %v", + InvalidLazygitEditURL: "Invalid lazygit-edit URL format: %s", DisabledMenuItemPrefix: "Disabled: ", NoCopiedCommits: "No copied commits", QuickStartInteractiveRebase: "Start interactive rebase", diff --git a/pkg/integration/tests/ui/open_link_failure.go b/pkg/integration/tests/ui/open_link_failure.go index 0bb45fb59a4..c4b27241ea3 100644 --- a/pkg/integration/tests/ui/open_link_failure.go +++ b/pkg/integration/tests/ui/open_link_failure.go @@ -17,8 +17,8 @@ var OpenLinkFailure = NewIntegrationTest(NewIntegrationTestArgs{ t.Views().Information().Click(0, 0) t.ExpectPopup().Confirmation(). - Title(Equals("Donate")). - Content(Equals("Please go to https://github.com/sponsors/jesseduffield")). + Title(Equals("Error")). + Content(Equals("Failed to open URL https://github.com/sponsors/jesseduffield\n\nError: exit status 42")). Confirm() }, }) diff --git a/pkg/utils/color.go b/pkg/utils/color.go index a4ad578e055..05e0aa9bc03 100644 --- a/pkg/utils/color.go +++ b/pkg/utils/color.go @@ -25,7 +25,9 @@ func Decolorise(str string) string { } re := regexp.MustCompile(`\x1B\[([0-9]{1,3}(;[0-9]{1,3})*)?[mGK]`) + linkRe := regexp.MustCompile(`\x1B]8;[^;]*;(.*?)(\x1B.|\x07)`) ret := re.ReplaceAllString(str, "") + ret = linkRe.ReplaceAllString(ret, "") decoloriseMutex.Lock() decoloriseCache[str] = ret diff --git a/pkg/utils/color_test.go b/pkg/utils/color_test.go index 1440f946c25..19770d63e74 100644 --- a/pkg/utils/color_test.go +++ b/pkg/utils/color_test.go @@ -2,6 +2,8 @@ package utils import ( "testing" + + "github.com/jesseduffield/lazygit/pkg/gui/style" ) func TestDecolorise(t *testing.T) { @@ -189,6 +191,10 @@ func TestDecolorise(t *testing.T) { input: "\x1b[38;2;157;205;18mta\x1b[0m", output: "ta", }, + { + input: "a_" + style.PrintSimpleHyperlink("xyz") + "_b", + output: "a_xyz_b", + }, } for _, test := range tests { diff --git a/vendor/github.com/jesseduffield/gocui/escape.go b/vendor/github.com/jesseduffield/gocui/escape.go index b52c2149525..f559bbb2589 100644 --- a/vendor/github.com/jesseduffield/gocui/escape.go +++ b/vendor/github.com/jesseduffield/gocui/escape.go @@ -17,6 +17,7 @@ type escapeInterpreter struct { curFgColor, curBgColor Attribute mode OutputMode instruction instruction + hyperlink string } type ( @@ -40,7 +41,11 @@ const ( stateCSI stateParams stateOSC - stateOSCEscape + stateOSCWaitForParams + stateOSCParams + stateOSCHyperlink + stateOSCEndEscape + stateOSCSkipUnknown bold fontEffect = 1 faint fontEffect = 2 @@ -60,6 +65,7 @@ var ( errNotCSI = errors.New("Not a CSI escape sequence") errCSIParseError = errors.New("CSI escape sequence parsing error") errCSITooLong = errors.New("CSI escape sequence is too long") + errOSCParseError = errors.New("OSC escape sequence parsing error") ) // runes in case of error will output the non-parsed runes as a string. @@ -78,6 +84,7 @@ func (ei *escapeInterpreter) runes() []rune { ret = append(ret, ';') } return append(ret, ei.curch) + default: } return nil } @@ -191,15 +198,47 @@ func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) { return false, errCSIParseError } case stateOSC: + if ch == '8' { + ei.state = stateOSCWaitForParams + ei.hyperlink = "" + return true, nil + } + + ei.state = stateOSCSkipUnknown + return true, nil + case stateOSCWaitForParams: + if ch != ';' { + return true, errOSCParseError + } + + ei.state = stateOSCParams + return true, nil + case stateOSCParams: + if ch == ';' { + ei.state = stateOSCHyperlink + } + return true, nil + case stateOSCHyperlink: switch ch { + case 0x07: + ei.state = stateNone case 0x1b: - ei.state = stateOSCEscape - return true, nil + ei.state = stateOSCEndEscape + default: + ei.hyperlink += string(ch) } return true, nil - case stateOSCEscape: + case stateOSCEndEscape: ei.state = stateNone return true, nil + case stateOSCSkipUnknown: + switch ch { + case 0x07: + ei.state = stateNone + case 0x1b: + ei.state = stateOSCEndEscape + } + return true, nil } return false, nil } @@ -267,58 +306,48 @@ func (ei *escapeInterpreter) outputCSI() error { func (ei *escapeInterpreter) csiColor(param []string) (color Attribute, skip int, err error) { if len(param) < 2 { - err = errCSIParseError - return + return 0, 0, errCSIParseError } switch param[1] { case "2": // 24-bit color if ei.mode < OutputTrue { - err = errCSIParseError - return + return 0, 0, errCSIParseError } if len(param) < 5 { - err = errCSIParseError - return + return 0, 0, errCSIParseError } var red, green, blue int red, err = strconv.Atoi(param[2]) if err != nil { - err = errCSIParseError - return + return 0, 0, errCSIParseError } green, err = strconv.Atoi(param[3]) if err != nil { - err = errCSIParseError - return + return 0, 0, errCSIParseError } blue, err = strconv.Atoi(param[4]) if err != nil { - err = errCSIParseError - return + return 0, 0, errCSIParseError } return NewRGBColor(int32(red), int32(green), int32(blue)), 5, nil case "5": // 8-bit color if ei.mode < Output256 { - err = errCSIParseError - return + return 0, 0, errCSIParseError } if len(param) < 3 { - err = errCSIParseError - return + return 0, 0, errCSIParseError } var hex int hex, err = strconv.Atoi(param[2]) if err != nil { - err = errCSIParseError - return + return 0, 0, errCSIParseError } return Get256Color(int32(hex)), 3, nil default: - err = errCSIParseError - return + return 0, 0, errCSIParseError } } diff --git a/vendor/github.com/jesseduffield/gocui/gui.go b/vendor/github.com/jesseduffield/gocui/gui.go index c1ee93ce407..9d848d93ded 100644 --- a/vendor/github.com/jesseduffield/gocui/gui.go +++ b/vendor/github.com/jesseduffield/gocui/gui.go @@ -130,6 +130,7 @@ type Gui struct { managers []Manager keybindings []*keybinding focusHandler func(bool) error + openHyperlink func(string) error maxX, maxY int outputMode OutputMode stop chan struct{} @@ -624,6 +625,10 @@ func (g *Gui) SetFocusHandler(handler func(bool) error) { g.focusHandler = handler } +func (g *Gui) SetOpenHyperlinkFunc(openHyperlinkFunc func(string) error) { + g.openHyperlink = openHyperlinkFunc +} + // getKey takes an empty interface with a key and returns the corresponding // typed Key or rune. func getKey(key interface{}) (Key, rune, error) { @@ -1302,7 +1307,7 @@ func (g *Gui) onKey(ev *GocuiEvent) error { switch ev.Type { case eventKey: - _, err := g.execKeybindings(g.currentView, ev) + err := g.execKeybindings(g.currentView, ev) if err != nil { return err } @@ -1367,6 +1372,14 @@ func (g *Gui) onKey(ev *GocuiEvent) error { } } + if ev.Key == MouseLeft && !v.Editable && g.openHyperlink != nil { + if newY >= 0 && newY <= len(v.viewLines)-1 && newX >= 0 && newX <= len(v.viewLines[newY].line)-1 { + if link := v.viewLines[newY].line[newX].hyperlink; link != "" { + return g.openHyperlink(link) + } + } + } + if IsMouseKey(ev.Key) { opts := ViewMouseBindingOpts{X: newX, Y: newY} matched, err := g.execMouseKeybindings(v, ev, opts) @@ -1378,9 +1391,11 @@ func (g *Gui) onKey(ev *GocuiEvent) error { } } - if _, err := g.execKeybindings(v, ev); err != nil { + if err := g.execKeybindings(v, ev); err != nil { return err } + + default: } return nil @@ -1440,25 +1455,25 @@ func IsMouseScrollKey(key interface{}) bool { } // execKeybindings executes the keybinding handlers that match the passed view -// and event. The value of matched is true if there is a match and no errors. -func (g *Gui) execKeybindings(v *View, ev *GocuiEvent) (matched bool, err error) { +// and event. +func (g *Gui) execKeybindings(v *View, ev *GocuiEvent) error { var globalKb *keybinding var matchingParentViewKb *keybinding // if we're searching, and we've hit n/N/Esc, we ignore the default keybinding if v != nil && v.IsSearching() && ev.Mod == ModNone { if eventMatchesKey(ev, g.NextSearchMatchKey) { - return true, v.gotoNextMatch() + return v.gotoNextMatch() } else if eventMatchesKey(ev, g.PrevSearchMatchKey) { - return true, v.gotoPreviousMatch() + return v.gotoPreviousMatch() } else if eventMatchesKey(ev, g.SearchEscapeKey) { v.searcher.clearSearch() if g.OnSearchEscape != nil { if err := g.OnSearchEscape(); err != nil { - return true, err + return err } } - return true, nil + return nil } } @@ -1486,26 +1501,26 @@ func (g *Gui) execKeybindings(v *View, ev *GocuiEvent) (matched bool, err error) if g.currentView != nil && g.currentView.Editable && g.currentView.Editor != nil { matched := g.currentView.Editor.Edit(g.currentView, ev.Key, ev.Ch, ev.Mod) if matched { - return true, nil + return nil } } if globalKb != nil { return g.execKeybinding(v, globalKb) } - return false, nil + return nil } // execKeybinding executes a given keybinding -func (g *Gui) execKeybinding(v *View, kb *keybinding) (bool, error) { +func (g *Gui) execKeybinding(v *View, kb *keybinding) error { if g.isBlacklisted(kb.key) { - return true, nil + return nil } if err := kb.handler(g, v); err != nil { - return false, err + return err } - return true, nil + return nil } func (g *Gui) onFocus(ev *GocuiEvent) error { diff --git a/vendor/github.com/jesseduffield/gocui/tcell_driver.go b/vendor/github.com/jesseduffield/gocui/tcell_driver.go index 96f24390f34..6665432c56a 100644 --- a/vendor/github.com/jesseduffield/gocui/tcell_driver.go +++ b/vendor/github.com/jesseduffield/gocui/tcell_driver.go @@ -363,6 +363,7 @@ func (g *Gui) pollEvent() GocuiEvent { mouseKey = MouseRight case tcell.ButtonMiddle: mouseKey = MouseMiddle + default: } } @@ -374,11 +375,13 @@ func (g *Gui) pollEvent() GocuiEvent { dragState = NOT_DRAGGING case tcell.ButtonSecondary: case tcell.ButtonMiddle: + default: } mouseMod = Modifier(lastMouseMod) lastMouseMod = tcell.ModNone lastMouseKey = tcell.ButtonNone } + default: } if !wheeling { diff --git a/vendor/github.com/jesseduffield/gocui/view.go b/vendor/github.com/jesseduffield/gocui/view.go index e12991ee9ec..0752a5e9c01 100644 --- a/vendor/github.com/jesseduffield/gocui/view.go +++ b/vendor/github.com/jesseduffield/gocui/view.go @@ -378,6 +378,7 @@ type viewLine struct { type cell struct { chr rune bgColor, fgColor Attribute + hyperlink string } type lineType []cell @@ -851,9 +852,10 @@ func (v *View) parseInput(ch rune, x int, _ int) (bool, []cell) { repeatCount = tabStop - (x % tabStop) } c := cell{ - fgColor: v.ei.curFgColor, - bgColor: v.ei.curBgColor, - chr: ch, + fgColor: v.ei.curFgColor, + bgColor: v.ei.curBgColor, + hyperlink: v.ei.hyperlink, + chr: ch, } for i := 0; i < repeatCount; i++ { cells = append(cells, c) @@ -1188,6 +1190,9 @@ func (v *View) draw() error { if bgColor == ColorDefault { bgColor = v.BgColor } + if c.hyperlink != "" { + fgColor |= AttrUnderline + } if err := v.setRune(x, y, c.chr, fgColor, bgColor); err != nil { return err diff --git a/vendor/modules.txt b/vendor/modules.txt index b271313d79c..e1a96bb7ba7 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -172,7 +172,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem github.com/jesseduffield/go-git/v5/utils/merkletrie/index github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame github.com/jesseduffield/go-git/v5/utils/merkletrie/noder -# github.com/jesseduffield/gocui v0.3.1-0.20240824081936-a3adeb73f602 +# github.com/jesseduffield/gocui v0.3.1-0.20240824083442-15b7fbca7ae9 ## explicit; go 1.12 github.com/jesseduffield/gocui # github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10