1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-12-04 10:34:55 +02:00
lazygit/pkg/gui/controllers/workspace_reset_controller.go
2023-08-08 22:01:43 +10:00

261 lines
6.9 KiB
Go

package controllers
import (
"bytes"
"fmt"
"math"
"math/rand"
"time"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
// this is in its own file given that the workspace controller file is already quite long
func (self *FilesController) createResetMenu() error {
red := style.FgRed
nukeStr := "git reset --hard HEAD && git clean -fd"
if len(self.c.Model().Submodules) > 0 {
nukeStr = fmt.Sprintf("%s (%s)", nukeStr, self.c.Tr.AndResetSubmodules)
}
menuItems := []*types.MenuItem{
{
LabelColumns: []string{
self.c.Tr.DiscardAllChangesToAllFiles,
red.Sprint(nukeStr),
},
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.NukeWorkingTree)
if err := self.c.Git().WorkingTree.ResetAndClean(); err != nil {
return self.c.Error(err)
}
if self.c.UserConfig.Gui.AnimateExplosion {
self.animateExplosion()
}
return self.c.Refresh(
types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}},
)
},
Key: 'x',
Tooltip: self.c.Tr.NukeDescription,
},
{
LabelColumns: []string{
self.c.Tr.DiscardAnyUnstagedChanges,
red.Sprint("git checkout -- ."),
},
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.DiscardUnstagedFileChanges)
if err := self.c.Git().WorkingTree.DiscardAnyUnstagedFileChanges(); err != nil {
return self.c.Error(err)
}
return self.c.Refresh(
types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}},
)
},
Key: 'u',
},
{
LabelColumns: []string{
self.c.Tr.DiscardUntrackedFiles,
red.Sprint("git clean -fd"),
},
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.RemoveUntrackedFiles)
if err := self.c.Git().WorkingTree.RemoveUntrackedFiles(); err != nil {
return self.c.Error(err)
}
return self.c.Refresh(
types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}},
)
},
Key: 'c',
},
{
LabelColumns: []string{
self.c.Tr.DiscardStagedChanges,
red.Sprint("stash staged and drop stash"),
},
Tooltip: self.c.Tr.DiscardStagedChangesDescription,
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.RemoveStagedFiles)
if !self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
return self.c.ErrorMsg(self.c.Tr.NoTrackedStagedFilesStash)
}
if err := self.c.Git().Stash.SaveStagedChanges("[lazygit] tmp stash"); err != nil {
return self.c.Error(err)
}
if err := self.c.Git().Stash.DropNewest(); err != nil {
return self.c.Error(err)
}
return self.c.Refresh(
types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}},
)
},
Key: 'S',
},
{
LabelColumns: []string{
self.c.Tr.SoftReset,
red.Sprint("git reset --soft HEAD"),
},
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.SoftReset)
if err := self.c.Git().WorkingTree.ResetSoft("HEAD"); err != nil {
return self.c.Error(err)
}
return self.c.Refresh(
types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}},
)
},
Key: 's',
},
{
LabelColumns: []string{
"mixed reset",
red.Sprint("git reset --mixed HEAD"),
},
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.MixedReset)
if err := self.c.Git().WorkingTree.ResetMixed("HEAD"); err != nil {
return self.c.Error(err)
}
return self.c.Refresh(
types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}},
)
},
Key: 'm',
},
{
LabelColumns: []string{
self.c.Tr.HardReset,
red.Sprint("git reset --hard HEAD"),
},
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.HardReset)
if err := self.c.Git().WorkingTree.ResetHard("HEAD"); err != nil {
return self.c.Error(err)
}
return self.c.Refresh(
types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}},
)
},
Key: 'h',
},
}
return self.c.Menu(types.CreateMenuOptions{Title: "", Items: menuItems})
}
func (self *FilesController) animateExplosion() {
self.Explode(self.c.Views().Files, func() {
err := self.c.PostRefreshUpdate(self.c.Contexts().Files)
if err != nil {
self.c.Log.Error(err)
}
})
}
// Animates an explosion within the view by drawing a bunch of flamey characters
func (self *FilesController) Explode(v *gocui.View, onDone func()) {
width := v.InnerWidth()
height := v.InnerHeight() + 1
styles := []style.TextStyle{
style.FgLightWhite.SetBold(),
style.FgYellow.SetBold(),
style.FgRed.SetBold(),
style.FgBlue.SetBold(),
style.FgBlack.SetBold(),
}
self.c.OnWorker(func(_ gocui.Task) {
max := 25
for i := 0; i < max; i++ {
image := getExplodeImage(width, height, i, max)
style := styles[(i*len(styles)/max)%len(styles)]
coloredImage := style.Sprint(image)
self.c.OnUIThread(func() error {
_ = v.SetOrigin(0, 0)
v.SetContent(coloredImage)
return nil
})
time.Sleep(time.Millisecond * 20)
}
self.c.OnUIThread(func() error {
v.Clear()
onDone()
return nil
})
})
}
// Render an explosion in the given bounds.
func getExplodeImage(width int, height int, frame int, max int) string {
// Predefine the explosion symbols
explosionChars := []rune{'*', '.', '@', '#', '&', '+', '%'}
// Initialize a buffer to build our string
var buf bytes.Buffer
// Initialize RNG seed
random := rand.New(rand.NewSource(time.Now().UnixNano()))
// calculate the center of explosion
centerX, centerY := width/2, height/2
// calculate the max radius (hypotenuse of the view)
maxRadius := math.Hypot(float64(centerX), float64(centerY))
// calculate frame as a proportion of max, apply square root to create the non-linear effect
progress := math.Sqrt(float64(frame) / float64(max))
// calculate radius of explosion according to frame and max
radius := progress * maxRadius * 2
// introduce a new radius for the inner boundary of the explosion (the shockwave effect)
var innerRadius float64
if progress > 0.5 {
innerRadius = (progress - 0.5) * 2 * maxRadius
}
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
// calculate distance from center, scale x by 2 to compensate for character aspect ratio
distance := math.Hypot(float64(x-centerX), float64(y-centerY)*2)
// if distance is less than radius and greater than innerRadius, draw explosion char
if distance <= radius && distance >= innerRadius {
// Make placement random and less likely as explosion progresses
if random.Float64() > progress {
// Pick a random explosion char
char := explosionChars[random.Intn(len(explosionChars))]
buf.WriteRune(char)
} else {
buf.WriteRune(' ')
}
} else {
// If not explosion, then it's empty space
buf.WriteRune(' ')
}
}
// End of line
if y < height-1 {
buf.WriteRune('\n')
}
}
return buf.String()
}