1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-07-28 16:02:01 +03:00

another integration test

This commit is contained in:
Jesse Duffield
2022-08-08 21:32:58 +10:00
parent 77881a9c7d
commit 225c563c63
46 changed files with 326 additions and 26 deletions

View File

@ -2,8 +2,10 @@ package gui
import ( import (
"fmt" "fmt"
"strings"
"time" "time"
guiTypes "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/integration/types" "github.com/jesseduffield/lazygit/pkg/integration/types"
) )
@ -67,8 +69,23 @@ func (self *AssertImpl) CurrentBranchName(expectedViewName string) {
}) })
} }
func (self *AssertImpl) InListContext() {
self.assertWithRetries(func() (bool, string) {
currentContext := self.gui.currentContext()
_, ok := currentContext.(guiTypes.IListContext)
return ok, fmt.Sprintf("Expected current context to be a list context, but got %s", currentContext.GetKey())
})
}
func (self *AssertImpl) SelectedLineContains(text string) {
self.assertWithRetries(func() (bool, string) {
line := self.gui.currentContext().GetView().SelectedLine()
return strings.Contains(line, text), fmt.Sprintf("Expected selected line to contain '%s', but got '%s'", text, line)
})
}
func (self *AssertImpl) assertWithRetries(test func() (bool, string)) { func (self *AssertImpl) assertWithRetries(test func() (bool, string)) {
waitTimes := []int{0, 100, 200, 400, 800, 1600} waitTimes := []int{0, 1, 5, 10, 200, 500, 1000}
var message string var message string
for _, waitTime := range waitTimes { for _, waitTime := range waitTimes {
@ -81,6 +98,10 @@ func (self *AssertImpl) assertWithRetries(test func() (bool, string)) {
} }
} }
self.Fail(message)
}
func (self *AssertImpl) Fail(message string) {
self.gui.g.Close() self.gui.g.Close()
// need to give the gui time to close // need to give the gui time to close
time.Sleep(time.Millisecond * 100) time.Sleep(time.Millisecond * 100)

View File

@ -1,29 +1,45 @@
package gui package gui
import ( import (
"fmt"
"strings"
"time" "time"
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings" "github.com/jesseduffield/lazygit/pkg/gui/keybindings"
guiTypes "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/integration/types" "github.com/jesseduffield/lazygit/pkg/integration/types"
) )
type InputImpl struct { type InputImpl struct {
g *gocui.Gui gui *Gui
keys config.KeybindingConfig keys config.KeybindingConfig
assert types.Assert
pushKeyDelay int
}
func NewInputImpl(gui *Gui, keys config.KeybindingConfig, assert types.Assert, pushKeyDelay int) *InputImpl {
return &InputImpl{
gui: gui,
keys: keys,
assert: assert,
pushKeyDelay: pushKeyDelay,
}
} }
var _ types.Input = &InputImpl{} var _ types.Input = &InputImpl{}
func (self *InputImpl) PushKeys(keyStrs ...string) { func (self *InputImpl) PressKeys(keyStrs ...string) {
for _, keyStr := range keyStrs { for _, keyStr := range keyStrs {
self.pushKey(keyStr) self.pressKey(keyStr)
} }
} }
func (self *InputImpl) pushKey(keyStr string) { func (self *InputImpl) pressKey(keyStr string) {
self.Wait(self.pushKeyDelay)
key := keybindings.GetKey(keyStr) key := keybindings.GetKey(keyStr)
var r rune var r rune
@ -36,58 +52,115 @@ func (self *InputImpl) pushKey(keyStr string) {
tcellKey = tcell.Key(v) tcellKey = tcell.Key(v)
} }
self.g.ReplayedEvents.Keys <- gocui.NewTcellKeyEventWrapper( self.gui.g.ReplayedEvents.Keys <- gocui.NewTcellKeyEventWrapper(
tcell.NewEventKey(tcellKey, r, tcell.ModNone), tcell.NewEventKey(tcellKey, r, tcell.ModNone),
0, 0,
) )
} }
func (self *InputImpl) SwitchToStatusWindow() { func (self *InputImpl) SwitchToStatusWindow() {
self.pushKey(self.keys.Universal.JumpToBlock[0]) self.pressKey(self.keys.Universal.JumpToBlock[0])
} }
func (self *InputImpl) SwitchToFilesWindow() { func (self *InputImpl) SwitchToFilesWindow() {
self.pushKey(self.keys.Universal.JumpToBlock[1]) self.pressKey(self.keys.Universal.JumpToBlock[1])
} }
func (self *InputImpl) SwitchToBranchesWindow() { func (self *InputImpl) SwitchToBranchesWindow() {
self.pushKey(self.keys.Universal.JumpToBlock[2]) self.pressKey(self.keys.Universal.JumpToBlock[2])
} }
func (self *InputImpl) SwitchToCommitsWindow() { func (self *InputImpl) SwitchToCommitsWindow() {
self.pushKey(self.keys.Universal.JumpToBlock[3]) self.pressKey(self.keys.Universal.JumpToBlock[3])
} }
func (self *InputImpl) SwitchToStashWindow() { func (self *InputImpl) SwitchToStashWindow() {
self.pushKey(self.keys.Universal.JumpToBlock[4]) self.pressKey(self.keys.Universal.JumpToBlock[4])
} }
func (self *InputImpl) Type(content string) { func (self *InputImpl) Type(content string) {
for _, char := range content { for _, char := range content {
self.pushKey(string(char)) self.pressKey(string(char))
} }
} }
func (self *InputImpl) Confirm() { func (self *InputImpl) Confirm() {
self.pushKey(self.keys.Universal.Confirm) self.pressKey(self.keys.Universal.Confirm)
} }
func (self *InputImpl) Cancel() { func (self *InputImpl) Cancel() {
self.pushKey(self.keys.Universal.Return) self.pressKey(self.keys.Universal.Return)
} }
func (self *InputImpl) Select() { func (self *InputImpl) Select() {
self.pushKey(self.keys.Universal.Select) self.pressKey(self.keys.Universal.Select)
} }
func (self *InputImpl) NextItem() { func (self *InputImpl) NextItem() {
self.pushKey(self.keys.Universal.NextItem) self.pressKey(self.keys.Universal.NextItem)
} }
func (self *InputImpl) PreviousItem() { func (self *InputImpl) PreviousItem() {
self.pushKey(self.keys.Universal.PrevItem) self.pressKey(self.keys.Universal.PrevItem)
}
func (self *InputImpl) ContinueMerge() {
self.PressKeys(self.keys.Universal.CreateRebaseOptionsMenu)
self.assert.SelectedLineContains("continue")
self.Confirm()
}
func (self *InputImpl) ContinueRebase() {
self.ContinueMerge()
} }
func (self *InputImpl) Wait(milliseconds int) { func (self *InputImpl) Wait(milliseconds int) {
time.Sleep(time.Duration(milliseconds) * time.Millisecond) time.Sleep(time.Duration(milliseconds) * time.Millisecond)
} }
func (self *InputImpl) log(message string) {
self.gui.c.LogAction(message)
}
// NOTE: this currently assumes that ViewBufferLines returns all the lines that can be accessed.
// If this changes in future, we'll need to update this code to first attempt to find the item
// in the current page and failing that, jump to the top of the view and iterate through all of it,
// looking for the item.
func (self *InputImpl) NavigateToListItemContainingText(text string) {
self.assert.InListContext()
currentContext := self.gui.currentContext().(guiTypes.IListContext)
view := currentContext.GetView()
// first we look for a duplicate on the current screen. We won't bother looking beyond that though.
matchCount := 0
matchIndex := -1
for i, line := range view.ViewBufferLines() {
if strings.Contains(line, text) {
matchCount++
matchIndex = i
}
}
if matchCount > 1 {
self.assert.Fail(fmt.Sprintf("Found %d matches for %s, expected only a single match", matchCount, text))
}
if matchCount == 1 {
selectedLineIdx := view.SelectedLineIdx()
if selectedLineIdx == matchIndex {
return
}
if selectedLineIdx < matchIndex {
for i := selectedLineIdx; i < matchIndex; i++ {
self.NextItem()
}
return
} else {
for i := selectedLineIdx; i > matchIndex; i-- {
self.PreviousItem()
}
return
}
}
self.assert.Fail(fmt.Sprintf("Could not find item containing text: %s", text))
}

