Merge branch 'master' into stash-untracked-changes

This commit is contained in:
Andrew Hynes
2022-10-06 22:59:06 -02:30
committed by GitHub
822 changed files with 4921 additions and 2627 deletions

View File

@@ -1,7 +1,7 @@
package controllers
import (
"io/ioutil"
"os"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
@@ -192,7 +192,7 @@ func (self *MergeConflictsController) HandleUndo() error {
self.c.LogAction("Restoring file to previous state")
self.c.LogCommand("Undoing last conflict resolution", false)
if err := ioutil.WriteFile(state.GetPath(), []byte(state.GetContent()), 0o644); err != nil {
if err := os.WriteFile(state.GetPath(), []byte(state.GetContent()), 0o644); err != nil {
return err
}
@@ -280,7 +280,7 @@ func (self *MergeConflictsController) resolveConflict(selection mergeconflicts.S
self.c.LogAction("Resolve merge conflict")
self.c.LogCommand(logStr, false)
state.PushContent(content)
return true, ioutil.WriteFile(state.GetPath(), []byte(content), 0o644)
return true, os.WriteFile(state.GetPath(), []byte(content), 0o644)
}
func (self *MergeConflictsController) onLastConflictResolved() error {

View File

@@ -22,9 +22,13 @@ func (gui *Gui) handleEditorKeypress(textArea *gocui.TextArea, key gocui.Key, ch
textArea.MoveCursorDown()
case key == gocui.KeyArrowUp:
textArea.MoveCursorUp()
case key == gocui.KeyArrowLeft:
case key == gocui.KeyArrowLeft && (mod&gocui.ModAlt) != 0:
textArea.MoveLeftWord()
case key == gocui.KeyArrowLeft || key == gocui.KeyCtrlB:
textArea.MoveCursorLeft()
case key == gocui.KeyArrowRight:
case key == gocui.KeyArrowRight && (mod&gocui.ModAlt) != 0:
textArea.MoveRightWord()
case key == gocui.KeyArrowRight || key == gocui.KeyCtrlF:
textArea.MoveCursorRight()
case key == newlineKey:
if allowMultiline {
@@ -38,10 +42,16 @@ func (gui *Gui) handleEditorKeypress(textArea *gocui.TextArea, key gocui.Key, ch
textArea.ToggleOverwrite()
case key == gocui.KeyCtrlU:
textArea.DeleteToStartOfLine()
case key == gocui.KeyCtrlK:
textArea.DeleteToEndOfLine()
case key == gocui.KeyCtrlA || key == gocui.KeyHome:
textArea.GoToStartOfLine()
case key == gocui.KeyCtrlE || key == gocui.KeyEnd:
textArea.GoToEndOfLine()
case key == gocui.KeyCtrlW:
textArea.BackSpaceWord()
case key == gocui.KeyCtrlY:
textArea.Yank()
// TODO: see if we need all three of these conditions: maybe the final one is sufficient
case ch != 0 && mod == 0 && unicode.IsPrint(ch):

View File

@@ -2,7 +2,7 @@ package gui
import (
"fmt"
"io/ioutil"
"io"
"log"
"os"
"strings"
@@ -185,7 +185,7 @@ type GuiRepoState struct {
// WindowViewNameMap is a mapping of windows to the current view of that window.
// Some views move between windows for example the commitFiles view and when cycling through
// side windows we need to know which view to give focus to for a given window
WindowViewNameMap map[string]string
WindowViewNameMap *utils.ThreadSafeMap[string, string]
// tells us whether we've set up our views for the current repo. We'll need to
// do this whenever we switch back and forth between repos to get the views
@@ -494,8 +494,6 @@ func (gui *Gui) Run(startArgs appTypes.StartArgs) error {
})
deadlock.Opts.Disable = !gui.Debug
gui.handleTestMode(startArgs.IntegrationTest)
gui.g.OnSearchEscape = gui.onSearchEscape
if err := gui.Config.ReloadUserConfig(); err != nil {
return nil
@@ -552,6 +550,8 @@ func (gui *Gui) Run(startArgs appTypes.StartArgs) error {
gui.c.Log.Info("starting main loop")
gui.handleTestMode(startArgs.IntegrationTest)
return gui.g.MainLoop()
}
@@ -656,13 +656,16 @@ func (gui *Gui) runSubprocess(cmdObj oscommands.ICmdObj) error { //nolint:unpara
err := subprocess.Run()
subprocess.Stdout = ioutil.Discard
subprocess.Stderr = ioutil.Discard
subprocess.Stdout = io.Discard
subprocess.Stderr = io.Discard
subprocess.Stdin = nil
if gui.Config.GetUserConfig().PromptToReturnFromSubprocess {
fmt.Fprintf(os.Stdout, "\n%s", style.FgGreen.Sprint(gui.Tr.PressEnterToReturn))
fmt.Scanln() // wait for enter press
// scan to buffer to prevent run unintentional operations when TUI resumes.
var buffer string
fmt.Scanln(&buffer) // wait for enter press
}
return err

View File

@@ -79,3 +79,11 @@ func (self *GuiDriver) MainView() *gocui.View {
func (self *GuiDriver) SecondaryView() *gocui.View {
return self.gui.secondaryView()
}
func (self *GuiDriver) View(viewName string) *gocui.View {
view, err := self.gui.g.View(viewName)
if err != nil {
panic(err)
}
return view
}

View File

@@ -27,11 +27,35 @@ func (gui *Gui) runTaskForView(view *gocui.View, task types.UpdateTask) error {
}
func (gui *Gui) moveMainContextPairToTop(pair types.MainContextPair) {
gui.setWindowContext(pair.Main)
gui.moveToTopOfWindow(pair.Main)
gui.moveMainContextToTop(pair.Main)
if pair.Secondary != nil {
gui.setWindowContext(pair.Secondary)
gui.moveToTopOfWindow(pair.Secondary)
gui.moveMainContextToTop(pair.Secondary)
}
}
func (gui *Gui) moveMainContextToTop(context types.Context) {
gui.setWindowContext(context)
view := context.GetView()
topView := gui.topViewInWindow(context.GetWindowName())
if topView == nil {
gui.Log.Error("unexpected: topView is nil")
return
}
if topView != view {
// We need to copy the content to avoid a flicker effect: If we're flicking
// through files in the files panel, we use a different view to render the
// files vs the directories, and if you select dir A, then file B, then dir
// C, you'll briefly see dir A's contents again before the view is updated.
// So here we're copying the content from the top window to avoid that
// flicker effect.
gui.g.CopyContent(topView, view)
if err := gui.g.SetViewOnTopOf(view.Name(), topView.Name()); err != nil {
gui.Log.Error(err)
}
}
}

View File

@@ -53,10 +53,17 @@ func (gui *Gui) modeStatuses() []modeStatus {
{
isActive: gui.State.Modes.CherryPicking.Active,
description: func() string {
copiedCount := len(gui.State.Modes.CherryPicking.CherryPickedCommits)
text := gui.c.Tr.LcCommitsCopied
if copiedCount == 1 {
text = gui.c.Tr.LcCommitCopied
}
return gui.withResetButton(
fmt.Sprintf(
"%d commits copied",
len(gui.State.Modes.CherryPicking.CherryPickedCommits),
"%d %s",
copiedCount,
text,
),
style.FgCyan,
)

View File

@@ -2,7 +2,6 @@ package gui
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -20,7 +19,7 @@ import (
func (gui *Gui) getCurrentBranch(path string) string {
readHeadFile := func(path string) (string, error) {
headFile, err := ioutil.ReadFile(filepath.Join(path, "HEAD"))
headFile, err := os.ReadFile(filepath.Join(path, "HEAD"))
if err == nil {
content := strings.TrimSpace(string(headFile))
refsPrefix := "ref: refs/heads/"
@@ -47,7 +46,7 @@ func (gui *Gui) getCurrentBranch(path string) string {
}
} else {
// worktree
if worktreeGitDir, err := ioutil.ReadFile(gitDirPath); err == nil {
if worktreeGitDir, err := os.ReadFile(gitDirPath); err == nil {
content := strings.TrimSpace(string(worktreeGitDir))
worktreePath := strings.TrimPrefix(content, "gitdir: ")
if branch, err := readHeadFile(worktreePath); err == nil {

View File

@@ -2,6 +2,7 @@ package custom_commands
import (
"strings"
"text/template"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands"
@@ -45,8 +46,9 @@ func (self *HandlerCreator) call(customCommand config.CustomCommand) func() erro
return func() error {
sessionState := self.sessionStateLoader.call()
promptResponses := make([]string, len(customCommand.Prompts))
form := make(map[string]string)
f := func() error { return self.finalHandler(customCommand, sessionState, promptResponses) }
f := func() error { return self.finalHandler(customCommand, sessionState, promptResponses, form) }
// if we have prompts we'll recursively wrap our confirm handlers with more prompts
// until we reach the actual command
@@ -60,10 +62,11 @@ func (self *HandlerCreator) call(customCommand config.CustomCommand) func() erro
wrappedF := func(response string) error {
promptResponses[idx] = response
form[prompt.Key] = response
return g()
}
resolveTemplate := self.getResolveTemplateFn(promptResponses, sessionState)
resolveTemplate := self.getResolveTemplateFn(form, promptResponses, sessionState)
resolvedPrompt, err := self.resolver.resolvePrompt(&prompt, resolveTemplate)
if err != nil {
return self.c.Error(err)
@@ -154,19 +157,25 @@ func (self *HandlerCreator) menuPromptFromCommand(prompt *config.CustomCommandPr
type CustomCommandObjects struct {
*SessionState
PromptResponses []string
Form map[string]string
}
func (self *HandlerCreator) getResolveTemplateFn(promptResponses []string, sessionState *SessionState) func(string) (string, error) {
func (self *HandlerCreator) getResolveTemplateFn(form map[string]string, promptResponses []string, sessionState *SessionState) func(string) (string, error) {
objects := CustomCommandObjects{
SessionState: sessionState,
PromptResponses: promptResponses,
Form: form,
}
return func(templateStr string) (string, error) { return utils.ResolveTemplate(templateStr, objects) }
funcs := template.FuncMap{
"quote": self.os.Quote,
}
return func(templateStr string) (string, error) { return utils.ResolveTemplate(templateStr, objects, funcs) }
}
func (self *HandlerCreator) finalHandler(customCommand config.CustomCommand, sessionState *SessionState, promptResponses []string) error {
resolveTemplate := self.getResolveTemplateFn(promptResponses, sessionState)
func (self *HandlerCreator) finalHandler(customCommand config.CustomCommand, sessionState *SessionState, promptResponses []string, form map[string]string) error {
resolveTemplate := self.getResolveTemplateFn(form, promptResponses, sessionState)
cmdStr, err := resolveTemplate(customCommand.Command)
if err != nil {
return self.c.Error(err)

View File

@@ -1,9 +1,6 @@
package custom_commands
import (
"bytes"
"text/template"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/config"
)
@@ -110,17 +107,3 @@ type CustomCommandObject struct {
PromptResponses []string
Form map[string]string
}
func ResolveTemplate(templateStr string, object interface{}) (string, error) {
tmpl, err := template.New("template").Parse(templateStr)
if err != nil {
return "", err
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, object); err != nil {
return "", err
}
return buf.String(), nil
}

View File

@@ -2,7 +2,6 @@ package gui
import (
"encoding/json"
"io/ioutil"
"log"
"os"
"strconv"
@@ -42,7 +41,7 @@ func (gui *Gui) handleTestMode(test integrationTypes.IntegrationTest) {
if Replaying() {
gui.g.RecordingConfig = gocui.RecordingConfig{
Speed: GetRecordingSpeed(),
Leeway: 100,
Leeway: 1000,
}
var err error
@@ -93,7 +92,7 @@ func GetRecordingSpeed() float64 {
func LoadRecording() (*gocui.Recording, error) {
path := os.Getenv("REPLAY_EVENTS_FROM")
data, err := ioutil.ReadFile(path)
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
@@ -120,5 +119,5 @@ func SaveRecording(recording *gocui.Recording) error {
path := recordEventsTo()
return ioutil.WriteFile(path, jsonEvents, 0o600)
return os.WriteFile(path, jsonEvents, 0o600)
}

View File

@@ -3,8 +3,10 @@ package gui
import (
"fmt"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
@@ -14,18 +16,18 @@ import (
// space. Right now most windows are 1:1 with views, except for commitFiles which
// is a view that moves between windows
func (gui *Gui) initialWindowViewNameMap(contextTree *context.ContextTree) map[string]string {
result := map[string]string{}
func (gui *Gui) initialWindowViewNameMap(contextTree *context.ContextTree) *utils.ThreadSafeMap[string, string] {
result := utils.NewThreadSafeMap[string, string]()
for _, context := range contextTree.Flatten() {
result[context.GetWindowName()] = context.GetViewName()
result.Set(context.GetWindowName(), context.GetViewName())
}
return result
}
func (gui *Gui) getViewNameForWindow(window string) string {
viewName, ok := gui.State.WindowViewNameMap[window]
viewName, ok := gui.State.WindowViewNameMap.Get(window)
if !ok {
panic(fmt.Sprintf("Viewname not found for window: %s", window))
}
@@ -50,7 +52,7 @@ func (gui *Gui) setWindowContext(c types.Context) {
gui.resetWindowContext(c)
}
gui.State.WindowViewNameMap[c.GetWindowName()] = c.GetViewName()
gui.State.WindowViewNameMap.Set(c.GetWindowName(), c.GetViewName())
}
func (gui *Gui) currentWindow() string {
@@ -59,39 +61,57 @@ func (gui *Gui) currentWindow() string {
// assumes the context's windowName has been set to the new window if necessary
func (gui *Gui) resetWindowContext(c types.Context) {
for windowName, viewName := range gui.State.WindowViewNameMap {
for _, windowName := range gui.State.WindowViewNameMap.Keys() {
viewName, ok := gui.State.WindowViewNameMap.Get(windowName)
if !ok {
continue
}
if viewName == c.GetViewName() && windowName != c.GetWindowName() {
for _, context := range gui.State.Contexts.Flatten() {
if context.GetKey() != c.GetKey() && context.GetWindowName() == windowName {
gui.State.WindowViewNameMap[windowName] = context.GetViewName()
gui.State.WindowViewNameMap.Set(windowName, context.GetViewName())
}
}
}
}
}
func (gui *Gui) moveToTopOfWindow(context types.Context) {
// moves given context's view to the top of the window and returns
// true if the view was not already on top.
func (gui *Gui) moveToTopOfWindow(context types.Context) bool {
view := context.GetView()
if view == nil {
return
return false
}
window := context.GetWindowName()
topView := gui.topViewInWindow(window)
if view.Name() == topView.Name() {
return false
} else {
if err := gui.g.SetViewOnTopOf(view.Name(), topView.Name()); err != nil {
gui.Log.Error(err)
}
return true
}
}
func (gui *Gui) topViewInWindow(windowName string) *gocui.View {
// now I need to find all views in that same window, via contexts. And I guess then I need to find the index of the highest view in that list.
viewNamesInWindow := gui.viewNamesInWindow(window)
viewNamesInWindow := gui.viewNamesInWindow(windowName)
// The views list is ordered highest-last, so we're grabbing the last view of the window
topView := view
var topView *gocui.View
for _, currentView := range gui.g.Views() {
if lo.Contains(viewNamesInWindow, currentView.Name()) {
topView = currentView
}
}
if err := gui.g.SetViewOnTopOf(view.Name(), topView.Name()); err != nil {
gui.Log.Error(err)
}
return topView
}
func (gui *Gui) viewNamesInWindow(windowName string) []string {