Skip to content

Commit

Permalink
Render hyperlinks in the command log output
Browse files Browse the repository at this point in the history
  • Loading branch information
gmlexx committed Sep 14, 2024
1 parent 2d0c7cb commit ee4fd02
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 118 deletions.
92 changes: 2 additions & 90 deletions pkg/gui/command_log_panel.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func (gui *Gui) LogCommand(cmdStr string, commandLine bool) {
}

gui.Views.Extras.Autoscroll = true
gui.Views.Extras.UnderlineHyperLinksOnlyOnHover = true

textStyle := theme.DefaultTextColor
if !commandLine {
Expand All @@ -64,7 +65,7 @@ func (gui *Gui) printCommandLogHeader() {
gui.Views.Extras,
"%s: %s",
style.FgYellow.Sprint(gui.c.Tr.RandomTip),
style.FgGreen.Sprint(gui.getRandomTip()),
style.FgGreen.Sprint(style.UnderlineLinks(gui.getRandomTip())),
)
}
}
Expand All @@ -78,102 +79,13 @@ func (gui *Gui) getRandomTip() string {

tips := []string{
// keybindings and lazygit-specific advice
fmt.Sprintf(
"To force push, press '%s' and then if the push is rejected you will be asked if you want to force push",
formattedKey(config.Universal.Push),
),
fmt.Sprintf(
"To filter commits by path, press '%s'",
formattedKey(config.Universal.FilteringMenu),
),
fmt.Sprintf(
"To start an interactive rebase, press '%s' on a commit. You can always abort the rebase by pressing '%s' and selecting 'abort'",
formattedKey(config.Universal.Edit),
formattedKey(config.Universal.CreateRebaseOptionsMenu),
),
fmt.Sprintf(
"In flat file view, merge conflicts are sorted to the top. To switch to flat file view press '%s'",
formattedKey(config.Files.ToggleTreeView),
),
"If you want to learn Go and can think of ways to improve lazygit, join the team! Click 'Ask Question' and express your interest",
fmt.Sprintf(
"If you press '%s'/'%s' you can undo/redo your changes. Be wary though, this only applies to branches/commits, so only do this if your worktree is clear.\nDocs: %s",
formattedKey(config.Universal.Undo),
formattedKey(config.Universal.Redo),
constants.Links.Docs.Undoing,
),
fmt.Sprintf(
"to hard reset onto your current upstream branch, press '%s' in the files panel",
formattedKey(config.Commits.ViewResetOptions),
),
fmt.Sprintf(
"To push a tag, navigate to the tag in the tags tab and press '%s'",
formattedKey(config.Branches.PushTag),
),
fmt.Sprintf(
"You can view the individual files of a stash entry by pressing '%s'",
formattedKey(config.Universal.GoInto),
),
fmt.Sprintf(
"You can diff two commits by pressing '%s' on one commit and then navigating to the other. You can then press '%s' to view the files of the diff",
formattedKey(config.Universal.DiffingMenu),
formattedKey(config.Universal.GoInto),
),
fmt.Sprintf(
"press '%s' on a commit to drop it (delete it)",
formattedKey(config.Universal.Remove),
),
fmt.Sprintf(
"If you need to pull out the big guns to resolve merge conflicts, you can press '%s' in the files panel to open 'git mergetool'",
formattedKey(config.Files.OpenMergeTool),
),
fmt.Sprintf(
"To revert a commit, press '%s' on that commit",
formattedKey(config.Commits.RevertCommit),
),
fmt.Sprintf(
"To escape a mode, for example cherry-picking, patch-building, diffing, or filtering mode, you can just spam the '%s' button. Unless of course you have `quitOnTopLevelReturn` enabled in your config",
formattedKey(config.Universal.Return),
),
fmt.Sprintf(
"You can page through the items of a panel using '%s' and '%s'",
formattedKey(config.Universal.PrevPage),
formattedKey(config.Universal.NextPage),
),
fmt.Sprintf(
"You can jump to the top/bottom of a panel using '%s' and '%s'",
formattedKey(config.Universal.GotoTop),
formattedKey(config.Universal.GotoBottom),
),
fmt.Sprintf(
"To collapse/expand a directory, press '%s'",
formattedKey(config.Universal.GoInto),
),
fmt.Sprintf(
"You can append your staged changes to an older commit by pressing '%s' on that commit",
formattedKey(config.Commits.AmendToCommit),
),
fmt.Sprintf(
"You can amend the last commit with your new file changes by pressing '%s' in the files panel",
formattedKey(config.Files.AmendLastCommit),
),
fmt.Sprintf(
"You can now navigate the side panels with '%s' and '%s'",
formattedKey(config.Universal.NextBlockAlt2),
formattedKey(config.Universal.PrevBlockAlt2),
),

"You can use lazygit with a bare repo by passing the --git-dir and --work-tree arguments as you would for the git CLI",

// general advice
"`git commit` is really just the programmer equivalent of saving your game. Always do it before embarking on an ambitious change!",
"Try to separate commits that refactor code from commits that add new functionality: if they're squashed into the one commit, it can be hard to spot what's new.",
"If you ever want to experiment, it's easy to create a new branch off your current one and go nuts, then delete it afterwards",
"Always read through the diff of your changes before assigning somebody to review your code. Better for you to catch any silly mistakes than your colleagues!",
"If something goes wrong, you can always checkout a commit from your reflog to return to an earlier state",
"The stash is a good place to save snippets of code that you always find yourself adding when debugging.",

// links
fmt.Sprintf(
"If you want a git diff with syntax colouring, check out lazygit's integration with delta:\n%s",
constants.Links.Docs.CustomPagers,
Expand Down
24 changes: 1 addition & 23 deletions pkg/gui/controllers/helpers/confirmation_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ func (self *ConfirmationHelper) CreatePopupPanel(ctx goContext.Context, opts typ
confirmationView.RenderTextArea()
} else {
self.c.ResetViewOrigin(confirmationView)
self.c.SetViewContent(confirmationView, style.AttrBold.Sprint(underlineLinks(opts.Prompt)))
self.c.SetViewContent(confirmationView, style.AttrBold.Sprint(style.UnderlineLinks(opts.Prompt)))
}

self.setKeyBindings(cancel, opts)
Expand All @@ -233,28 +233,6 @@ func (self *ConfirmationHelper) CreatePopupPanel(ctx goContext.Context, opts typ
self.c.Context().Push(self.c.Contexts().Confirmation)
}

func underlineLinks(text string) string {
result := ""
remaining := text
for {
linkStart := strings.Index(remaining, "https://")
if linkStart == -1 {
break
}

linkEnd := strings.IndexAny(remaining[linkStart:], " \n>")
if linkEnd == -1 {
linkEnd = len(remaining)
} else {
linkEnd += linkStart
}
underlinedLink := style.PrintSimpleHyperlink(remaining[linkStart:linkEnd])
result += remaining[:linkStart] + underlinedLink
remaining = remaining[linkEnd:]
}
return result + remaining
}

func (self *ConfirmationHelper) setKeyBindings(cancel goContext.CancelFunc, opts types.CreatePopupPanelOpts) {
var onConfirm func() error
if opts.HandleConfirmPrompt != nil {
Expand Down
4 changes: 2 additions & 2 deletions pkg/gui/extras_panel.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func (gui *Gui) scrollDownExtra() error {
}

func (gui *Gui) getCmdWriter() io.Writer {
return &prefixWriter{writer: gui.Views.Extras, prefix: style.FgMagenta.Sprintf("\n\n%s\n", gui.c.Tr.GitOutput)}
return &prefixWriter{writer: gui.Views.Extras, prefix: style.FgMagenta.Sprintf("\n\n%s\n", style.UnderlineLinks(gui.c.Tr.GitOutput))}
}

// Ensures that the first write is preceded by writing a prefix.
Expand All @@ -80,5 +80,5 @@ func (self *prefixWriter) Write(p []byte) (int, error) {
return n, err
}
}
return self.writer.Write(p)
return self.writer.Write([]byte(style.UnderlineLinks(p)))
}
27 changes: 26 additions & 1 deletion pkg/gui/style/hyperlink.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package style

import "fmt"
import (
"fmt"
"strings"
)

// Render the given text as an OSC 8 hyperlink
func PrintHyperlink(text string, link string) string {
Expand All @@ -11,3 +14,25 @@ func PrintHyperlink(text string, link string) string {
func PrintSimpleHyperlink(link string) string {
return fmt.Sprintf("\033]8;;%s\033\\%s\033]8;;\033\\", link, link)
}

func UnderlineLinks[T string | []byte](text T) string {
result := ""
remaining := string(text)
for {
linkStart := strings.Index(remaining, "https://")
if linkStart == -1 {
break
}

linkEnd := strings.IndexAny(remaining[linkStart:], " \n>")
if linkEnd == -1 {
linkEnd = len(remaining)
} else {
linkEnd += linkStart
}
underlinedLink := PrintSimpleHyperlink(remaining[linkStart:linkEnd])
result += remaining[:linkStart] + underlinedLink
remaining = remaining[linkEnd:]
}
return result + remaining
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package helpers
package style

import (
"testing"
Expand Down Expand Up @@ -56,7 +56,7 @@ func Test_underlineLinks(t *testing.T) {

for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
result := underlineLinks(s.text)
result := UnderlineLinks(s.text)
assert.Equal(t, s.expectedResult, result)
})
}
Expand Down

0 comments on commit ee4fd02

Please sign in to comment.