feat(ui): back using right mouse click and backspace key

Ayman Bagabas created

Beware that most terminals block the right mouse click unless combined
with a modifier.

Fixes: https://github.com/charmbracelet/soft-serve/issues/138

Change summary

ui/keymap/keymap.go    |  1 +
ui/pages/repo/files.go | 15 ++++++++-------
ui/pages/repo/log.go   | 20 ++++++++++++--------
ui/pages/repo/refs.go  |  4 ++--
ui/pages/repo/repo.go  | 43 +++++++++++++++++++++++++++++--------------
5 files changed, 52 insertions(+), 31 deletions(-)

Detailed changes

ui/keymap/keymap.go 🔗

@@ -183,6 +183,7 @@ func DefaultKeyMap() *KeyMap {
 		key.WithKeys(
 			"h",
 			"left",
+			"backspace",
 		),
 		key.WithHelp(
 			"←",

ui/pages/repo/files.go 🔗

@@ -227,20 +227,21 @@ func (f *Files) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 				cmds = append(cmds, f.selectFileCmd)
 			}
 		}
+	case BackMsg:
+		cmds = append(cmds, f.deselectItemCmd)
 	case tea.KeyMsg:
 		switch f.activeView {
 		case filesViewFiles:
-			switch msg.String() {
-			case "l", "right":
+			switch {
+			case key.Matches(msg, f.common.KeyMap.SelectItem):
 				cmds = append(cmds, f.selector.SelectItem)
-			case "h", "left":
-				cmds = append(cmds, f.deselectItemCmd)
+			case key.Matches(msg, f.common.KeyMap.BackItem):
+				cmds = append(cmds, backCmd)
 			}
 		case filesViewContent:
-			keyStr := msg.String()
 			switch {
-			case keyStr == "h", keyStr == "left":
-				cmds = append(cmds, f.deselectItemCmd)
+			case key.Matches(msg, f.common.KeyMap.BackItem):
+				cmds = append(cmds, backCmd)
 			case key.Matches(msg, f.common.KeyMap.Copy):
 				f.common.Copy.Copy(f.currentContent.content)
 			case key.Matches(msg, lineNo):

ui/pages/repo/log.go 🔗

@@ -211,10 +211,10 @@ func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	case tea.KeyMsg, tea.MouseMsg:
 		switch l.activeView {
 		case logViewCommits:
-			switch key := msg.(type) {
+			switch kmsg := msg.(type) {
 			case tea.KeyMsg:
-				switch key.String() {
-				case "l", "right":
+				switch {
+				case key.Matches(kmsg, l.common.KeyMap.SelectItem):
 					cmds = append(cmds, l.selector.SelectItem)
 				}
 			}
@@ -233,15 +233,19 @@ func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 			}
 			cmds = append(cmds, cmd)
 		case logViewDiff:
-			switch key := msg.(type) {
+			switch kmsg := msg.(type) {
 			case tea.KeyMsg:
-				switch key.String() {
-				case "h", "left":
-					l.activeView = logViewCommits
-					l.selectedCommit = nil
+				switch {
+				case key.Matches(kmsg, l.common.KeyMap.BackItem):
+					cmds = append(cmds, backCmd)
 				}
 			}
 		}
+	case BackMsg:
+		if l.activeView == logViewDiff {
+			l.activeView = logViewCommits
+			l.selectedCommit = nil
+		}
 	case selector.ActiveMsg:
 		switch sel := msg.IdentifiableItem.(type) {
 		case LogItem:

ui/pages/repo/refs.go 🔗

@@ -132,8 +132,8 @@ func (r *Refs) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 			)
 		}
 	case tea.KeyMsg:
-		switch msg.String() {
-		case "l", "right":
+		switch {
+		case key.Matches(msg, r.common.KeyMap.SelectItem):
 			cmds = append(cmds, r.selector.SelectItem)
 		}
 	}

ui/pages/repo/repo.go 🔗

@@ -45,11 +45,11 @@ func (t tab) String() string {
 	}[t]
 }
 
-// CopyUrlMsg is a message to copy the URL of the current repository.
-type CopyUrlMsg struct{}
+// CopyURLMsg is a message to copy the URL of the current repository.
+type CopyURLMsg struct{}
 
-// ResetUrlMsg is a message to reset the URL string.
-type ResetUrlMsg struct{}
+// ResetURLMsg is a message to reset the URL string.
+type ResetURLMsg struct{}
 
 // UpdateStatusBarMsg updates the status bar.
 type UpdateStatusBarMsg struct{}
@@ -60,6 +60,9 @@ type RepoMsg git.GitRepo
 // RefMsg is a message that contains a git.Reference.
 type RefMsg *ggit.Reference
 
+// BackMsg is a message to go back to the previous view.
+type BackMsg struct{}
+
 // Repo is a view for a git repository.
 type Repo struct {
 	common       common.Common
@@ -70,7 +73,7 @@ type Repo struct {
 	statusbar    *statusbar.StatusBar
 	panes        []common.Component
 	ref          *ggit.Reference
-	copyUrl      time.Time
+	copyURL      time.Time
 }
 
 // New returns a new Repo.
@@ -198,20 +201,25 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 			cmds = append(cmds, r.updateStatusBarCmd)
 			switch msg := msg.(type) {
 			case tea.MouseMsg:
-				if msg.Type == tea.MouseLeft {
+				switch msg.Type {
+				case tea.MouseLeft:
 					id := fmt.Sprintf("%s-url", r.selectedRepo.Repo())
 					if r.common.Zone.Get(id).InBounds(msg) {
 						cmds = append(cmds, r.copyUrlCmd())
 					}
+				case tea.MouseRight:
+					if r.common.Zone.Get("repo-main").InBounds(msg) {
+						cmds = append(cmds, backCmd)
+					}
 				}
 			}
 		}
-	case CopyUrlMsg:
+	case CopyURLMsg:
 		r.common.Copy.Copy(
 			git.RepoURL(r.cfg.Host, r.cfg.Port, r.selectedRepo.Repo()),
 		)
-	case ResetUrlMsg:
-		r.copyUrl = time.Time{}
+	case ResetURLMsg:
+		r.copyURL = time.Time{}
 	case ReadmeMsg:
 	case FileItemsMsg:
 		f, cmd := r.panes[filesTab].Update(msg)
@@ -275,7 +283,10 @@ func (r *Repo) View() string {
 		r.common.Styles.Tabs.GetVerticalFrameSize()
 	mainStyle := repoBodyStyle.
 		Height(r.common.Height - hm)
-	main := r.panes[r.activeTab].View()
+	main := r.common.Zone.Mark(
+		"repo-main",
+		r.panes[r.activeTab].View(),
+	)
 	view := lipgloss.JoinVertical(lipgloss.Top,
 		r.headerView(),
 		r.tabs.View(),
@@ -306,7 +317,7 @@ func (r *Repo) headerView() string {
 		Width(r.common.Width - lipgloss.Width(desc) - 1).
 		Align(lipgloss.Right)
 	url := git.RepoURL(cfg.Host, cfg.Port, r.selectedRepo.Repo())
-	if !r.copyUrl.IsZero() && r.copyUrl.Add(time.Second).After(time.Now()) {
+	if !r.copyURL.IsZero() && r.copyURL.Add(time.Second).After(time.Now()) {
 		url = "copied!"
 	}
 	url = common.TruncateString(url, r.common.Width-lipgloss.Width(desc)-1)
@@ -368,13 +379,13 @@ func (r *Repo) updateModels(msg tea.Msg) tea.Cmd {
 }
 
 func (r *Repo) copyUrlCmd() tea.Cmd {
-	r.copyUrl = time.Now()
+	r.copyURL = time.Now()
 	return tea.Batch(
 		func() tea.Msg {
-			return CopyUrlMsg{}
+			return CopyURLMsg{}
 		},
 		tea.Tick(time.Second, func(time.Time) tea.Msg {
-			return ResetUrlMsg{}
+			return ResetURLMsg{}
 		}),
 	)
 }
@@ -382,3 +393,7 @@ func (r *Repo) copyUrlCmd() tea.Cmd {
 func updateStatusBarCmd() tea.Msg {
 	return UpdateStatusBarMsg{}
 }
+
+func backCmd() tea.Msg {
+	return BackMsg{}
+}