View File

@ -21,10 +21,15 @@ func (gui *Gui) handleTestMode() {
go func() { go func() {
time.Sleep(time.Millisecond * 100) time.Sleep(time.Millisecond * 100)
shell := &integration.ShellImpl{}
assert := &AssertImpl{gui: gui}
keys := gui.Config.GetUserConfig().Keybinding
input := NewInputImpl(gui, keys, assert, integration.KeyPressDelay())
test.Run( test.Run(
&integration.ShellImpl{}, shell,
&InputImpl{g: gui.g, keys: gui.Config.GetUserConfig().Keybinding}, input,
&AssertImpl{gui: gui}, assert,
gui.c.UserConfig.Keybinding, gui.c.UserConfig.Keybinding,
) )

View File

@ -2,6 +2,7 @@ package integration
import ( import (
"os" "os"
"strconv"
"github.com/jesseduffield/generics/slices" "github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/integration/types" "github.com/jesseduffield/lazygit/pkg/integration/types"
@ -31,6 +32,21 @@ func PlayingIntegrationTest() bool {
return IntegrationTestName() != "" return IntegrationTestName() != ""
} }
// this is the delay in milliseconds between keypresses
// defaults to zero
func KeyPressDelay() int {
delayStr := os.Getenv("KEY_PRESS_DELAY")
if delayStr == "" {
return 0
}
delay, err := strconv.Atoi(delayStr)
if err != nil {
panic(err)
}
return delay
}
// OLD integration test format stuff // OLD integration test format stuff
func Replaying() bool { func Replaying() bool {

View File

@ -23,12 +23,12 @@ var Suggestions = types.NewTest(types.NewTestArgs{
Run: func(shell types.Shell, input types.Input, assert types.Assert, keys config.KeybindingConfig) { Run: func(shell types.Shell, input types.Input, assert types.Assert, keys config.KeybindingConfig) {
input.SwitchToBranchesWindow() input.SwitchToBranchesWindow()
input.PushKeys(keys.Branches.CheckoutBranchByName) input.PressKeys(keys.Branches.CheckoutBranchByName)
assert.CurrentViewName("confirmation") assert.CurrentViewName("confirmation")
input.Type("branch-to") input.Type("branch-to")
input.PushKeys(keys.Universal.TogglePanel) input.PressKeys(keys.Universal.TogglePanel)
assert.CurrentViewName("suggestions") assert.CurrentViewName("suggestions")
// we expect the first suggestion to be the branch we want because it most // we expect the first suggestion to be the branch we want because it most

View File

@ -20,7 +20,7 @@ var Commit = types.NewTest(types.NewTestArgs{
input.Select() input.Select()
input.NextItem() input.NextItem()
input.Select() input.Select()
input.PushKeys(keys.Files.CommitChanges) input.PressKeys(keys.Files.CommitChanges)
commitMessage := "my commit message" commitMessage := "my commit message"
input.Type(commitMessage) input.Type(commitMessage)

View File

@ -23,7 +23,7 @@ var NewBranch = types.NewTest(types.NewTestArgs{
assert.CurrentViewName("commits") assert.CurrentViewName("commits")
input.NextItem() input.NextItem()
input.PushKeys(keys.Universal.New) input.PressKeys(keys.Universal.New)
assert.CurrentViewName("confirmation") assert.CurrentViewName("confirmation")

View File

@ -0,0 +1,41 @@
package interactive_rebase
import (
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/integration/types"
)
var One = types.NewTest(types.NewTestArgs{
Description: "Begins an interactive rebase, then fixups, drops, and squashes some commits",
ExtraCmdArgs: "",
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell types.Shell) {
shell.
CreateNCommits(5) // these will appears at commit 05, 04, 04, down to 01
},
Run: func(shell types.Shell, input types.Input, assert types.Assert, keys config.KeybindingConfig) {
input.SwitchToCommitsWindow()
assert.CurrentViewName("commits")
input.NavigateToListItemContainingText("commit 02")
input.PressKeys(keys.Universal.Edit)
assert.SelectedLineContains("YOU ARE HERE")
input.PreviousItem()
input.PressKeys(keys.Commits.MarkCommitAsFixup)
assert.SelectedLineContains("fixup")
input.PreviousItem()
input.PressKeys(keys.Universal.Remove)
assert.SelectedLineContains("drop")
input.PreviousItem()
input.PressKeys(keys.Commits.SquashDown)
assert.SelectedLineContains("squash")
input.ContinueRebase()
assert.CommitCount(2)
},
})

View File

@ -3,6 +3,8 @@ package integration_tests
import ( import (
"github.com/jesseduffield/lazygit/pkg/integration/integration_tests/branch" "github.com/jesseduffield/lazygit/pkg/integration/integration_tests/branch"
"github.com/jesseduffield/lazygit/pkg/integration/integration_tests/commit" "github.com/jesseduffield/lazygit/pkg/integration/integration_tests/commit"
"github.com/jesseduffield/lazygit/pkg/integration/integration_tests/interactive_rebase"
"github.com/jesseduffield/lazygit/pkg/integration/types" "github.com/jesseduffield/lazygit/pkg/integration/types"
) )
@ -13,4 +15,5 @@ var Tests = []types.Test{
commit.Commit, commit.Commit,
commit.NewBranch, commit.NewBranch,
branch.Suggestions, branch.Suggestions,
interactive_rebase.One,
} }

View File

@ -40,6 +40,10 @@ func (s *ShellImpl) NewBranch(name string) types.Shell {
return s.RunCommand("git checkout -b " + name) return s.RunCommand("git checkout -b " + name)
} }
func (s *ShellImpl) GitAdd(path string) types.Shell {
return s.RunCommand(fmt.Sprintf("git add \"%s\"", path))
}
func (s *ShellImpl) GitAddAll() types.Shell { func (s *ShellImpl) GitAddAll() types.Shell {
return s.RunCommand("git add -A") return s.RunCommand("git add -A")
} }
@ -51,3 +55,21 @@ func (s *ShellImpl) Commit(message string) types.Shell {
func (s *ShellImpl) EmptyCommit(message string) types.Shell { func (s *ShellImpl) EmptyCommit(message string) types.Shell {
return s.RunCommand(fmt.Sprintf("git commit --allow-empty -m \"%s\"", message)) return s.RunCommand(fmt.Sprintf("git commit --allow-empty -m \"%s\"", message))
} }
func (s *ShellImpl) CreateFileAndAdd(fileName string, fileContents string) types.Shell {
return s.
CreateFile(fileName, fileContents).
GitAdd(fileName)
}
func (s *ShellImpl) CreateNCommits(n int) types.Shell {
for i := 1; i <= n; i++ {
s.CreateFileAndAdd(
fmt.Sprintf("file%02d.txt", i),
fmt.Sprintf("file%02d content", i),
).
Commit(fmt.Sprintf("commit %02d", i))
}
return s
}

View File

@ -31,9 +31,16 @@ type Shell interface {
RunCommand(command string) Shell RunCommand(command string) Shell
CreateFile(name string, content string) Shell CreateFile(name string, content string) Shell
NewBranch(branchName string) Shell NewBranch(branchName string) Shell
GitAdd(path string) Shell
GitAddAll() Shell GitAddAll() Shell
Commit(message string) Shell Commit(message string) Shell
EmptyCommit(message string) Shell EmptyCommit(message string) Shell
// convenience method for creating a file and adding it
CreateFileAndAdd(fileName string, fileContents string) Shell
// creates commits 01, 02, 03, ..., n with a new file in each
// The reason for padding with zeroes is so that it's easier to do string
// matches on the commit messages when there are many of them
CreateNCommits(n int) Shell
} }
// through this interface our test interacts with the lazygit gui // through this interface our test interacts with the lazygit gui
@ -41,7 +48,7 @@ type Shell interface {
type Input interface { type Input interface {
// key is something like 'w' or '<space>'. It's best not to pass a direct value, // key is something like 'w' or '<space>'. It's best not to pass a direct value,
// but instead to go through the default user config to get a more meaningful key name // but instead to go through the default user config to get a more meaningful key name
PushKeys(keys ...string) PressKeys(keys ...string)
// for typing into a popup prompt // for typing into a popup prompt
Type(content string) Type(content string)
// for when you want to allow lazygit to process something before continuing // for when you want to allow lazygit to process something before continuing
@ -62,6 +69,15 @@ type Input interface {
NextItem() NextItem()
// i.e. pressing up arrow // i.e. pressing up arrow
PreviousItem() PreviousItem()
// this will look for a list item in the current panel and if it finds it, it will
// enter the keypresses required to navigate to it.
// The test will fail if:
// - the user is not in a list item
// - no list item is found containing the given text
// - multiple list items are found containing the given text in the initial page of items
NavigateToListItemContainingText(text string)
ContinueRebase()
ContinueMerge()
} }
// through this interface we assert on the state of the lazygit gui // through this interface we assert on the state of the lazygit gui
@ -71,6 +87,10 @@ type Assert interface {
HeadCommitMessage(string) HeadCommitMessage(string)
CurrentViewName(expectedViewName string) CurrentViewName(expectedViewName string)
CurrentBranchName(expectedBranchName string) CurrentBranchName(expectedBranchName string)
InListContext()
SelectedLineContains(text string)
// for when you just want to fail the test yourself
Fail(errorMessage string)
} }
type TestImpl struct { type TestImpl struct {

View File

@ -0,0 +1,30 @@
# This is a combination of 3 commits.
# This is the 1st commit message:
commit 02
# The commit message #2 will be skipped:
# commit 03
# This is the commit message #3:
commit 05
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Mon Aug 8 21:32:34 2022 +1000
#
# interactive rebase in progress; onto a1a6f7b
# Last commands done (4 commands done):
# drop 84b1ae9 commit 04
# squash aa2585a commit 05
# No commands remaining.
# You are currently rebasing branch 'master' on 'a1a6f7b'.
#
# Changes to be committed:
# new file: file02.txt
# new file: file03.txt
# new file: file05.txt
#

View File

@ -0,0 +1 @@
ref: refs/heads/master

View File

@ -0,0 +1 @@
aa2585aff7d2278341ca816f187e623503d7c4fb

View File

@ -0,0 +1 @@
aa2585aff7d2278341ca816f187e623503d7c4fb

View File

@ -0,0 +1,10 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[user]
email = CI@example.com
name = CI

View File

@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@ -0,0 +1,7 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
.DS_Store

View File

@ -0,0 +1,10 @@
0000000000000000000000000000000000000000 a1a6f7bda6aeaa08ec75f590845780fde90d901c CI <CI@example.com> 1659958354 +1000 commit (initial): commit 01
a1a6f7bda6aeaa08ec75f590845780fde90d901c cb7e56856ecee89fa44c613e094fcf962fe18cf1 CI <CI@example.com> 1659958354 +1000 commit: commit 02
cb7e56856ecee89fa44c613e094fcf962fe18cf1 6741ab4fe22a3d36b6c64397fc4295dbae1ba71d CI <CI@example.com> 1659958354 +1000 commit: commit 03
6741ab4fe22a3d36b6c64397fc4295dbae1ba71d 84b1ae9d83049341897c9388afffdc9049c3317f CI <CI@example.com> 1659958354 +1000 commit: commit 04
84b1ae9d83049341897c9388afffdc9049c3317f aa2585aff7d2278341ca816f187e623503d7c4fb CI <CI@example.com> 1659958354 +1000 commit: commit 05
aa2585aff7d2278341ca816f187e623503d7c4fb a1a6f7bda6aeaa08ec75f590845780fde90d901c CI <CI@example.com> 1659958355 +1000 rebase (start): checkout a1a6f7bda6aeaa08ec75f590845780fde90d901c
a1a6f7bda6aeaa08ec75f590845780fde90d901c cb7e56856ecee89fa44c613e094fcf962fe18cf1 CI <CI@example.com> 1659958355 +1000 rebase: fast-forward
cb7e56856ecee89fa44c613e094fcf962fe18cf1 9c68b57ac7b652fbebc5e93a9a1b72014400c269 CI <CI@example.com> 1659958355 +1000 rebase (continue) (fixup): # This is a combination of 2 commits.
9c68b57ac7b652fbebc5e93a9a1b72014400c269 f4316f7a6df3fe5b7e8da1b2c8767ed1e825dc05 CI <CI@example.com> 1659958355 +1000 rebase (continue) (squash): commit 02
f4316f7a6df3fe5b7e8da1b2c8767ed1e825dc05 f4316f7a6df3fe5b7e8da1b2c8767ed1e825dc05 CI <CI@example.com> 1659958355 +1000 rebase (continue) (finish): returning to refs/heads/master

View File

@ -0,0 +1,6 @@
0000000000000000000000000000000000000000 a1a6f7bda6aeaa08ec75f590845780fde90d901c CI <CI@example.com> 1659958354 +1000 commit (initial): commit 01
a1a6f7bda6aeaa08ec75f590845780fde90d901c cb7e56856ecee89fa44c613e094fcf962fe18cf1 CI <CI@example.com> 1659958354 +1000 commit: commit 02
cb7e56856ecee89fa44c613e094fcf962fe18cf1 6741ab4fe22a3d36b6c64397fc4295dbae1ba71d CI <CI@example.com> 1659958354 +1000 commit: commit 03
6741ab4fe22a3d36b6c64397fc4295dbae1ba71d 84b1ae9d83049341897c9388afffdc9049c3317f CI <CI@example.com> 1659958354 +1000 commit: commit 04
84b1ae9d83049341897c9388afffdc9049c3317f aa2585aff7d2278341ca816f187e623503d7c4fb CI <CI@example.com> 1659958354 +1000 commit: commit 05
aa2585aff7d2278341ca816f187e623503d7c4fb f4316f7a6df3fe5b7e8da1b2c8767ed1e825dc05 CI <CI@example.com> 1659958355 +1000 rebase (continue) (finish): refs/heads/master onto a1a6f7bda6aeaa08ec75f590845780fde90d901c

View File

@ -0,0 +1,3 @@
x<01><>M
<EFBFBD>0<10>a<EFBFBD>9<EFBFBD><39><05>ɟ<11><>1<EFBFBD><31>
֖<12><>ۅp<><70><><DEBA><EFBFBD><EFBFBD>h<<3C>

View File

@ -0,0 +1,3 @@
x<01><>M
<EFBFBD>0@a<>9<EFBFBD><39>I2?m@D<><44>cL<63>
֖<12><>ۅp<><70><16>,<2C><><EFBFBD><10><1C>f<06>U<EFBFBD>[L<><4C>L<EFBFBD><4C><EFBFBD>sE<73><45><EFBFBD>&K#dnݪ<><DDAA>:<3A>@A35<33>Q<EFBFBD><51>d)B<><42>V(&<26>Y-dBu<42><75><EFBFBD>e<EFBFBD><65><06><>v<EFBFBD><76><EFBFBD><EFBFBD><EFBFBD>Ne<4E>/<10>S<EFBFBD><11><><18><>n<EFBFBD><6E>T<EFBFBD>?<3F>σ'<27><05>u;<3B>

View File

@ -0,0 +1,2 @@
x<01><><EFBFBD>J<EFBFBD>0<10>=<3D>)z<14><49>Ȳ<EFBFBD><C8B2><EFBFBD><EFBFBD><02>fb<66>6mi"<22><>F<EFBFBD> ^<5E>9}<7D><>3<EFBFBD><33><EFBFBD> <0B><>{(;3p<33><70> ڌ<>9<EFBFBD><39>;<3B><><><12><1E>2%Y<><59>v^
<EFBFBD><EFBFBD>> <0C>SOL<4F><4C><EFBFBD>Am<>tz0<[<5B><16>(<28><>L<EFBFBD><0E><_o<17><><EFBFBD><EFBFBD>|<1A>t<06>kk<6B>iu<07>E<><45>T<EFBFBD>t}袁<>)f<>CP<43>..T<><54><EFBFBD>@}<7D>z,<2C>~Yeb<65><62>+H<>3<EFBFBD><33><EFBFBD>8*<2A>m<EFBFBD><05>q<EFBFBD><71>1<EFBFBD>{<7B>6<EFBFBD>5<EFBFBD><35>X؊Oz d<>

View File

@ -0,0 +1,4 @@
x<01><>A
1 <0C>a<EFBFBD>=E<><45>4<EFBFBD><34>m@D<><44><EFBFBD>h3)
<EFBFBD><19>
<1E>.<<3C>ۏ<EFBFBD><DB8F><EFBFBD><EFBFBD><EFBFBD>G<EFBFBD><47><EFBFBD><EFBFBD>wU<77>pLJ9׊<39><D78A><EFBFBD><EFBFBD><EFBFBD><EFBFBD>$BT!*<2A><>ly<6C>W<EFBFBD><57><17><>KB<4B>=$<24>˜Ҙ<C298>Ex<45> B<>&<26><>}<7D><>|<7C><><EFBFBD>v<EFBFBD>On<4F>SO<53><4F><EFBFBD><EFBFBD><><CC94>=<3D>s<EFBFBD> <1D><><EFBFBD><EFBFBD><EFBFBD>z<EFBFBD><7A>|<01>);=

View File

@ -0,0 +1 @@
f4316f7a6df3fe5b7e8da1b2c8767ed1e825dc05

View File

@ -0,0 +1 @@
file01 content

View File

@ -0,0 +1 @@
file02 content

View File

@ -0,0 +1 @@
file03 content

View File

@ -0,0 +1 @@
file05 content

View File

@ -465,6 +465,14 @@ func (v *View) Cursor() (x, y int) {
return v.cx, v.cy return v.cx, v.cy
} }
func (v *View) CursorX() int {
return v.cx
}
func (v *View) CursorY() int {
return v.cy
}
// SetOrigin sets the origin position of the view's internal buffer, // SetOrigin sets the origin position of the view's internal buffer,
// so the buffer starts to be printed from this point, which means that // so the buffer starts to be printed from this point, which means that
// it is linked with the origin point of view. It can be used to // it is linked with the origin point of view. It can be used to
@ -1235,6 +1243,13 @@ func (v *View) SelectedLineIdx() int {
return seletedLineIdx return seletedLineIdx
} }
// expected to only be used in tests
func (v *View) SelectedLine() string {
line := v.lines[v.SelectedLineIdx()]
str := lineType(line).String()
return strings.Replace(str, "\x00", " ", -1)
}
func (v *View) SelectedPoint() (int, int) { func (v *View) SelectedPoint() (int, int) {
cx, cy := v.Cursor() cx, cy := v.Cursor()
ox, oy := v.Origin() ox, oy := v.Origin()