mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-02-09 13:47:11 +02:00
allow scoped mode where the commits/reflog/stash panels are scoped to a file
WIP restrict certain actions in scoped mode WIP
This commit is contained in:
parent
2756b82f57
commit
624ae45ebb
@ -89,6 +89,7 @@ Default path for the config file:
|
||||
prevScreenMode: '_'
|
||||
undo: 'z'
|
||||
redo: '<c-z>'
|
||||
scopingMenu: <c-s>
|
||||
status:
|
||||
checkForUpdate: 'u'
|
||||
recentRepos: '<enter>'
|
||||
|
5
main.go
5
main.go
@ -25,6 +25,9 @@ func main() {
|
||||
repoPath := "."
|
||||
flaggy.String(&repoPath, "p", "path", "Path of git repo")
|
||||
|
||||
logScope := ""
|
||||
flaggy.String(&logScope, "s", "scope", "scope for `git log`, typically the path of a file")
|
||||
|
||||
dump := ""
|
||||
flaggy.AddPositionalValue(&dump, "gitargs", 1, false, "Todo file")
|
||||
flaggy.DefaultParser.PositionalFlags[0].Hidden = true
|
||||
@ -61,7 +64,7 @@ func main() {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
app, err := app.NewApp(appConfig)
|
||||
app, err := app.NewApp(appConfig, logScope)
|
||||
|
||||
if err == nil {
|
||||
err = app.Run()
|
||||
|
@ -91,7 +91,7 @@ func newLogger(config config.AppConfigurer) *logrus.Entry {
|
||||
}
|
||||
|
||||
// NewApp bootstrap a new application
|
||||
func NewApp(config config.AppConfigurer) (*App, error) {
|
||||
func NewApp(config config.AppConfigurer, logScope string) (*App, error) {
|
||||
app := &App{
|
||||
closers: []io.Closer{},
|
||||
Config: config,
|
||||
@ -121,7 +121,7 @@ func NewApp(config config.AppConfigurer) (*App, error) {
|
||||
if err != nil {
|
||||
return app, err
|
||||
}
|
||||
app.Gui, err = gui.NewGui(app.Log, app.GitCommand, app.OSCommand, app.Tr, config, app.Updater)
|
||||
app.Gui, err = gui.NewGui(app.Log, app.GitCommand, app.OSCommand, app.Tr, config, app.Updater, logScope)
|
||||
if err != nil {
|
||||
return app, err
|
||||
}
|
||||
|
@ -83,15 +83,20 @@ func (c *CommitListBuilder) extractCommitFromLine(line string) *Commit {
|
||||
}
|
||||
}
|
||||
|
||||
type GetCommitsOptions struct {
|
||||
Limit bool
|
||||
LogScope string
|
||||
}
|
||||
|
||||
// GetCommits obtains the commits of the current branch
|
||||
func (c *CommitListBuilder) GetCommits(limit bool) ([]*Commit, error) {
|
||||
func (c *CommitListBuilder) GetCommits(options GetCommitsOptions) ([]*Commit, error) {
|
||||
commits := []*Commit{}
|
||||
var rebasingCommits []*Commit
|
||||
rebaseMode, err := c.GitCommand.RebaseMode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rebaseMode != "" {
|
||||
if rebaseMode != "" && options.LogScope == "" {
|
||||
// here we want to also prepend the commits that we're in the process of rebasing
|
||||
rebasingCommits, err = c.getRebasingCommits(rebaseMode)
|
||||
if err != nil {
|
||||
@ -103,7 +108,7 @@ func (c *CommitListBuilder) GetCommits(limit bool) ([]*Commit, error) {
|
||||
}
|
||||
|
||||
unpushedCommits := c.getUnpushedCommits()
|
||||
cmd := c.getLogCmd(limit)
|
||||
cmd := c.getLogCmd(options)
|
||||
|
||||
err = RunLineOutputCmd(cmd, func(line string) (bool, error) {
|
||||
commit := c.extractCommitFromLine(line)
|
||||
@ -294,11 +299,16 @@ func (c *CommitListBuilder) getUnpushedCommits() map[string]bool {
|
||||
}
|
||||
|
||||
// getLog gets the git log.
|
||||
func (c *CommitListBuilder) getLogCmd(limit bool) *exec.Cmd {
|
||||
func (c *CommitListBuilder) getLogCmd(options GetCommitsOptions) *exec.Cmd {
|
||||
limitFlag := ""
|
||||
if limit {
|
||||
if options.Limit {
|
||||
limitFlag = "-300"
|
||||
}
|
||||
|
||||
return c.OSCommand.ExecutableFromString(fmt.Sprintf("git log --oneline --pretty=format:\"%%H%s%%at%s%%aN%s%%d%s%%s\" %s --abbrev=%d --date=unix ", SEPARATION_CHAR, SEPARATION_CHAR, SEPARATION_CHAR, SEPARATION_CHAR, limitFlag, 20))
|
||||
scopeFlag := ""
|
||||
if options.LogScope != "" {
|
||||
scopeFlag = fmt.Sprintf(" -- %s", c.OSCommand.Quote(options.LogScope))
|
||||
}
|
||||
|
||||
return c.OSCommand.ExecutableFromString(fmt.Sprintf("git log --oneline --pretty=format:\"%%H%s%%at%s%%aN%s%%d%s%%s\" %s --abbrev=%d --date=unix %s", SEPARATION_CHAR, SEPARATION_CHAR, SEPARATION_CHAR, SEPARATION_CHAR, limitFlag, 20, scopeFlag))
|
||||
}
|
||||
|
@ -156,9 +156,7 @@ func findDotGitDir(stat func(string) (os.FileInfo, error), readFile func(filenam
|
||||
return strings.TrimSpace(strings.TrimPrefix(fileContent, "gitdir: ")), nil
|
||||
}
|
||||
|
||||
// GetStashEntries stash entries
|
||||
func (c *GitCommand) GetStashEntries() []*StashEntry {
|
||||
// if we directly put this string in RunCommandWithOutput the compiler complains because it thinks it's a format string
|
||||
func (c *GitCommand) getStashEntriesWithoutScope() []*StashEntry {
|
||||
unescaped := "git stash list --pretty='%gs'"
|
||||
rawString, _ := c.OSCommand.RunCommandWithOutput(unescaped)
|
||||
stashEntries := []*StashEntry{}
|
||||
@ -168,6 +166,45 @@ func (c *GitCommand) GetStashEntries() []*StashEntry {
|
||||
return stashEntries
|
||||
}
|
||||
|
||||
// GetStashEntries stash entries
|
||||
func (c *GitCommand) GetStashEntries(scope string) []*StashEntry {
|
||||
if scope == "" {
|
||||
return c.getStashEntriesWithoutScope()
|
||||
}
|
||||
|
||||
unescaped := fmt.Sprintf("git stash list --name-only")
|
||||
rawString, err := c.OSCommand.RunCommandWithOutput(unescaped)
|
||||
if err != nil {
|
||||
return c.getStashEntriesWithoutScope()
|
||||
}
|
||||
stashEntries := []*StashEntry{}
|
||||
var currentStashEntry *StashEntry
|
||||
lines := utils.SplitLines(rawString)
|
||||
isAStash := func(line string) bool { return strings.HasPrefix(line, "stash@{") }
|
||||
re := regexp.MustCompile(`stash@\{(\d+)\}`)
|
||||
|
||||
outer:
|
||||
for i := 0; i < len(lines); i++ {
|
||||
if !isAStash(lines[i]) {
|
||||
continue
|
||||
}
|
||||
match := re.FindStringSubmatch(lines[i])
|
||||
idx, err := strconv.Atoi(match[1])
|
||||
if err != nil {
|
||||
return c.getStashEntriesWithoutScope()
|
||||
}
|
||||
currentStashEntry = stashEntryFromLine(lines[i], idx)
|
||||
for i+1 < len(lines) && !isAStash(lines[i+1]) {
|
||||
i++
|
||||
if lines[i] == scope {
|
||||
stashEntries = append(stashEntries, currentStashEntry)
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
}
|
||||
return stashEntries
|
||||
}
|
||||
|
||||
func stashEntryFromLine(line string, index int) *StashEntry {
|
||||
return &StashEntry{
|
||||
Name: line,
|
||||
@ -568,8 +605,12 @@ func (c *GitCommand) Ignore(filename string) error {
|
||||
return c.OSCommand.AppendLineToFile(".gitignore", filename)
|
||||
}
|
||||
|
||||
func (c *GitCommand) ShowCmdStr(sha string) string {
|
||||
return fmt.Sprintf("git show --color=%s --no-renames --stat -p %s", c.colorArg(), sha)
|
||||
func (c *GitCommand) ShowCmdStr(sha string, scope string) string {
|
||||
scopeArg := ""
|
||||
if scope != "" {
|
||||
scopeArg = fmt.Sprintf(" -- %s", c.OSCommand.Quote(scope))
|
||||
}
|
||||
return fmt.Sprintf("git show --color=%s --no-renames --stat -p %s %s", c.colorArg(), sha, scopeArg)
|
||||
}
|
||||
|
||||
func (c *GitCommand) GetBranchGraphCmdStr(branchName string) string {
|
||||
@ -1121,11 +1162,17 @@ func (c *GitCommand) FetchRemote(remoteName string) error {
|
||||
|
||||
// GetReflogCommits only returns the new reflog commits since the given lastReflogCommit
|
||||
// if none is passed (i.e. it's value is nil) then we get all the reflog commits
|
||||
func (c *GitCommand) GetReflogCommits(lastReflogCommit *Commit) ([]*Commit, bool, error) {
|
||||
|
||||
func (c *GitCommand) GetReflogCommits(lastReflogCommit *Commit, scope string) ([]*Commit, bool, error) {
|
||||
commits := make([]*Commit, 0)
|
||||
re := regexp.MustCompile(`(\w+).*HEAD@\{([^\}]+)\}: (.*)`)
|
||||
|
||||
cmd := c.OSCommand.ExecutableFromString("git reflog --abbrev=20 --date=unix")
|
||||
scopeArg := ""
|
||||
if scope != "" {
|
||||
scopeArg = fmt.Sprintf(" -- %s", c.OSCommand.Quote(scope))
|
||||
}
|
||||
|
||||
cmd := c.OSCommand.ExecutableFromString(fmt.Sprintf("git reflog --abbrev=20 --date=unix %s", scopeArg))
|
||||
onlyObtainedNewReflogCommits := false
|
||||
err := RunLineOutputCmd(cmd, func(line string) (bool, error) {
|
||||
match := re.FindStringSubmatch(line)
|
||||
|
@ -312,7 +312,7 @@ func TestGitCommandGetStashEntries(t *testing.T) {
|
||||
gitCmd := NewDummyGitCommand()
|
||||
gitCmd.OSCommand.command = s.command
|
||||
|
||||
s.test(gitCmd.GetStashEntries())
|
||||
s.test(gitCmd.GetStashEntries(""))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -320,6 +320,7 @@ keybinding:
|
||||
prevScreenMode: '_'
|
||||
undo: 'z'
|
||||
redo: '<c-z>'
|
||||
scopingMenu: <c-s>
|
||||
status:
|
||||
checkForUpdate: 'u'
|
||||
recentRepos: '<enter>'
|
||||
|
@ -263,6 +263,10 @@ func (gui *Gui) deleteNamedBranch(g *gocui.Gui, v *gocui.View, selectedBranch *c
|
||||
}
|
||||
|
||||
func (gui *Gui) mergeBranchIntoCheckedOutBranch(branchName string) error {
|
||||
if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
if gui.GitCommand.IsHeadDetached() {
|
||||
return gui.createErrorPanel("Cannot merge branch in detached head state. You might have checked out a commit directly or a remote branch, in which case you should checkout the local branch you want to be on")
|
||||
}
|
||||
@ -286,6 +290,10 @@ func (gui *Gui) mergeBranchIntoCheckedOutBranch(branchName string) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMerge(g *gocui.Gui, v *gocui.View) error {
|
||||
if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
selectedBranchName := gui.getSelectedBranch().Name
|
||||
return gui.mergeBranchIntoCheckedOutBranch(selectedBranchName)
|
||||
}
|
||||
@ -296,6 +304,10 @@ func (gui *Gui) handleRebaseOntoLocalBranch(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleRebaseOntoBranch(selectedBranchName string) error {
|
||||
if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
checkedOutBranch := gui.getCheckedOutBranch().Name
|
||||
if selectedBranchName == checkedOutBranch {
|
||||
return gui.createErrorPanel(gui.Tr.SLocalize("CantRebaseOntoSelf"))
|
||||
|
@ -62,7 +62,7 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
gui.GitCommand.ShowCmdStr(commit.Sha),
|
||||
gui.GitCommand.ShowCmdStr(commit.Sha, gui.State.LogScope),
|
||||
)
|
||||
if err := gui.newPtyTask("main", cmd); err != nil {
|
||||
gui.Log.Error(err)
|
||||
@ -122,7 +122,7 @@ func (gui *Gui) refreshCommitsWithLimit() error {
|
||||
return err
|
||||
}
|
||||
|
||||
commits, err := builder.GetCommits(gui.State.Panels.Commits.LimitCommits)
|
||||
commits, err := builder.GetCommits(commands.GetCommitsOptions{Limit: gui.State.Panels.Commits.LimitCommits, LogScope: gui.State.LogScope})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -140,6 +140,10 @@ func (gui *Gui) refreshCommitsWithLimit() error {
|
||||
// specific functions
|
||||
|
||||
func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error {
|
||||
if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(gui.State.Commits) <= 1 {
|
||||
return gui.createErrorPanel(gui.Tr.SLocalize("YouNoCommitsToSquash"))
|
||||
}
|
||||
@ -161,6 +165,10 @@ func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error {
|
||||
if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(gui.State.Commits) <= 1 {
|
||||
return gui.createErrorPanel(gui.Tr.SLocalize("YouNoCommitsToSquash"))
|
||||
}
|
||||
@ -182,6 +190,10 @@ func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
applied, err := gui.handleMidRebaseCommand("reword")
|
||||
if err != nil {
|
||||
return err
|
||||
@ -203,6 +215,10 @@ func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleRenameCommitEditor(g *gocui.Gui, v *gocui.View) error {
|
||||
if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
applied, err := gui.handleMidRebaseCommand("reword")
|
||||
if err != nil {
|
||||
return err
|
||||
@ -249,6 +265,10 @@ func (gui *Gui) handleMidRebaseCommand(action string) (bool, error) {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitDelete(g *gocui.Gui, v *gocui.View) error {
|
||||
if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
applied, err := gui.handleMidRebaseCommand("drop")
|
||||
if err != nil {
|
||||
return err
|
||||
@ -266,6 +286,10 @@ func (gui *Gui) handleCommitDelete(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitMoveDown(g *gocui.Gui, v *gocui.View) error {
|
||||
if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
index := gui.State.Panels.Commits.SelectedLine
|
||||
selectedCommit := gui.State.Commits[index]
|
||||
if selectedCommit.Status == "rebasing" {
|
||||
@ -289,6 +313,10 @@ func (gui *Gui) handleCommitMoveDown(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitMoveUp(g *gocui.Gui, v *gocui.View) error {
|
||||
if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
index := gui.State.Panels.Commits.SelectedLine
|
||||
if index == 0 {
|
||||
return nil
|
||||
@ -312,6 +340,10 @@ func (gui *Gui) handleCommitMoveUp(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitEdit(g *gocui.Gui, v *gocui.View) error {
|
||||
if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
applied, err := gui.handleMidRebaseCommand("edit")
|
||||
if err != nil {
|
||||
return err
|
||||
@ -327,6 +359,10 @@ func (gui *Gui) handleCommitEdit(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitAmendTo(g *gocui.Gui, v *gocui.View) error {
|
||||
if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.createConfirmationPanel(gui.g, v, true, gui.Tr.SLocalize("AmendCommitTitle"), gui.Tr.SLocalize("AmendCommitPrompt"), func(*gocui.Gui, *gocui.View) error {
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("AmendingStatus"), func() error {
|
||||
err := gui.GitCommand.AmendTo(gui.State.Commits[gui.State.Panels.Commits.SelectedLine].Sha)
|
||||
@ -336,6 +372,10 @@ func (gui *Gui) handleCommitAmendTo(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitPick(g *gocui.Gui, v *gocui.View) error {
|
||||
if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
applied, err := gui.handleMidRebaseCommand("pick")
|
||||
if err != nil {
|
||||
return err
|
||||
@ -350,6 +390,10 @@ func (gui *Gui) handleCommitPick(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitRevert(g *gocui.Gui, v *gocui.View) error {
|
||||
if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.GitCommand.Revert(gui.State.Commits[gui.State.Panels.Commits.SelectedLine].Sha); err != nil {
|
||||
return gui.surfaceError(err)
|
||||
}
|
||||
@ -358,6 +402,10 @@ func (gui *Gui) handleCommitRevert(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCopyCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
// get currently selected commit, add the sha to state.
|
||||
commit := gui.State.Commits[gui.State.Panels.Commits.SelectedLine]
|
||||
|
||||
@ -397,6 +445,10 @@ func (gui *Gui) addCommitToCherryPickedCommits(index int) {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCopyCommitRange(g *gocui.Gui, v *gocui.View) error {
|
||||
if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
commitShaMap := gui.cherryPickedCommitShaMap()
|
||||
|
||||
// find the last commit that is copied that's above our position
|
||||
@ -419,6 +471,10 @@ func (gui *Gui) handleCopyCommitRange(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
// HandlePasteCommits begins a cherry-pick rebase with the commits the user has copied
|
||||
func (gui *Gui) HandlePasteCommits(g *gocui.Gui, v *gocui.View) error {
|
||||
if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("CherryPick"), gui.Tr.SLocalize("SureCherryPick"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("CherryPickingStatus"), func() error {
|
||||
err := gui.GitCommand.CherryPickCommits(gui.State.CherryPickedCommits)
|
||||
@ -495,6 +551,10 @@ func (gui *Gui) unchooseCommit(commits []*commands.Commit, i int) []*commands.Co
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateFixupCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
commit := gui.getSelectedCommit(g)
|
||||
if commit == nil {
|
||||
return nil
|
||||
@ -515,6 +575,10 @@ func (gui *Gui) handleCreateFixupCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSquashAllAboveFixupCommits(g *gocui.Gui, v *gocui.View) error {
|
||||
if ok, err := gui.validateNotInScopedMode(); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
commit := gui.getSelectedCommit(g)
|
||||
if commit == nil {
|
||||
return nil
|
||||
|
@ -50,6 +50,7 @@ type SentinelErrors struct {
|
||||
ErrSubProcess error
|
||||
ErrNoFiles error
|
||||
ErrSwitchRepo error
|
||||
ErrRestart error
|
||||
}
|
||||
|
||||
// GenerateSentinelErrors makes the sentinel errors for the gui. We're defining it here
|
||||
@ -67,6 +68,7 @@ func (gui *Gui) GenerateSentinelErrors() {
|
||||
ErrSubProcess: errors.New(gui.Tr.SLocalize("RunningSubprocess")),
|
||||
ErrNoFiles: errors.New(gui.Tr.SLocalize("NoChangedFiles")),
|
||||
ErrSwitchRepo: errors.New("switching repo"),
|
||||
ErrRestart: errors.New("restarting"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,13 +216,13 @@ type guiState struct {
|
||||
PrevMainWidth int
|
||||
PrevMainHeight int
|
||||
OldInformation string
|
||||
StartupStage int // one of INITIAL and COMPLETE. Allows us to not load everything at once
|
||||
StartupStage int // one of INITIAL and COMPLETE. Allows us to not load everything at once
|
||||
LogScope string // the filename that gets passed to git log
|
||||
}
|
||||
|
||||
// for now the split view will always be on
|
||||
|
||||
// NewGui builds a new gui handler
|
||||
func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, config config.AppConfigurer, updater *updates.Updater) (*Gui, error) {
|
||||
func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, config config.AppConfigurer, updater *updates.Updater, logScope string) (*Gui, error) {
|
||||
|
||||
initialState := &guiState{
|
||||
Files: make([]*commands.File, 0),
|
||||
@ -248,9 +250,9 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *comma
|
||||
EditHistory: stack.New(),
|
||||
},
|
||||
},
|
||||
ScreenMode: SCREEN_NORMAL,
|
||||
SideView: nil,
|
||||
Ptmx: nil,
|
||||
SideView: nil,
|
||||
Ptmx: nil,
|
||||
LogScope: logScope,
|
||||
}
|
||||
|
||||
gui := &Gui{
|
||||
@ -509,7 +511,9 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
donate := color.New(color.FgMagenta, color.Underline).Sprint(gui.Tr.SLocalize("Donate"))
|
||||
information = donate + " " + information
|
||||
}
|
||||
if len(gui.State.CherryPickedCommits) > 0 {
|
||||
if gui.inScopedMode() {
|
||||
information = utils.ColoredString(fmt.Sprintf("%s '%s' %s", gui.Tr.SLocalize("scopingTo"), gui.State.LogScope, utils.ColoredString(gui.Tr.SLocalize("(reset)"), color.Underline)), color.FgRed, color.Bold)
|
||||
} else if len(gui.State.CherryPickedCommits) > 0 {
|
||||
information = utils.ColoredString(fmt.Sprintf("%d commits copied", len(gui.State.CherryPickedCommits)), color.FgCyan)
|
||||
}
|
||||
|
||||
@ -799,11 +803,15 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
|
||||
if gui.g.CurrentView() == nil {
|
||||
if _, err := gui.g.SetCurrentView(gui.getFilesView().Name()); err != nil {
|
||||
initialView := gui.getFilesView()
|
||||
if gui.inScopedMode() {
|
||||
initialView = gui.getCommitsView()
|
||||
}
|
||||
if _, err := gui.g.SetCurrentView(initialView.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.switchFocus(gui.g, nil, gui.getFilesView()); err != nil {
|
||||
if err := gui.switchFocus(gui.g, nil, initialView); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -985,6 +993,12 @@ func (gui *Gui) Run() error {
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
if gui.inScopedMode() {
|
||||
gui.State.ScreenMode = SCREEN_HALF
|
||||
} else {
|
||||
gui.State.ScreenMode = SCREEN_NORMAL
|
||||
}
|
||||
|
||||
g.OnSearchEscape = gui.onSearchEscape
|
||||
g.SearchEscapeKey = gui.getKey("universal.return")
|
||||
g.NextSearchMatchKey = gui.getKey("universal.nextMatch")
|
||||
@ -1061,6 +1075,8 @@ func (gui *Gui) RunWithSubprocesses() error {
|
||||
break
|
||||
} else if err == gui.Errors.ErrSwitchRepo {
|
||||
continue
|
||||
} else if err == gui.Errors.ErrRestart {
|
||||
continue
|
||||
} else if err == gui.Errors.ErrSubProcess {
|
||||
if err := gui.runCommand(); err != nil {
|
||||
return err
|
||||
@ -1097,16 +1113,29 @@ func (gui *Gui) runCommand() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleDonate(g *gocui.Gui, v *gocui.View) error {
|
||||
func (gui *Gui) handleInfoClick(g *gocui.Gui, v *gocui.View) error {
|
||||
if !gui.g.Mouse {
|
||||
return nil
|
||||
}
|
||||
|
||||
cx, _ := v.Cursor()
|
||||
if cx > len(gui.Tr.SLocalize("Donate")) {
|
||||
return nil
|
||||
width, _ := v.Size()
|
||||
|
||||
// if we're in the normal context there will be a donate button here
|
||||
// if we have ('reset') at the end then
|
||||
if gui.inScopedMode() {
|
||||
if width-cx <= len(gui.Tr.SLocalize("(reset)")) {
|
||||
gui.State.LogScope = ""
|
||||
return gui.Errors.ErrRestart
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return gui.OSCommand.OpenLink("https://github.com/sponsors/jesseduffield")
|
||||
|
||||
if cx <= len(gui.Tr.SLocalize("Donate")) {
|
||||
return gui.OSCommand.OpenLink("https://github.com/sponsors/jesseduffield")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setColorScheme sets the color scheme for the app based on the user config
|
||||
@ -1147,3 +1176,14 @@ func (gui *Gui) handleMouseDownSecondary(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) inScopedMode() bool {
|
||||
return gui.State.LogScope != ""
|
||||
}
|
||||
|
||||
func (gui *Gui) validateNotInScopedMode() (bool, error) {
|
||||
if gui.inScopedMode() {
|
||||
return false, gui.createErrorPanel("command not available in scoped mode. Either exit scoped mode or restart lazygit")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
@ -938,7 +938,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
ViewName: "information",
|
||||
Key: gocui.MouseLeft,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleDonate,
|
||||
Handler: gui.handleInfoClick,
|
||||
},
|
||||
{
|
||||
ViewName: "commitFiles",
|
||||
@ -982,6 +982,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Handler: gui.handleEnterCommitFile,
|
||||
Description: gui.Tr.SLocalize("enterFile"),
|
||||
},
|
||||
{
|
||||
ViewName: "",
|
||||
Key: gui.getKey("universal.scopingMenu"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCreateScopingMenuPanel,
|
||||
Description: gui.Tr.SLocalize("openScopingMenu"),
|
||||
},
|
||||
{
|
||||
ViewName: "secondary",
|
||||
Key: gocui.MouseWheelUp,
|
||||
|
@ -59,6 +59,9 @@ func (gui *Gui) getPatchCommitIndex() int {
|
||||
}
|
||||
|
||||
func (gui *Gui) validateNormalWorkingTreeState() (bool, error) {
|
||||
if gui.GitCommand.WorkingTreeState() != "normal" {
|
||||
return false, gui.createErrorPanel(gui.Tr.SLocalize("CantPatchWhileRebasingError"))
|
||||
}
|
||||
if gui.GitCommand.WorkingTreeState() != "normal" {
|
||||
return false, gui.createErrorPanel(gui.Tr.SLocalize("CantPatchWhileRebasingError"))
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ func (gui *Gui) handleReflogCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
v.FocusPoint(0, gui.State.Panels.ReflogCommits.SelectedLine)
|
||||
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
gui.GitCommand.ShowCmdStr(commit.Sha),
|
||||
gui.GitCommand.ShowCmdStr(commit.Sha, gui.State.LogScope),
|
||||
)
|
||||
if err := gui.newPtyTask("main", cmd); err != nil {
|
||||
gui.Log.Error(err)
|
||||
@ -52,7 +52,7 @@ func (gui *Gui) refreshReflogCommits() error {
|
||||
lastReflogCommit = gui.State.ReflogCommits[0]
|
||||
}
|
||||
|
||||
commits, onlyObtainedNewReflogCommits, err := gui.GitCommand.GetReflogCommits(lastReflogCommit)
|
||||
commits, onlyObtainedNewReflogCommits, err := gui.GitCommand.GetReflogCommits(lastReflogCommit, gui.State.LogScope)
|
||||
if err != nil {
|
||||
return gui.surfaceError(err)
|
||||
}
|
||||
|
58
pkg/gui/scoping_menu_panel.go
Normal file
58
pkg/gui/scoping_menu_panel.go
Normal file
@ -0,0 +1,58 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
func (gui *Gui) handleCreateScopingMenuPanel(g *gocui.Gui, v *gocui.View) error {
|
||||
fileName := ""
|
||||
switch v.Name() {
|
||||
case "files":
|
||||
file, err := gui.getSelectedFile(gui.g)
|
||||
if err == nil {
|
||||
fileName = file.Name
|
||||
}
|
||||
case "commitFiles":
|
||||
file := gui.getSelectedCommitFile(gui.g)
|
||||
if file != nil {
|
||||
fileName = file.Name
|
||||
}
|
||||
}
|
||||
|
||||
menuItems := []*menuItem{}
|
||||
|
||||
if fileName != "" {
|
||||
menuItems = append(menuItems, &menuItem{
|
||||
displayString: fmt.Sprintf("%s '%s'", gui.Tr.SLocalize("scopeTo"), fileName),
|
||||
onPress: func() error {
|
||||
gui.State.LogScope = fileName
|
||||
return gui.Errors.ErrRestart
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
menuItems = append(menuItems, &menuItem{
|
||||
displayString: gui.Tr.SLocalize("fileToScopeToOption"),
|
||||
onPress: func() error {
|
||||
return gui.createPromptPanel(gui.g, v, gui.Tr.SLocalize("enterFileName"), "", func(g *gocui.Gui, promptView *gocui.View) error {
|
||||
gui.State.LogScope = strings.TrimSpace(promptView.Buffer())
|
||||
return gui.Errors.ErrRestart
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
if gui.inScopedMode() {
|
||||
menuItems = append(menuItems, &menuItem{
|
||||
displayString: gui.Tr.SLocalize("exitOutOfScopedMode"),
|
||||
onPress: func() error {
|
||||
gui.State.LogScope = ""
|
||||
return gui.Errors.ErrRestart
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return gui.createMenu(gui.Tr.SLocalize("scopingMenuTitle"), menuItems, createMenuOptions{showCancel: true})
|
||||
}
|
@ -47,7 +47,7 @@ func (gui *Gui) handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshStashEntries(g *gocui.Gui) error {
|
||||
gui.State.StashEntries = gui.GitCommand.GetStashEntries()
|
||||
gui.State.StashEntries = gui.GitCommand.GetStashEntries(gui.State.LogScope)
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Stash.SelectedLine, len(gui.State.StashEntries))
|
||||
|
||||
|
@ -1083,6 +1083,30 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "gotoBottom",
|
||||
Other: "scroll to bottom",
|
||||
}, &i18n.Message{
|
||||
ID: "scopingTo",
|
||||
Other: "scoping to",
|
||||
}, &i18n.Message{
|
||||
ID: "(reset)",
|
||||
Other: "(reset)",
|
||||
}, &i18n.Message{
|
||||
ID: "openScopingMenu",
|
||||
Other: "view scoping options",
|
||||
}, &i18n.Message{
|
||||
ID: "scopeTo",
|
||||
Other: "scope to",
|
||||
}, &i18n.Message{
|
||||
ID: "exitOutOfScopedMode",
|
||||
Other: "stop scoping",
|
||||
}, &i18n.Message{
|
||||
ID: "fileToScopeToOption",
|
||||
Other: "enter path to scope to",
|
||||
}, &i18n.Message{
|
||||
ID: "enterFileName",
|
||||
Other: "enter path:",
|
||||
}, &i18n.Message{
|
||||
ID: "scopingMenuTitle",
|
||||
Other: "scoping",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -39,8 +39,8 @@ func WithPadding(str string, padding int) string {
|
||||
|
||||
// ColoredString takes a string and a colour attribute and returns a colored
|
||||
// string with that attribute
|
||||
func ColoredString(str string, colorAttribute color.Attribute) string {
|
||||
colour := color.New(colorAttribute)
|
||||
func ColoredString(str string, colorAttributes ...color.Attribute) string {
|
||||
colour := color.New(colorAttributes...)
|
||||
return ColoredStringDirect(str, colour)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user