mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-10-05 21:46:49 -04:00
Stage and unstage diff lines from the focused main view
Space in the focused main view now acts on the selected lines the way it does in the staging view: over the working tree's unstaged changes it puts them into the index, and over the staged ones it takes them back out. Which of the two it does is a property of the pane, each side of the diff having one of its own. Nothing has to be entered first, and a selection reaching across several files of a directory's diff is applied as one patch per file. The rows on screen are only a picture of the diff, so the patch is built from the diff itself: each selected row's identity — which file, which line of it, and whether it is a deletion — is looked for in the file's own diff, and the lines that match are the ones the patch includes. Matching by position rather than by counting rows is what tells the two halves of a modified line apart, since the deletion and the addition replacing it sit at the same place in the new file. Panels other than the working tree offer no action on their diff yet, so space says nothing and does nothing there. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
720097da50
commit
d514cd400c
@@ -35,6 +35,15 @@ func (self *DiffLineHelper) DiffLinesInViewRange(view *gocui.View, first int, la
|
||||
return infos
|
||||
}
|
||||
|
||||
// ChangeLinesInViewRange returns the change lines — the additions and deletions —
|
||||
// among the diff lines shown by the rows in the inclusive view-line range. Those are
|
||||
// the lines a patch is built from: a patch carries whatever context it needs around
|
||||
// them by itself, so a selection contributes only its changes.
|
||||
func (self *DiffLineHelper) ChangeLinesInViewRange(view *gocui.View, first int, last int) []types.DiffLineInfo {
|
||||
return lo.Filter(self.DiffLinesInViewRange(view, first, last),
|
||||
func(info types.DiffLineInfo, _ int) bool { return info.IsChange() })
|
||||
}
|
||||
|
||||
// changeLines resolves view's rendered diff to one flag per buffer line: whether
|
||||
// that row is a change line (an addition or a deletion), as opposed to context, a
|
||||
// header, or a row whose identity couldn't be recovered. Those are the rows a
|
||||
|
||||
@@ -79,6 +79,15 @@ func (self *MainViewController) GetKeybindings(opts types.KeybindingsOpts) []*ty
|
||||
GetDisabledReason: self.diffSelectionDisabledReason,
|
||||
Tooltip: self.c.Tr.EditFileTooltip,
|
||||
},
|
||||
{
|
||||
Keys: opts.GetKeys(opts.Config.Universal.Select),
|
||||
Handler: self.primaryAction,
|
||||
Description: self.c.Tr.Stage,
|
||||
DescriptionFunc: self.workingTreeActionDescription(self.c.Tr.Stage),
|
||||
GetDisabledReason: self.diffSelectionDisabledReason,
|
||||
Tooltip: self.c.Tr.StageSelectionTooltip,
|
||||
DisplayOnScreen: true,
|
||||
},
|
||||
{
|
||||
Keys: opts.GetKeys(opts.Config.Universal.CopyToClipboard),
|
||||
Handler: self.copySelection,
|
||||
@@ -269,6 +278,36 @@ func (self *MainViewController) diffSource() types.FocusedMainViewDiffSource {
|
||||
return sidePanel.GetFocusedMainViewDiffSource()
|
||||
}
|
||||
|
||||
// focusedMainViewActions returns what the panel beneath the focused main view does to
|
||||
// a selection in its diff, or nil where it does nothing to it — a panel whose diff can
|
||||
// be read and copied but not acted on.
|
||||
func (self *MainViewController) focusedMainViewActions() types.FocusedMainViewActions {
|
||||
actions, _ := self.diffSource().(types.FocusedMainViewActions)
|
||||
return actions
|
||||
}
|
||||
|
||||
// primaryAction acts on the selected diff lines, leaving what that means to the panel
|
||||
// beneath — which also re-renders the diff, since it is the one that changed it.
|
||||
func (self *MainViewController) primaryAction() error {
|
||||
actions := self.focusedMainViewActions()
|
||||
if actions == nil {
|
||||
return nil
|
||||
}
|
||||
first, last := self.context.GetView().SelectedLineRange()
|
||||
return actions.PrimaryAction(self.context, first, last)
|
||||
}
|
||||
|
||||
// workingTreeActionDescription gives a command's description only where the command
|
||||
// applies — over the working tree's diff — so that it is listed there and nowhere else.
|
||||
func (self *MainViewController) workingTreeActionDescription(description string) func() string {
|
||||
return func() string {
|
||||
if self.diffMainViewType() != types.DiffMainViewTypeStaging {
|
||||
return ""
|
||||
}
|
||||
return description
|
||||
}
|
||||
}
|
||||
|
||||
// copySelection copies the selected diff lines to the clipboard — not as the diff
|
||||
// renderer drew them, but as they read in the diff itself, which is both what you meant
|
||||
// to copy and the only form a renderer can't have mangled. A selection that is all
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/jesseduffield/generics/set"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/patch"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// WorkingTreeDiffActions implements what the files panel offers on the diff it renders
|
||||
@@ -12,7 +20,7 @@ type WorkingTreeDiffActions struct {
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.FocusedMainViewDiffSource = &WorkingTreeDiffActions{}
|
||||
var _ types.FocusedMainViewActions = &WorkingTreeDiffActions{}
|
||||
|
||||
func NewWorkingTreeDiffActions(c *ControllerCommon) *WorkingTreeDiffActions {
|
||||
return &WorkingTreeDiffActions{c: c}
|
||||
@@ -42,3 +50,137 @@ func (self *WorkingTreeDiffActions) PlainDiff(pane types.DiffPaneContext, paths
|
||||
func (self *WorkingTreeDiffActions) showsStagedSide(pane types.DiffPaneContext) bool {
|
||||
return pane.GetKey() == self.c.Contexts().NormalSecondary.GetKey()
|
||||
}
|
||||
|
||||
// PrimaryAction stages the selected diff lines, or takes them back out of the index
|
||||
// when what is selected is the staged side of the diff.
|
||||
func (self *WorkingTreeDiffActions) PrimaryAction(pane types.DiffPaneContext, firstLineIdx int, lastLineIdx int) error {
|
||||
if self.c.UserConfig().Git.DiffContextSize == 0 {
|
||||
return fmt.Errorf(self.c.Tr.Actions.NotEnoughContextToStage,
|
||||
self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView)
|
||||
}
|
||||
|
||||
infos, onStagedSide, ok := self.diffLineSelection(pane, firstLineIdx, lastLineIdx)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Either way the patch goes to the index: forwards from the unstaged side to stage
|
||||
// it, backwards from the staged side to take it back out.
|
||||
return self.applyDiffLineSelection(infos, onStagedSide,
|
||||
git_commands.ApplyPatchOpts{Reverse: onStagedSide, Cached: true})
|
||||
}
|
||||
|
||||
// diffLineSelection resolves what the user has selected in a pane of the focused main
|
||||
// view to the change lines to act on, and reports whether they are the staged side of
|
||||
// the diff — which is a question about the pane, so it is the same for every file of a
|
||||
// directory's diff. ok is false when the selection holds no change line, in which case
|
||||
// there is nothing to act on.
|
||||
func (self *WorkingTreeDiffActions) diffLineSelection(
|
||||
pane types.DiffPaneContext, firstLineIdx int, lastLineIdx int,
|
||||
) (infos []types.DiffLineInfo, onStagedSide bool, ok bool) {
|
||||
infos = self.c.Helpers().DiffLine.ChangeLinesInViewRange(pane.GetView(), firstLineIdx, lastLineIdx)
|
||||
if len(infos) == 0 {
|
||||
return nil, false, false
|
||||
}
|
||||
return infos, self.showsStagedSide(pane), true
|
||||
}
|
||||
|
||||
// applyDiffLineSelection applies the selected change lines, a patch per file, and
|
||||
// re-renders what that changed. onStagedSide says which of the file's two diffs the
|
||||
// lines were selected in and so are to be found in; opts says how to apply them.
|
||||
func (self *WorkingTreeDiffActions) applyDiffLineSelection(
|
||||
infos []types.DiffLineInfo, onStagedSide bool, opts git_commands.ApplyPatchOpts,
|
||||
) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.ApplyPatch)
|
||||
|
||||
// A directory's diff spans several files, and a patch is of one file, so the
|
||||
// selected lines are grouped by the file they belong to and applied file by file.
|
||||
infosByFile := lo.GroupBy(infos, func(info types.DiffLineInfo) string { return info.Path })
|
||||
for path, fileInfos := range infosByFile {
|
||||
file := self.fileForDiffLinePath(path)
|
||||
if file == nil {
|
||||
continue
|
||||
}
|
||||
if err := self.applyDiffLines(file, fileInfos, onStagedSide, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Block input until the refresh has landed, so that a quick second keypress acts on
|
||||
// the diff as it now is rather than on the one we just changed.
|
||||
self.c.RefreshBlockingInput(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
|
||||
return nil
|
||||
}
|
||||
|
||||
// fileForDiffLinePath maps the absolute path a diff line carries to the working tree
|
||||
// file it belongs to, or nil for a path that is no file of this repo's working tree.
|
||||
func (self *WorkingTreeDiffActions) fileForDiffLinePath(path string) *models.File {
|
||||
relativePath, err := filepath.Rel(self.c.Git().RepoPaths.WorktreePath(), path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return self.context().FileTreeViewModel.GetFile(filepath.ToSlash(relativePath))
|
||||
}
|
||||
|
||||
// applyDiffLines applies the given change lines of one file — a line, a hunk, a range —
|
||||
// as a patch built from that file's own diff:
|
||||
//
|
||||
// - stage: read the unstaged diff, apply it to the index
|
||||
// - unstage: read the staged diff, apply it to the index backwards
|
||||
//
|
||||
// sourceCached names the diff the lines were selected in, which is where they are found
|
||||
// again; opts says how to apply what is built from them. The two are independent — a
|
||||
// discard reads one side and reverses it — so they are passed separately.
|
||||
//
|
||||
// Each selected line is looked for by where it sits in the file. This tells the two
|
||||
// halves of a modified line apart: the deletion and the addition replacing it share a
|
||||
// position in the new file and differ only in being a deletion. Context lines are not
|
||||
// selected: a patch of the lines you picked keeps whatever context it needs around
|
||||
// them by itself.
|
||||
func (self *WorkingTreeDiffActions) applyDiffLines(
|
||||
file *models.File, infos []types.DiffLineInfo, sourceCached bool, opts git_commands.ApplyPatchOpts,
|
||||
) error {
|
||||
parsedPatch := patch.Parse(self.c.Git().WorkingTree.WorktreeFileDiff(file, true, sourceCached))
|
||||
|
||||
type changeLine struct {
|
||||
lineNumber int
|
||||
isDeletion bool
|
||||
}
|
||||
selected := set.New[changeLine]()
|
||||
for _, info := range infos {
|
||||
if info.Type == types.DiffLineDeleted {
|
||||
selected.Add(changeLine{info.OldLine, true})
|
||||
} else {
|
||||
selected.Add(changeLine{info.NewLine, false})
|
||||
}
|
||||
}
|
||||
|
||||
var patchLineIndices []int
|
||||
for idx, line := range parsedPatch.Lines() {
|
||||
var key changeLine
|
||||
switch {
|
||||
case line.IsAddition():
|
||||
key = changeLine{parsedPatch.LineNumberOfLine(idx), false}
|
||||
case line.IsDeletion():
|
||||
key = changeLine{parsedPatch.OldLineNumberOfLine(idx), true}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if selected.Includes(key) {
|
||||
patchLineIndices = append(patchLineIndices, idx)
|
||||
}
|
||||
}
|
||||
|
||||
patchToApply := parsedPatch.
|
||||
Transform(patch.TransformOpts{
|
||||
Reverse: opts.Reverse,
|
||||
IncludedLineIndices: patchLineIndices,
|
||||
FileNameOverride: file.GetPath(),
|
||||
}).
|
||||
FormatPlain()
|
||||
if patchToApply == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.c.Git().Patch.ApplyPatch(patchToApply, opts)
|
||||
}
|
||||
|
||||
@@ -239,6 +239,23 @@ type FocusedMainViewDiffSource interface {
|
||||
PlainDiff(pane DiffPaneContext, paths []string) string
|
||||
}
|
||||
|
||||
// FocusedMainViewActions describes what a side panel does when the user acts on a
|
||||
// selection of diff lines in the focused main view. The main view owns the selection
|
||||
// and the keys; what acting on it means is the panel's business, e.g. the working
|
||||
// tree panel stages and unstages.
|
||||
//
|
||||
// It extends the diff source rather than standing beside it, because acting on a
|
||||
// selection needs the diff behind the rendering just as reading it does; a panel that
|
||||
// implements only the source offers a diff to read and copy but nothing to do to it.
|
||||
type FocusedMainViewActions interface {
|
||||
FocusedMainViewDiffSource
|
||||
|
||||
// PrimaryAction acts on the diff lines in the inclusive view-line range, which is
|
||||
// the current selection in the given pane: a single line, a range, or a hunk. The
|
||||
// panel re-renders the diff itself, being the one that knows what it did to it.
|
||||
PrimaryAction(pane DiffPaneContext, firstLineIdx int, lastLineIdx int) error
|
||||
}
|
||||
|
||||
type IListContext interface {
|
||||
Context
|
||||
|
||||
|
||||
@@ -6,16 +6,18 @@ import (
|
||||
)
|
||||
|
||||
var NoSelectionOverABinaryDiff = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "A diff with nothing selectable in it shows no selection, and a refresh doesn't bring one",
|
||||
Description: "A diff with nothing selectable in it shows no selection, however it came to be showing",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(cfg *config.AppConfig) {
|
||||
cfg.GetUserConfig().Gui.UseHunkModeInStagingView = false
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("text", "one\ntwo\nthree\n")
|
||||
shell.CreateFileAndAdd("binary", "\x00one\x00two\x00")
|
||||
shell.Commit("one")
|
||||
|
||||
shell.UpdateFile("text", "one\nTWO\nthree\n")
|
||||
shell.UpdateFile("binary", "\x00one\x00TWO\x00")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
@@ -23,6 +25,7 @@ var NoSelectionOverABinaryDiff = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
// refresh, which renders the same diff again, doesn't make one appear.
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
NavigateToLine(Contains("binary")).
|
||||
Press(keys.Universal.FocusMainView)
|
||||
|
||||
t.Views().Main().
|
||||
@@ -32,5 +35,22 @@ var NoSelectionOverABinaryDiff = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
t.GlobalPress(keys.Universal.Refresh)
|
||||
}).
|
||||
SelectionIsHidden()
|
||||
|
||||
// The same when acting on a diff of several files leaves nothing selectable in
|
||||
// it: staging the text file's only change leaves the binary one behind.
|
||||
t.Views().Files().
|
||||
Focus().
|
||||
NavigateToLine(Contains("▼ /")).
|
||||
Press(keys.Universal.FocusMainView)
|
||||
|
||||
t.Views().Main().
|
||||
IsFocused().
|
||||
SelectionIsActive().
|
||||
SelectedLines(
|
||||
Contains("-two"),
|
||||
).
|
||||
Press(keys.Main.ToggleSelectHunk).
|
||||
PressPrimaryAction().
|
||||
SelectionIsHidden()
|
||||
},
|
||||
})
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package main_view
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var StageDiffLines = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Stage a line and a hunk of the working tree's diff from the focused main view",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(cfg *config.AppConfig) {
|
||||
cfg.GetUserConfig().Gui.UseHunkModeInStagingView = false
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("file1", "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\n")
|
||||
shell.Commit("one")
|
||||
|
||||
// Two change blocks, far enough apart to stay separate hunks.
|
||||
shell.UpdateFile("file1", "one\ntwo\nADD1\nADD2\nthree\nfour\nfive\nsix\nseven\neight\nNINE\nten\n")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("file1").IsSelected(),
|
||||
).
|
||||
Press(keys.Universal.FocusMainView)
|
||||
|
||||
// A single line goes into the index by itself, leaving the rest of its block
|
||||
// unstaged — which is the whole point of staging from the diff.
|
||||
t.Views().Main().
|
||||
IsFocused().
|
||||
SelectedLines(
|
||||
Contains("+ADD1"),
|
||||
).
|
||||
PressPrimaryAction()
|
||||
|
||||
t.Views().Files().Lines(
|
||||
Contains("MM file1"),
|
||||
)
|
||||
t.Views().Secondary().
|
||||
ContainsLines(
|
||||
Contains("+ADD1"),
|
||||
).
|
||||
Content(DoesNotContain("+ADD2"))
|
||||
t.Views().Main().
|
||||
Content(DoesNotContain("+ADD1")).
|
||||
ContainsLines(
|
||||
Contains("+ADD2"),
|
||||
)
|
||||
|
||||
// A whole change block goes in one press.
|
||||
t.Views().Main().
|
||||
IsFocused().
|
||||
NavigateToLine(Contains("-nine")).
|
||||
Press(keys.Main.ToggleSelectHunk).
|
||||
SelectedLines(
|
||||
Contains("-nine"),
|
||||
Contains("+NINE"),
|
||||
).
|
||||
PressPrimaryAction()
|
||||
|
||||
t.Views().Secondary().ContainsLines(
|
||||
Contains("-nine"),
|
||||
Contains("+NINE"),
|
||||
)
|
||||
t.Views().Main().Content(DoesNotContain("NINE"))
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,52 @@
|
||||
package main_view
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var StageDiffLinesOfAPathWithASpace = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Stage a line of a file whose path contains a space from the focused main view",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(cfg *config.AppConfig) {
|
||||
cfg.GetUserConfig().Gui.UseHunkModeInStagingView = false
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
// git ends the path field of the diff's "---" and "+++" lines with a tab
|
||||
// when the path contains a space, and the view shows the tab as spaces.
|
||||
shell.CreateFileAndAdd("my file", "one\ntwo\nthree\n")
|
||||
shell.Commit("one")
|
||||
|
||||
shell.UpdateFile("my file", "one\nADD1\nADD2\ntwo\nthree\n")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("my file").IsSelected(),
|
||||
).
|
||||
Press(keys.Universal.FocusMainView)
|
||||
|
||||
t.Views().Main().
|
||||
IsFocused().
|
||||
SelectedLines(
|
||||
Contains("+ADD1"),
|
||||
).
|
||||
PressPrimaryAction()
|
||||
|
||||
t.Views().Files().Lines(
|
||||
Contains("MM my file"),
|
||||
)
|
||||
t.Views().Secondary().
|
||||
ContainsLines(
|
||||
Contains("+ADD1"),
|
||||
).
|
||||
Content(DoesNotContain("+ADD2"))
|
||||
t.Views().Main().
|
||||
Content(DoesNotContain("+ADD1")).
|
||||
ContainsLines(
|
||||
Contains("+ADD2"),
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,55 @@
|
||||
package main_view
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var StageRangeSpanningFiles = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Stage a range reaching from one file's diff into another's, in a directory's focused main view",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(cfg *config.AppConfig) {
|
||||
cfg.GetUserConfig().Gui.UseHunkModeInStagingView = false
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("fileA", "a\n")
|
||||
shell.CreateFileAndAdd("fileB", "b\n")
|
||||
shell.CreateFileAndAdd("fileC", "c\n")
|
||||
shell.Commit("one")
|
||||
|
||||
shell.UpdateFile("fileA", "a\nfromA\n")
|
||||
shell.UpdateFile("fileB", "b\nfromB\n")
|
||||
shell.UpdateFile("fileC", "c\nfromC\n")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
// With the root of the tree selected, the main view shows all three files' diffs.
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("▼ /").IsSelected(),
|
||||
Contains(" M fileA"),
|
||||
Contains(" M fileB"),
|
||||
Contains(" M fileC"),
|
||||
).
|
||||
Press(keys.Universal.FocusMainView)
|
||||
|
||||
t.Views().Main().
|
||||
IsFocused().
|
||||
SelectedLines(
|
||||
Contains("+fromA"),
|
||||
).
|
||||
Press(keys.Universal.ToggleRangeSelect).
|
||||
NavigateToLine(Contains("+fromB")).
|
||||
PressPrimaryAction()
|
||||
|
||||
// Both files the range reached into are staged, each by its own patch, and the
|
||||
// file below it is untouched.
|
||||
t.Views().Files().Lines(
|
||||
Contains("▼ /"),
|
||||
Contains("M fileA"),
|
||||
Contains("M fileB"),
|
||||
Contains(" M fileC"),
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,57 @@
|
||||
package main_view
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var UnstageDiffLines = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Take a line back out of the index from the staged half of the focused main view",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(cfg *config.AppConfig) {
|
||||
cfg.GetUserConfig().Gui.UseHunkModeInStagingView = false
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("file1", "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\n")
|
||||
shell.Commit("one")
|
||||
|
||||
// Two staged additions, far enough apart to be separate hunks, plus an unstaged
|
||||
// one, so that the diff is split into a staged and an unstaged half.
|
||||
shell.UpdateFileAndAdd("file1", "one\nSTAGED1\ntwo\nthree\nfour\nfive\nsix\nseven\nSTAGED2\neight\nnine\nten\n")
|
||||
shell.UpdateFile("file1", "one\nSTAGED1\ntwo\nthree\nUNSTAGED\nfour\nfive\nsix\nseven\nSTAGED2\neight\nnine\nten\n")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("MM file1").IsSelected(),
|
||||
).
|
||||
Press(keys.Universal.FocusMainView)
|
||||
|
||||
// The main half holds the unstaged changes; the staged ones are next door.
|
||||
t.Views().Main().
|
||||
IsFocused().
|
||||
SelectedLines(
|
||||
Contains("+UNSTAGED"),
|
||||
).
|
||||
Press(keys.Universal.TogglePanel)
|
||||
|
||||
t.Views().Secondary().
|
||||
IsFocused().
|
||||
SelectedLines(
|
||||
Contains("+STAGED1"),
|
||||
).
|
||||
PressPrimaryAction()
|
||||
|
||||
// The line acted on is out of the index, and the one below it stays in.
|
||||
t.Views().Secondary().
|
||||
Content(DoesNotContain("+STAGED1")).
|
||||
ContainsLines(
|
||||
Contains("+STAGED2"),
|
||||
)
|
||||
t.Views().Main().ContainsLines(
|
||||
Contains("+STAGED1"),
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -408,6 +408,10 @@ var tests = []*components.IntegrationTest{
|
||||
main_view.SelectVisibleHunkOnFocusingMainView,
|
||||
main_view.SelectionCommandsOnlyWhereTheyApply,
|
||||
main_view.SelectionOverTheCustomPatch,
|
||||
main_view.StageDiffLines,
|
||||
main_view.StageDiffLinesOfAPathWithASpace,
|
||||
main_view.StageRangeSpanningFiles,
|
||||
main_view.UnstageDiffLines,
|
||||
misc.ConfirmOnQuit,
|
||||
misc.CopyConfirmationMessageToClipboard,
|
||||
misc.CopyToClipboard,
|
||||
|
||||
Reference in New Issue
Block a user