mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-10-05 05:26:53 -04:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a07e03701 |
@@ -1,24 +1,15 @@
|
||||
# see https://github.com/JamesIves/github-sponsors-readme-action
|
||||
name: Generate Sponsors README
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request: # Add this if you want to run checks on PRs as well
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: master # This ensures the checkout step fetches the master branch
|
||||
|
||||
- name: Switch to master branch
|
||||
run: |
|
||||
git fetch origin master:refs/remotes/origin/master
|
||||
git checkout master
|
||||
|
||||
- name: Generate Sponsors 💖
|
||||
uses: JamesIves/github-sponsors-readme-action@v1.2.2
|
||||
@@ -33,7 +24,5 @@ jobs:
|
||||
git config --global user.email "actions@users.noreply.github.com"
|
||||
git config --global user.name "README-bot"
|
||||
git add README.md
|
||||
git commit -m "Updated README.md [skip ci]" || exit 0
|
||||
git push origin master # Explicitly push to master branch
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_API_TOKEN }}
|
||||
git commit -m "Updated README.md" || exit 0
|
||||
git push
|
||||
|
||||
@@ -92,8 +92,6 @@ git:
|
||||
useConfig: false
|
||||
commit:
|
||||
signOff: false
|
||||
autoWrapCommitMessage: true # automatic WYSIWYG wrapping of the commit message as you type
|
||||
autoWrapWidth: 72 # if autoWrapCommitMessage is true, the width to wrap to
|
||||
merging:
|
||||
# only applicable to unix users
|
||||
manualCommit: false
|
||||
|
||||
@@ -9,14 +9,14 @@ require (
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
|
||||
github.com/creack/pty v1.1.11
|
||||
github.com/fsmiamoto/git-todo-parser v0.0.5
|
||||
github.com/gdamore/tcell/v2 v2.7.4
|
||||
github.com/gdamore/tcell/v2 v2.7.3
|
||||
github.com/go-errors/errors v1.5.1
|
||||
github.com/gookit/color v1.4.2
|
||||
github.com/imdario/mergo v0.3.11
|
||||
github.com/integrii/flaggy v1.4.0
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20240309085756-86e0d5a312de
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20240303173746-f2b0f1f68dd8
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
|
||||
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
|
||||
@@ -74,8 +74,8 @@ require (
|
||||
github.com/xanzy/ssh-agent v0.2.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/term v0.18.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
golang.org/x/term v0.17.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
||||
@@ -89,8 +89,8 @@ github.com/fsmiamoto/git-todo-parser v0.0.5/go.mod h1:B+AgTbNE2BARvJqzXygThzqxLI
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
|
||||
github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
|
||||
github.com/gdamore/tcell/v2 v2.7.3 h1:YLQlOj5F0hSlKy5TJvlych29+WTcJzbElnLYwx8gvdg=
|
||||
github.com/gdamore/tcell/v2 v2.7.3/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
|
||||
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
|
||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
@@ -187,8 +187,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20240309085756-86e0d5a312de h1:2ww1SWgakihE8hFxZ7L3agVeGpA6qwW5vdnhFUXKMQo=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20240309085756-86e0d5a312de/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20240303173746-f2b0f1f68dd8 h1:DGyAjpaAnxDuKO4MEoFjifhkUV7sU6znMR9eRfjjvn0=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20240303173746-f2b0f1f68dd8/go.mod h1:lLLfxEGyIvvkzzpHdKkfgIVFmxqEejeACxKMVxSHLeM=
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
|
||||
@@ -470,15 +470,13 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
||||
@@ -142,7 +142,7 @@ func (self *CommitCommands) GetCommitMessage(commitSha string) (string, error) {
|
||||
ToArgv()
|
||||
|
||||
message, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
|
||||
return strings.ReplaceAll(strings.TrimSpace(message), "\r\n", "\n"), err
|
||||
return strings.TrimSpace(message), err
|
||||
}
|
||||
|
||||
func (self *CommitCommands) GetCommitSubject(commitSha string) (string, error) {
|
||||
|
||||
@@ -236,10 +236,6 @@ type PagingConfig struct {
|
||||
type CommitConfig struct {
|
||||
// If true, pass '--signoff' flag when committing
|
||||
SignOff bool `yaml:"signOff"`
|
||||
// Automatic WYSIWYG wrapping of the commit message as you type
|
||||
AutoWrapCommitMessage bool `yaml:"autoWrapCommitMessage"`
|
||||
// If autoWrapCommitMessage is true, the width to wrap to
|
||||
AutoWrapWidth int `yaml:"autoWrapWidth"`
|
||||
}
|
||||
|
||||
type MergingConfig struct {
|
||||
@@ -662,9 +658,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
ExternalDiffCommand: "",
|
||||
},
|
||||
Commit: CommitConfig{
|
||||
SignOff: false,
|
||||
AutoWrapCommitMessage: true,
|
||||
AutoWrapWidth: 72,
|
||||
SignOff: false,
|
||||
},
|
||||
Merging: MergingConfig{
|
||||
ManualCommit: false,
|
||||
|
||||
@@ -38,14 +38,10 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
getCommitDescription := func() string {
|
||||
return strings.TrimSpace(gui.Views.CommitDescription.TextArea.GetContent())
|
||||
}
|
||||
getUnwrappedCommitDescription := func() string {
|
||||
return strings.TrimSpace(gui.Views.CommitDescription.TextArea.GetUnwrappedContent())
|
||||
}
|
||||
commitsHelper := helpers.NewCommitsHelper(helperCommon,
|
||||
getCommitSummary,
|
||||
setCommitSummary,
|
||||
getCommitDescription,
|
||||
getUnwrappedCommitDescription,
|
||||
setCommitDescription,
|
||||
)
|
||||
|
||||
|
||||
@@ -433,7 +433,7 @@ func (self *BranchesController) forceCheckout() error {
|
||||
func (self *BranchesController) checkoutByName() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.BranchName + ":",
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRefsSuggestionsFunc(),
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetCheckoutBranchesSuggestionsFunc(),
|
||||
HandleConfirm: func(response string) error {
|
||||
self.c.LogAction("Checkout branch")
|
||||
return self.c.Helpers().Refs.CheckoutRef(response, types.CheckoutRefOptions{
|
||||
|
||||
@@ -3,7 +3,6 @@ package controllers
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@@ -101,7 +100,7 @@ func (self *CommitMessageController) handleCommitIndexChange(value int) error {
|
||||
self.c.Helpers().Commits.SetMessageAndDescriptionInView(self.context().GetHistoryMessage())
|
||||
return nil
|
||||
} else if currentIndex == context.NoCommitIndex {
|
||||
self.context().SetHistoryMessage(self.c.Helpers().Commits.JoinCommitMessageAndUnwrappedDescription())
|
||||
self.context().SetHistoryMessage(self.c.Helpers().Commits.JoinCommitMessageAndDescription())
|
||||
}
|
||||
|
||||
validCommit, err := self.setCommitMessageAtIndex(newIndex)
|
||||
@@ -120,9 +119,6 @@ func (self *CommitMessageController) setCommitMessageAtIndex(index int) (bool, e
|
||||
}
|
||||
return false, self.c.ErrorMsg(self.c.Tr.CommitWithoutMessageErr)
|
||||
}
|
||||
if self.c.UserConfig.Git.Commit.AutoWrapCommitMessage {
|
||||
commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig.Git.Commit.AutoWrapWidth)
|
||||
}
|
||||
self.c.Helpers().Commits.UpdateCommitPanelView(commitMessage)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
@@ -17,11 +16,10 @@ type ICommitsHelper interface {
|
||||
type CommitsHelper struct {
|
||||
c *HelperCommon
|
||||
|
||||
getCommitSummary func() string
|
||||
setCommitSummary func(string)
|
||||
getCommitDescription func() string
|
||||
getUnwrappedCommitDescription func() string
|
||||
setCommitDescription func(string)
|
||||
getCommitSummary func() string
|
||||
setCommitSummary func(string)
|
||||
getCommitDescription func() string
|
||||
setCommitDescription func(string)
|
||||
}
|
||||
|
||||
var _ ICommitsHelper = &CommitsHelper{}
|
||||
@@ -31,16 +29,14 @@ func NewCommitsHelper(
|
||||
getCommitSummary func() string,
|
||||
setCommitSummary func(string),
|
||||
getCommitDescription func() string,
|
||||
getUnwrappedCommitDescription func() string,
|
||||
setCommitDescription func(string),
|
||||
) *CommitsHelper {
|
||||
return &CommitsHelper{
|
||||
c: c,
|
||||
getCommitSummary: getCommitSummary,
|
||||
setCommitSummary: setCommitSummary,
|
||||
getCommitDescription: getCommitDescription,
|
||||
getUnwrappedCommitDescription: getUnwrappedCommitDescription,
|
||||
setCommitDescription: setCommitDescription,
|
||||
c: c,
|
||||
getCommitSummary: getCommitSummary,
|
||||
setCommitSummary: setCommitSummary,
|
||||
getCommitDescription: getCommitDescription,
|
||||
setCommitDescription: setCommitDescription,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,36 +53,11 @@ func (self *CommitsHelper) SetMessageAndDescriptionInView(message string) {
|
||||
self.c.Contexts().CommitMessage.RenderCommitLength()
|
||||
}
|
||||
|
||||
func (self *CommitsHelper) JoinCommitMessageAndUnwrappedDescription() string {
|
||||
if len(self.getUnwrappedCommitDescription()) == 0 {
|
||||
func (self *CommitsHelper) JoinCommitMessageAndDescription() string {
|
||||
if len(self.getCommitDescription()) == 0 {
|
||||
return self.getCommitSummary()
|
||||
}
|
||||
return self.getCommitSummary() + "\n" + self.getUnwrappedCommitDescription()
|
||||
}
|
||||
|
||||
func TryRemoveHardLineBreaks(message string, autoWrapWidth int) string {
|
||||
messageRunes := []rune(message)
|
||||
lastHardLineStart := 0
|
||||
for i, r := range messageRunes {
|
||||
if r == '\n' {
|
||||
// Try to make this a soft linebreak by turning it into a space, and
|
||||
// checking whether it still wraps to the same result then.
|
||||
messageRunes[i] = ' '
|
||||
|
||||
_, cursorMapping := gocui.AutoWrapContent(messageRunes[lastHardLineStart:], autoWrapWidth)
|
||||
|
||||
// Look at the cursorMapping to check whether auto-wrapping inserted
|
||||
// a line break. If it did, there will be a cursorMapping entry with
|
||||
// Orig pointing to the position after the inserted line break.
|
||||
if len(cursorMapping) == 0 || cursorMapping[0].Orig != i-lastHardLineStart+1 {
|
||||
// It didn't, so change it back to a newline
|
||||
messageRunes[i] = '\n'
|
||||
}
|
||||
lastHardLineStart = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
return string(messageRunes)
|
||||
return self.getCommitSummary() + "\n" + self.getCommitDescription()
|
||||
}
|
||||
|
||||
func (self *CommitsHelper) SwitchToEditor() error {
|
||||
@@ -183,7 +154,7 @@ func (self *CommitsHelper) HandleCommitConfirm() error {
|
||||
|
||||
func (self *CommitsHelper) CloseCommitMessagePanel() error {
|
||||
if self.c.Contexts().CommitMessage.GetPreserveMessage() {
|
||||
message := self.JoinCommitMessageAndUnwrappedDescription()
|
||||
message := self.JoinCommitMessageAndDescription()
|
||||
|
||||
self.c.Contexts().CommitMessage.SetPreservedMessage(message)
|
||||
} else {
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTryRemoveHardLineBreaks(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
message string
|
||||
autoWrapWidth int
|
||||
expectedResult string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
message: "",
|
||||
autoWrapWidth: 7,
|
||||
expectedResult: "",
|
||||
},
|
||||
{
|
||||
name: "all line breaks are needed",
|
||||
message: "abc\ndef\n\nxyz",
|
||||
autoWrapWidth: 7,
|
||||
expectedResult: "abc\ndef\n\nxyz",
|
||||
},
|
||||
{
|
||||
name: "some can be unwrapped",
|
||||
message: "123\nabc def\nghi jkl\nmno\n456\n",
|
||||
autoWrapWidth: 7,
|
||||
expectedResult: "123\nabc def ghi jkl mno\n456\n",
|
||||
},
|
||||
}
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
actualResult := TryRemoveHardLineBreaks(s.message, s.autoWrapWidth)
|
||||
assert.Equal(t, s.expectedResult, actualResult)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package helpers
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
@@ -25,6 +26,8 @@ import (
|
||||
// finding suggestions in this file, so that it's easy to see if a function already
|
||||
// exists for fetching a particular model.
|
||||
|
||||
var specialRefNames = []string{"HEAD", "FETCH_HEAD", "MERGE_HEAD", "ORIG_HEAD"}
|
||||
|
||||
type ISuggestionsHelper interface {
|
||||
GetRemoteSuggestionsFunc() func(string) []*types.Suggestion
|
||||
GetBranchNameSuggestionsFunc() func(string) []*types.Suggestion
|
||||
@@ -168,9 +171,29 @@ func (self *SuggestionsHelper) GetRefsSuggestionsFunc() func(string) []*types.Su
|
||||
remoteBranchNames := self.getRemoteBranchNames("/")
|
||||
localBranchNames := self.getBranchNames()
|
||||
tagNames := self.getTagNames()
|
||||
additionalRefNames := []string{"HEAD", "FETCH_HEAD", "MERGE_HEAD", "ORIG_HEAD"}
|
||||
|
||||
refNames := append(append(append(remoteBranchNames, localBranchNames...), tagNames...), additionalRefNames...)
|
||||
refNames := append(append(append(remoteBranchNames, localBranchNames...), tagNames...), specialRefNames...)
|
||||
|
||||
return FuzzySearchFunc(refNames)
|
||||
}
|
||||
|
||||
func (self *SuggestionsHelper) GetCheckoutBranchesSuggestionsFunc() func(string) []*types.Suggestion {
|
||||
remoteBranchNames := self.getRemoteBranchNames("/")
|
||||
// We include remote branches with the remote stripped off in the list of suggestions
|
||||
// so that you can check out the branch as a local branch tracking the remote branch, just
|
||||
// like you can do in the git CLI. I.e. if you checkout 'origin/blah' it will be
|
||||
// checked out as as a detached head, but if you checkout 'blah' it will be checked out
|
||||
// as a local branch tracking 'origin/blah'.
|
||||
localisedRemoteBranchNames := lo.Map(remoteBranchNames, func(branchName string, _ int) string {
|
||||
// strip the remote name from the branch name
|
||||
return branchName[strings.Index(branchName, "/")+1:]
|
||||
})
|
||||
localBranchNames := self.getBranchNames()
|
||||
tagNames := self.getTagNames()
|
||||
|
||||
refNames := append(append(append(append(remoteBranchNames, localBranchNames...), tagNames...), specialRefNames...), localisedRemoteBranchNames...)
|
||||
|
||||
refNames = lo.Uniq(refNames)
|
||||
|
||||
return FuzzySearchFunc(refNames)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fsmiamoto/git-todo-parser/todo"
|
||||
"github.com/go-errors/errors"
|
||||
@@ -116,6 +115,7 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
|
||||
{
|
||||
Key: opts.GetKey(editCommitKey),
|
||||
Handler: self.withItems(self.edit),
|
||||
// TODO: have disabled reason ensure that if we're not rebasing, we only select one commit
|
||||
GetDisabledReason: self.require(
|
||||
self.itemRangeSelected(self.midRebaseCommandEnabled),
|
||||
),
|
||||
@@ -354,9 +354,7 @@ func (self *LocalCommitsController) reword(commit *models.Commit) error {
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
if self.c.UserConfig.Git.Commit.AutoWrapCommitMessage {
|
||||
commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig.Git.Commit.AutoWrapWidth)
|
||||
}
|
||||
|
||||
return self.c.Helpers().Commits.OpenCommitMessagePanel(
|
||||
&helpers.OpenCommitMessagePanelOpts{
|
||||
CommitIndex: self.context().GetSelectedLineIdx(),
|
||||
@@ -459,7 +457,15 @@ func (self *LocalCommitsController) edit(selectedCommits []*models.Commit) error
|
||||
return self.updateTodos(todo.Edit, selectedCommits)
|
||||
}
|
||||
|
||||
return self.startInteractiveRebaseWithEdit(selectedCommits)
|
||||
// TODO: support range select here (start a rebase and set the selected commits
|
||||
// to 'edit' in the todo file)
|
||||
if len(selectedCommits) > 1 {
|
||||
return self.c.ErrorMsg(self.c.Tr.RangeSelectNotSupported)
|
||||
}
|
||||
|
||||
selectedCommit := selectedCommits[0]
|
||||
|
||||
return self.startInteractiveRebaseWithEdit(selectedCommit)
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) quickStartInteractiveRebase() error {
|
||||
@@ -468,11 +474,11 @@ func (self *LocalCommitsController) quickStartInteractiveRebase() error {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
return self.startInteractiveRebaseWithEdit([]*models.Commit{commitToEdit})
|
||||
return self.startInteractiveRebaseWithEdit(commitToEdit)
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) startInteractiveRebaseWithEdit(
|
||||
commitsToEdit []*models.Commit,
|
||||
commitToEdit *models.Commit,
|
||||
) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.EditCommit)
|
||||
@@ -480,24 +486,10 @@ func (self *LocalCommitsController) startInteractiveRebaseWithEdit(
|
||||
commits := self.c.Model().Commits
|
||||
selectedSha := commits[selectedIdx].Sha
|
||||
rangeStartSha := commits[rangeStartIdx].Sha
|
||||
err := self.c.Git().Rebase.EditRebase(commitsToEdit[len(commitsToEdit)-1].Sha)
|
||||
err := self.c.Git().Rebase.EditRebase(commitToEdit.Sha)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
|
||||
err,
|
||||
types.RefreshOptions{Mode: types.BLOCK_UI, Then: func() {
|
||||
todos := make([]*models.Commit, 0, len(commitsToEdit)-1)
|
||||
for _, c := range commitsToEdit[:len(commitsToEdit)-1] {
|
||||
// Merge commits can't be set to "edit", so just skip them
|
||||
if !c.IsMerge() {
|
||||
todos = append(todos, &models.Commit{Sha: c.Sha, Action: todo.Pick})
|
||||
}
|
||||
}
|
||||
if len(todos) > 0 {
|
||||
err := self.updateTodos(todo.Edit, todos)
|
||||
if err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// We need to select the same commit range again because after starting a rebase,
|
||||
// new lines can be added for update-ref commands in the TODO file, due to
|
||||
// stacked branches. So the selected commits may be in different positions in the list.
|
||||
@@ -814,14 +806,11 @@ func (self *LocalCommitsController) createFixupCommit(commit *models.Commit) err
|
||||
HandleConfirm: func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommitableFiles(func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CreateFixupCommit)
|
||||
return self.c.WithWaitingStatusSync(self.c.Tr.CreatingFixupCommitStatus, func() error {
|
||||
if err := self.c.Git().Commit.CreateFixupCommit(commit.Sha); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
if err := self.c.Git().Commit.CreateFixupCommit(commit.Sha); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.context().MoveSelectedLine(1)
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC})
|
||||
})
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -850,89 +839,37 @@ func (self *LocalCommitsController) squashFixupCommits() error {
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) squashAllFixupsAboveSelectedCommit(commit *models.Commit) error {
|
||||
return self.squashFixupsImpl(commit, self.context().GetSelectedLineIdx())
|
||||
return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.SquashAllAboveFixupCommits)
|
||||
err := self.c.Git().Rebase.SquashAllAboveFixupCommits(commit)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) squashAllFixupsInCurrentBranch() error {
|
||||
commit, rebaseStartIdx, err := self.findCommitForSquashFixupsInCurrentBranch()
|
||||
commit, err := self.findCommitForSquashFixupsInCurrentBranch()
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
return self.squashFixupsImpl(commit, rebaseStartIdx)
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) squashFixupsImpl(commit *models.Commit, rebaseStartIdx int) error {
|
||||
selectionOffset := countSquashableCommitsAbove(self.c.Model().Commits, self.context().GetSelectedLineIdx(), rebaseStartIdx)
|
||||
return self.c.WithWaitingStatusSync(self.c.Tr.SquashingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.SquashAllAboveFixupCommits)
|
||||
err := self.c.Git().Rebase.SquashAllAboveFixupCommits(commit)
|
||||
self.context().MoveSelectedLine(-selectionOffset)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
|
||||
err, types.RefreshOptions{Mode: types.SYNC})
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) findCommitForSquashFixupsInCurrentBranch() (*models.Commit, int, error) {
|
||||
func (self *LocalCommitsController) findCommitForSquashFixupsInCurrentBranch() (*models.Commit, error) {
|
||||
commits := self.c.Model().Commits
|
||||
_, index, ok := lo.FindIndexOf(commits, func(c *models.Commit) bool {
|
||||
return c.IsMerge() || c.Status == models.StatusMerged
|
||||
})
|
||||
|
||||
if !ok || index == 0 {
|
||||
return nil, -1, errors.New(self.c.Tr.CannotSquashCommitsInCurrentBranch)
|
||||
return nil, errors.New(self.c.Tr.CannotSquashCommitsInCurrentBranch)
|
||||
}
|
||||
|
||||
return commits[index-1], index - 1, nil
|
||||
}
|
||||
|
||||
// Anticipate how many commits above the selectedIdx are going to get squashed
|
||||
// by the SquashAllAboveFixupCommits call, so that we can adjust the selection
|
||||
// afterwards. Let's hope we're matching git's behavior correctly here.
|
||||
func countSquashableCommitsAbove(commits []*models.Commit, selectedIdx int, rebaseStartIdx int) int {
|
||||
result := 0
|
||||
|
||||
// For each commit _above_ the selection, ...
|
||||
for i, commit := range commits[0:selectedIdx] {
|
||||
// ... see if it is a fixup commit, and get the base subject it applies to
|
||||
if baseSubject, isFixup := isFixupCommit(commit.Name); isFixup {
|
||||
// Then, for each commit after the fixup, up to and including the
|
||||
// rebase start commit, see if we find the base commit
|
||||
for _, baseCommit := range commits[i+1 : rebaseStartIdx+1] {
|
||||
if strings.HasPrefix(baseCommit.Name, baseSubject) {
|
||||
result++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Check whether the given subject line is the subject of a fixup commit, and
|
||||
// returns (trimmedSubject, true) if so (where trimmedSubject is the subject
|
||||
// with all fixup prefixes removed), or (subject, false) if not.
|
||||
func isFixupCommit(subject string) (string, bool) {
|
||||
prefixes := []string{"fixup! ", "squash! ", "amend! "}
|
||||
trimPrefix := func(s string) (string, bool) {
|
||||
for _, prefix := range prefixes {
|
||||
if strings.HasPrefix(s, prefix) {
|
||||
return strings.TrimPrefix(s, prefix), true
|
||||
}
|
||||
}
|
||||
return s, false
|
||||
}
|
||||
|
||||
if subject, wasTrimmed := trimPrefix(subject); wasTrimmed {
|
||||
for {
|
||||
// handle repeated prefixes like "fixup! amend! fixup! Subject"
|
||||
if subject, wasTrimmed = trimPrefix(subject); !wasTrimmed {
|
||||
break
|
||||
}
|
||||
}
|
||||
return subject, true
|
||||
}
|
||||
|
||||
return subject, false
|
||||
return commits[index-1], nil
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) createTag(commit *models.Commit) error {
|
||||
@@ -1125,7 +1062,7 @@ func (self *LocalCommitsController) canFindCommitForQuickStart() *types.Disabled
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) canFindCommitForSquashFixupsInCurrentBranch() *types.DisabledReason {
|
||||
if _, _, err := self.findCommitForSquashFixupsInCurrentBranch(); err != nil {
|
||||
if _, err := self.findCommitForSquashFixupsInCurrentBranch(); err != nil {
|
||||
return &types.DisabledReason{Text: err.Error()}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_countSquashableCommitsAbove(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
commits []*models.Commit
|
||||
selectedIdx int
|
||||
rebaseStartIdx int
|
||||
expectedResult int
|
||||
}{
|
||||
{
|
||||
name: "no squashable commits",
|
||||
commits: []*models.Commit{
|
||||
{Name: "abc"},
|
||||
{Name: "def"},
|
||||
{Name: "ghi"},
|
||||
},
|
||||
selectedIdx: 2,
|
||||
rebaseStartIdx: 2,
|
||||
expectedResult: 0,
|
||||
},
|
||||
{
|
||||
name: "some squashable commits, including for the selected commit",
|
||||
commits: []*models.Commit{
|
||||
{Name: "fixup! def"},
|
||||
{Name: "fixup! ghi"},
|
||||
{Name: "abc"},
|
||||
{Name: "def"},
|
||||
{Name: "ghi"},
|
||||
},
|
||||
selectedIdx: 4,
|
||||
rebaseStartIdx: 4,
|
||||
expectedResult: 2,
|
||||
},
|
||||
{
|
||||
name: "base commit is below rebase start",
|
||||
commits: []*models.Commit{
|
||||
{Name: "fixup! def"},
|
||||
{Name: "abc"},
|
||||
{Name: "def"},
|
||||
},
|
||||
selectedIdx: 1,
|
||||
rebaseStartIdx: 1,
|
||||
expectedResult: 0,
|
||||
},
|
||||
{
|
||||
name: "base commit does not exist at all",
|
||||
commits: []*models.Commit{
|
||||
{Name: "fixup! xyz"},
|
||||
{Name: "abc"},
|
||||
{Name: "def"},
|
||||
},
|
||||
selectedIdx: 2,
|
||||
rebaseStartIdx: 2,
|
||||
expectedResult: 0,
|
||||
},
|
||||
{
|
||||
name: "selected commit is in the middle of fixups",
|
||||
commits: []*models.Commit{
|
||||
{Name: "fixup! def"},
|
||||
{Name: "abc"},
|
||||
{Name: "fixup! ghi"},
|
||||
{Name: "def"},
|
||||
{Name: "ghi"},
|
||||
},
|
||||
selectedIdx: 1,
|
||||
rebaseStartIdx: 4,
|
||||
expectedResult: 1,
|
||||
},
|
||||
{
|
||||
name: "selected commit is after rebase start",
|
||||
commits: []*models.Commit{
|
||||
{Name: "fixup! def"},
|
||||
{Name: "abc"},
|
||||
{Name: "def"},
|
||||
{Name: "ghi"},
|
||||
},
|
||||
selectedIdx: 3,
|
||||
rebaseStartIdx: 2,
|
||||
expectedResult: 1,
|
||||
},
|
||||
}
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
assert.Equal(t, s.expectedResult, countSquashableCommitsAbove(s.commits, s.selectedIdx, s.rebaseStartIdx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_isFixupCommit(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
subject string
|
||||
expectedTrimmedSubject string
|
||||
expectedIsFixup bool
|
||||
}{
|
||||
{
|
||||
subject: "Bla",
|
||||
expectedTrimmedSubject: "Bla",
|
||||
expectedIsFixup: false,
|
||||
},
|
||||
{
|
||||
subject: "fixup Bla",
|
||||
expectedTrimmedSubject: "fixup Bla",
|
||||
expectedIsFixup: false,
|
||||
},
|
||||
{
|
||||
subject: "fixup! Bla",
|
||||
expectedTrimmedSubject: "Bla",
|
||||
expectedIsFixup: true,
|
||||
},
|
||||
{
|
||||
subject: "fixup! fixup! Bla",
|
||||
expectedTrimmedSubject: "Bla",
|
||||
expectedIsFixup: true,
|
||||
},
|
||||
{
|
||||
subject: "amend! squash! Bla",
|
||||
expectedTrimmedSubject: "Bla",
|
||||
expectedIsFixup: true,
|
||||
},
|
||||
{
|
||||
subject: "fixup!",
|
||||
expectedTrimmedSubject: "fixup!",
|
||||
expectedIsFixup: false,
|
||||
},
|
||||
}
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.subject, func(t *testing.T) {
|
||||
trimmedSubject, isFixupCommit := isFixupCommit(s.subject)
|
||||
assert.Equal(t, s.expectedTrimmedSubject, trimmedSubject)
|
||||
assert.Equal(t, s.expectedIsFixup, isFixupCommit)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -168,8 +168,6 @@ func (gui *Gui) createAllViews() error {
|
||||
gui.Views.CommitDescription.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.Views.CommitDescription.Editable = true
|
||||
gui.Views.CommitDescription.Editor = gocui.EditorFunc(gui.commitDescriptionEditor)
|
||||
gui.Views.CommitDescription.TextArea.AutoWrap = gui.c.UserConfig.Git.Commit.AutoWrapCommitMessage
|
||||
gui.Views.CommitDescription.TextArea.AutoWrapWidth = gui.c.UserConfig.Git.Commit.AutoWrapWidth
|
||||
|
||||
gui.Views.Confirmation.Visible = false
|
||||
gui.Views.Confirmation.Editor = gocui.EditorFunc(gui.promptEditor)
|
||||
|
||||
@@ -341,7 +341,6 @@ type TranslationSet struct {
|
||||
CheckingOutStatus string
|
||||
CommittingStatus string
|
||||
RevertingStatus string
|
||||
CreatingFixupCommitStatus string
|
||||
CommitFiles string
|
||||
SubCommitsDynamicTitle string
|
||||
CommitFilesDynamicTitle string
|
||||
@@ -1290,7 +1289,6 @@ func EnglishTranslationSet() TranslationSet {
|
||||
CheckingOutStatus: "Checking out",
|
||||
CommittingStatus: "Committing",
|
||||
RevertingStatus: "Reverting",
|
||||
CreatingFixupCommitStatus: "Creating fixup commit",
|
||||
CommitFiles: "Commit files",
|
||||
SubCommitsDynamicTitle: "Commits (%s)",
|
||||
CommitFilesDynamicTitle: "Diff files (%s)",
|
||||
|
||||
@@ -31,16 +31,6 @@ func (self *CommitDescriptionPanelDriver) AddNewline() *CommitDescriptionPanelDr
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionPanelDriver) GoToBeginning() *CommitDescriptionPanelDriver {
|
||||
numLines := len(self.getViewDriver().getView().BufferLines())
|
||||
for i := 0; i < numLines; i++ {
|
||||
self.t.pressFast("<up>")
|
||||
}
|
||||
|
||||
self.t.pressFast("<c-a>")
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionPanelDriver) Title(expected *TextMatcher) *CommitDescriptionPanelDriver {
|
||||
self.getViewDriver().Title(expected)
|
||||
|
||||
|
||||
@@ -148,6 +148,10 @@ func (self *Shell) Checkout(name string) *Shell {
|
||||
return self.RunCommand([]string{"git", "checkout", name})
|
||||
}
|
||||
|
||||
func (self *Shell) DeleteBranch(name string) *Shell {
|
||||
return self.RunCommand([]string{"git", "branch", "-D", name})
|
||||
}
|
||||
|
||||
func (self *Shell) Merge(name string) *Shell {
|
||||
return self.RunCommand([]string{"git", "merge", "--commit", "--no-ff", name})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package branch
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var CheckoutByNameRemote = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Checkout a remote branch by name, both using the full name and the local name.",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.EmptyCommit("initial commit")
|
||||
// create an origin/foo remote branch
|
||||
shell.CloneIntoRemote("origin")
|
||||
shell.NewBranch("foo")
|
||||
shell.PushBranch("origin", "foo")
|
||||
// delete the local version of the branch because we need to test checking it out from scratch
|
||||
shell.Checkout("master")
|
||||
shell.DeleteBranch("foo")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Branches().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("master").IsSelected(),
|
||||
).
|
||||
// maximising window so that we can see the tracked branch
|
||||
Press(keys.Universal.NextScreenMode).
|
||||
Press(keys.Branches.CheckoutBranchByName).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Prompt().
|
||||
Title(Equals("Branch name:")).
|
||||
Type("foo").
|
||||
SuggestionLines(
|
||||
Contains("foo"),
|
||||
Contains("origin/foo"),
|
||||
).
|
||||
ConfirmFirstSuggestion()
|
||||
}).
|
||||
Lines(
|
||||
Contains("foo").
|
||||
// we have not checked out origin/foo...
|
||||
DoesNotContain("origin/foo").
|
||||
// ... but we are tracking it (formatted as '<remote> <branch>')
|
||||
Contains("origin foo"),
|
||||
Contains("master"),
|
||||
).
|
||||
Press(keys.Branches.CheckoutBranchByName).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Prompt().
|
||||
Title(Equals("Branch name:")).
|
||||
Type("origin/foo").
|
||||
SuggestionLines(
|
||||
Contains("origin/foo"),
|
||||
).
|
||||
ConfirmFirstSuggestion()
|
||||
}).
|
||||
Lines(
|
||||
Contains("HEAD detached at origin/foo"),
|
||||
Contains("foo"),
|
||||
Contains("master"),
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -1,59 +0,0 @@
|
||||
package commit
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var AutoWrapMessage = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Commit, and test how the commit message body is auto-wrapped",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {
|
||||
// Use a ridiculously small width so that we don't have to use so much test data
|
||||
config.UserConfig.Git.Commit.AutoWrapWidth = 20
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFile("file", "file content")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
IsEmpty()
|
||||
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
PressPrimaryAction(). // stage file
|
||||
Press(keys.Files.CommitChanges)
|
||||
|
||||
t.ExpectPopup().CommitMessagePanel().
|
||||
Type("subject").
|
||||
SwitchToDescription().
|
||||
Type("Lorem ipsum dolor sit amet, consectetur adipiscing elit.").
|
||||
// See how it automatically inserted line feeds to wrap the text:
|
||||
Content(Equals("Lorem ipsum dolor \nsit amet, \nconsectetur \nadipiscing elit.")).
|
||||
SwitchToSummary().
|
||||
Confirm()
|
||||
|
||||
t.Views().Commits().
|
||||
Lines(
|
||||
Contains("subject"),
|
||||
).
|
||||
Focus().
|
||||
Tap(func() {
|
||||
t.Views().Main().Content(Contains(
|
||||
"subject\n \n Lorem ipsum dolor\n sit amet,\n consectetur\n adipiscing elit."))
|
||||
}).
|
||||
Press(keys.Commits.RenameCommit)
|
||||
|
||||
// Test that when rewording, the hard line breaks are turned back into
|
||||
// soft ones, so that we can insert text at the beginning and have the
|
||||
// paragraph reflow nicely.
|
||||
t.ExpectPopup().CommitMessagePanel().
|
||||
InitialText(Equals("subject")).
|
||||
SwitchToDescription().
|
||||
Content(Equals("Lorem ipsum dolor \nsit amet, \nconsectetur \nadipiscing elit.")).
|
||||
GoToBeginning().
|
||||
Type("More text. ").
|
||||
Content(Equals("More text. Lorem \nipsum dolor sit \namet, consectetur \nadipiscing elit."))
|
||||
},
|
||||
})
|
||||
@@ -1,51 +0,0 @@
|
||||
package interactive_rebase
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/tests/shared"
|
||||
)
|
||||
|
||||
var EditRangeSelectOutsideRebase = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Select a range of commits to edit outside of a rebase",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
GitVersion: AtLeast("2.22.0"), // first version that supports the --rebase-merges option
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shared.CreateMergeCommit(shell)
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
TopLines(
|
||||
Contains("Merge branch 'second-change-branch' into first-change-branch").IsSelected(),
|
||||
).
|
||||
Press(keys.Universal.RangeSelectDown).
|
||||
Press(keys.Universal.RangeSelectDown).
|
||||
Press(keys.Universal.RangeSelectDown).
|
||||
Press(keys.Universal.RangeSelectDown).
|
||||
Press(keys.Universal.RangeSelectDown).
|
||||
Lines(
|
||||
Contains("CI ⏣─╮ Merge branch 'second-change-branch' into first-change-branch").IsSelected(),
|
||||
Contains("CI │ ◯ * second-change-branch unrelated change").IsSelected(),
|
||||
Contains("CI │ ◯ second change").IsSelected(),
|
||||
Contains("CI ◯ │ first change").IsSelected(),
|
||||
Contains("CI ◯─╯ * original").IsSelected(),
|
||||
Contains("CI ◯ three").IsSelected(),
|
||||
Contains("CI ◯ two"),
|
||||
Contains("CI ◯ one"),
|
||||
).
|
||||
Press(keys.Universal.Edit).
|
||||
Lines(
|
||||
Contains("merge CI Merge branch 'second-change-branch' into first-change-branch").IsSelected(),
|
||||
Contains("edit CI first change").IsSelected(),
|
||||
Contains("edit CI * second-change-branch unrelated change").IsSelected(),
|
||||
Contains("edit CI second change").IsSelected(),
|
||||
Contains("edit CI * original").IsSelected(),
|
||||
Contains(" CI ◯ <-- YOU ARE HERE --- three").IsSelected(),
|
||||
Contains(" CI ◯ two"),
|
||||
Contains(" CI ◯ one"),
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -20,6 +20,27 @@ var MidRebaseRangeSelect = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
TopLines(
|
||||
Contains("commit 10").IsSelected(),
|
||||
).
|
||||
NavigateToLine(Contains("commit 07")).
|
||||
Press(keys.Universal.RangeSelectDown).
|
||||
TopLines(
|
||||
Contains("commit 10"),
|
||||
Contains("commit 09"),
|
||||
Contains("commit 08"),
|
||||
Contains("commit 07").IsSelected(),
|
||||
Contains("commit 06").IsSelected(),
|
||||
Contains("commit 05"),
|
||||
Contains("commit 04"),
|
||||
).
|
||||
// Verify we can't perform an edit on multiple commits (it's not supported
|
||||
// yet)
|
||||
Press(keys.Universal.Edit).
|
||||
Tap(func() {
|
||||
// This ought to be a toast but I'm too lazy to implement that right now.
|
||||
t.ExpectPopup().Alert().
|
||||
Title(Equals("Error")).
|
||||
Content(Contains("Action does not support range selection, please select a single item")).
|
||||
Confirm()
|
||||
}).
|
||||
NavigateToLine(Contains("commit 05")).
|
||||
// Start a rebase
|
||||
Press(keys.Universal.Edit).
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
package interactive_rebase
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var SquashFixupsAbove = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Squashes all fixups above a commit and checks that the selected line stays correct.",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.
|
||||
CreateNCommits(3).
|
||||
CreateFileAndAdd("fixup-file", "fixup content")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("commit 03"),
|
||||
Contains("commit 02"),
|
||||
Contains("commit 01"),
|
||||
).
|
||||
NavigateToLine(Contains("commit 02")).
|
||||
Press(keys.Commits.CreateFixupCommit).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Create fixup commit")).
|
||||
Content(Contains("Are you sure you want to create a fixup! commit for commit")).
|
||||
Confirm()
|
||||
}).
|
||||
Lines(
|
||||
Contains("fixup! commit 02"),
|
||||
Contains("commit 03"),
|
||||
Contains("commit 02").IsSelected(),
|
||||
Contains("commit 01"),
|
||||
).
|
||||
Press(keys.Commits.SquashAboveCommits).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Menu().
|
||||
Title(Equals("Apply fixup commits")).
|
||||
Select(Contains("Above the selected commit")).
|
||||
Confirm()
|
||||
}).
|
||||
Lines(
|
||||
Contains("commit 03"),
|
||||
Contains("commit 02").IsSelected(),
|
||||
Contains("commit 01"),
|
||||
)
|
||||
|
||||
t.Views().Main().
|
||||
Content(Contains("fixup content"))
|
||||
},
|
||||
})
|
||||
@@ -27,12 +27,10 @@ var SquashFixupsInCurrentBranch = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
SelectNextItem().
|
||||
SelectNextItem().
|
||||
Lines(
|
||||
Contains("fixup! commit 01"),
|
||||
Contains("commit 02"),
|
||||
Contains("commit 01").IsSelected(),
|
||||
Contains("commit 01"),
|
||||
Contains("fixup! master commit"),
|
||||
Contains("master commit"),
|
||||
).
|
||||
@@ -45,10 +43,11 @@ var SquashFixupsInCurrentBranch = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
}).
|
||||
Lines(
|
||||
Contains("commit 02"),
|
||||
Contains("commit 01").IsSelected(),
|
||||
Contains("commit 01"),
|
||||
Contains("fixup! master commit"),
|
||||
Contains("master commit"),
|
||||
)
|
||||
).
|
||||
NavigateToLine(Contains("commit 01"))
|
||||
|
||||
t.Views().Main().
|
||||
Content(Contains("fixup content"))
|
||||
|
||||
@@ -37,6 +37,7 @@ var tests = []*components.IntegrationTest{
|
||||
bisect.FromOtherBranch,
|
||||
bisect.Skip,
|
||||
branch.CheckoutByName,
|
||||
branch.CheckoutByNameRemote,
|
||||
branch.CreateTag,
|
||||
branch.Delete,
|
||||
branch.DeleteRemoteBranchWithCredentialPrompt,
|
||||
@@ -65,7 +66,6 @@ var tests = []*components.IntegrationTest{
|
||||
cherry_pick.CherryPickRange,
|
||||
commit.AddCoAuthor,
|
||||
commit.Amend,
|
||||
commit.AutoWrapMessage,
|
||||
commit.Commit,
|
||||
commit.CommitMultiline,
|
||||
commit.CommitSwitchToEditor,
|
||||
@@ -168,7 +168,6 @@ var tests = []*components.IntegrationTest{
|
||||
interactive_rebase.DropWithCustomCommentChar,
|
||||
interactive_rebase.EditFirstCommit,
|
||||
interactive_rebase.EditNonTodoCommitDuringRebase,
|
||||
interactive_rebase.EditRangeSelectOutsideRebase,
|
||||
interactive_rebase.EditTheConflCommit,
|
||||
interactive_rebase.FixupFirstCommit,
|
||||
interactive_rebase.FixupSecondCommit,
|
||||
@@ -189,7 +188,6 @@ var tests = []*components.IntegrationTest{
|
||||
interactive_rebase.RewordYouAreHereCommitWithEditor,
|
||||
interactive_rebase.SquashDownFirstCommit,
|
||||
interactive_rebase.SquashDownSecondCommit,
|
||||
interactive_rebase.SquashFixupsAbove,
|
||||
interactive_rebase.SquashFixupsAboveFirstCommit,
|
||||
interactive_rebase.SquashFixupsInCurrentBranch,
|
||||
interactive_rebase.SwapInRebaseWithConflict,
|
||||
|
||||
@@ -405,16 +405,6 @@
|
||||
"signOff": {
|
||||
"type": "boolean",
|
||||
"description": "If true, pass '--signoff' flag when committing"
|
||||
},
|
||||
"autoWrapCommitMessage": {
|
||||
"type": "boolean",
|
||||
"description": "Automatic WYSIWYG wrapping of the commit message as you type",
|
||||
"default": true
|
||||
},
|
||||
"autoWrapWidth": {
|
||||
"type": "integer",
|
||||
"description": "If autoWrapCommitMessage is true, the width to wrap to",
|
||||
"default": 72
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
+3
-3
@@ -341,12 +341,12 @@ func (s *cScreen) disengage() {
|
||||
}
|
||||
} else if !s.disableAlt {
|
||||
s.clearScreen(StyleDefault, s.vten)
|
||||
s.setCursorPos(0, 0, false)
|
||||
}
|
||||
s.setCursorInfo(&s.ocursor)
|
||||
s.setBufferSize(int(s.oscreen.size.x), int(s.oscreen.size.y))
|
||||
s.setInMode(s.oimode)
|
||||
s.setOutMode(s.oomode)
|
||||
s.setBufferSize(int(s.oscreen.size.x), int(s.oscreen.size.y))
|
||||
s.setCursorPos(0, 0, false)
|
||||
s.setCursorInfo(&s.ocursor)
|
||||
_, _, _ = procSetConsoleTextAttribute.Call(
|
||||
uintptr(s.out),
|
||||
uintptr(s.mapStyle(StyleDefault)))
|
||||
|
||||
+14
-154
@@ -11,59 +11,11 @@ const (
|
||||
WORD_SEPARATORS = "*?_+-.[]~=/&;!#$%^(){}<>"
|
||||
)
|
||||
|
||||
type CursorMapping struct {
|
||||
Orig int
|
||||
Wrapped int
|
||||
}
|
||||
|
||||
type TextArea struct {
|
||||
content []rune
|
||||
wrappedContent []rune
|
||||
cursorMapping []CursorMapping
|
||||
cursor int
|
||||
overwrite bool
|
||||
clipboard string
|
||||
AutoWrap bool
|
||||
AutoWrapWidth int
|
||||
}
|
||||
|
||||
func AutoWrapContent(content []rune, autoWrapWidth int) ([]rune, []CursorMapping) {
|
||||
estimatedNumberOfSoftLineBreaks := len(content) / autoWrapWidth
|
||||
cursorMapping := make([]CursorMapping, 0, estimatedNumberOfSoftLineBreaks)
|
||||
wrappedContent := make([]rune, 0, len(content)+estimatedNumberOfSoftLineBreaks)
|
||||
startOfLine := 0
|
||||
indexOfLastWhitespace := -1
|
||||
|
||||
for currentPos, r := range content {
|
||||
if r == '\n' {
|
||||
wrappedContent = append(wrappedContent, content[startOfLine:currentPos+1]...)
|
||||
startOfLine = currentPos + 1
|
||||
indexOfLastWhitespace = -1
|
||||
} else {
|
||||
if r == ' ' {
|
||||
indexOfLastWhitespace = currentPos + 1
|
||||
} else if currentPos-startOfLine >= autoWrapWidth && indexOfLastWhitespace >= 0 {
|
||||
wrapAt := indexOfLastWhitespace
|
||||
wrappedContent = append(wrappedContent, content[startOfLine:wrapAt]...)
|
||||
wrappedContent = append(wrappedContent, '\n')
|
||||
cursorMapping = append(cursorMapping, CursorMapping{wrapAt, len(wrappedContent)})
|
||||
startOfLine = wrapAt
|
||||
indexOfLastWhitespace = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrappedContent = append(wrappedContent, content[startOfLine:]...)
|
||||
|
||||
return wrappedContent, cursorMapping
|
||||
}
|
||||
|
||||
func (self *TextArea) autoWrapContent() {
|
||||
if self.AutoWrap {
|
||||
self.wrappedContent, self.cursorMapping = AutoWrapContent(self.content, self.AutoWrapWidth)
|
||||
} else {
|
||||
self.wrappedContent, self.cursorMapping = self.content, []CursorMapping{}
|
||||
}
|
||||
content []rune
|
||||
cursor int
|
||||
overwrite bool
|
||||
clipboard string
|
||||
}
|
||||
|
||||
func (self *TextArea) TypeRune(r rune) {
|
||||
@@ -75,7 +27,6 @@ func (self *TextArea) TypeRune(r rune) {
|
||||
append([]rune{r}, self.content[self.cursor:]...)...,
|
||||
)
|
||||
}
|
||||
self.autoWrapContent()
|
||||
|
||||
self.cursor++
|
||||
}
|
||||
@@ -86,7 +37,6 @@ func (self *TextArea) BackSpaceChar() {
|
||||
}
|
||||
|
||||
self.content = append(self.content[:self.cursor-1], self.content[self.cursor:]...)
|
||||
self.autoWrapContent()
|
||||
self.cursor--
|
||||
}
|
||||
|
||||
@@ -96,7 +46,6 @@ func (self *TextArea) DeleteChar() {
|
||||
}
|
||||
|
||||
self.content = append(self.content[:self.cursor], self.content[self.cursor+1:]...)
|
||||
self.autoWrapContent()
|
||||
}
|
||||
|
||||
func (self *TextArea) MoveCursorLeft() {
|
||||
@@ -174,10 +123,6 @@ func (self *TextArea) MoveCursorDown() {
|
||||
}
|
||||
|
||||
func (self *TextArea) GetContent() string {
|
||||
return string(self.wrappedContent)
|
||||
}
|
||||
|
||||
func (self *TextArea) GetUnwrappedContent() string {
|
||||
return string(self.content)
|
||||
}
|
||||
|
||||
@@ -199,24 +144,14 @@ func (self *TextArea) DeleteToStartOfLine() {
|
||||
|
||||
self.content = append(self.content[:self.cursor-1], self.content[self.cursor:]...)
|
||||
self.cursor--
|
||||
self.autoWrapContent()
|
||||
return
|
||||
}
|
||||
|
||||
// otherwise, if we're at a soft line start, skip left past the soft line
|
||||
// break, so we'll end up deleting the previous line. This seems like the
|
||||
// only reasonable behavior in this case, as you can't delete just the soft
|
||||
// line break.
|
||||
if self.atSoftLineStart() {
|
||||
self.cursor--
|
||||
}
|
||||
|
||||
// otherwise, you delete everything up to the start of the current line, without
|
||||
// deleting the newline character
|
||||
newlineIndex := self.closestNewlineOnLeft()
|
||||
self.clipboard = string(self.content[newlineIndex+1 : self.cursor])
|
||||
self.content = append(self.content[:newlineIndex+1], self.content[self.cursor:]...)
|
||||
self.autoWrapContent()
|
||||
self.cursor = newlineIndex + 1
|
||||
}
|
||||
|
||||
@@ -224,30 +159,18 @@ func (self *TextArea) DeleteToEndOfLine() {
|
||||
if self.atEnd() {
|
||||
return
|
||||
}
|
||||
|
||||
// if we're at the end of the line, delete just the newline character
|
||||
if self.atLineEnd() {
|
||||
self.content = append(self.content[:self.cursor], self.content[self.cursor+1:]...)
|
||||
self.autoWrapContent()
|
||||
return
|
||||
}
|
||||
|
||||
// otherwise, if we're at a soft line end, skip right past the soft line
|
||||
// break, so we'll end up deleting the next line. This seems like the
|
||||
// only reasonable behavior in this case, as you can't delete just the soft
|
||||
// line break.
|
||||
if self.atSoftLineEnd() {
|
||||
self.cursor++
|
||||
}
|
||||
|
||||
lineEndIndex := self.closestNewlineOnRight()
|
||||
self.clipboard = string(self.content[self.cursor:lineEndIndex])
|
||||
self.content = append(self.content[:self.cursor], self.content[lineEndIndex:]...)
|
||||
self.autoWrapContent()
|
||||
}
|
||||
|
||||
func (self *TextArea) GoToStartOfLine() {
|
||||
if self.atSoftLineStart() {
|
||||
if self.atLineStart() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -258,21 +181,15 @@ func (self *TextArea) GoToStartOfLine() {
|
||||
}
|
||||
|
||||
func (self *TextArea) closestNewlineOnLeft() int {
|
||||
wrappedCursor := self.origCursorToWrappedCursor(self.cursor)
|
||||
|
||||
newlineIndex := -1
|
||||
|
||||
for i, r := range self.wrappedContent[0:wrappedCursor] {
|
||||
for i, r := range self.content[0:self.cursor] {
|
||||
if r == '\n' {
|
||||
newlineIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
unwrappedNewlineIndex := self.wrappedCursorToOrigCursor(newlineIndex)
|
||||
if unwrappedNewlineIndex >= 0 && self.content[unwrappedNewlineIndex] != '\n' {
|
||||
unwrappedNewlineIndex--
|
||||
}
|
||||
return unwrappedNewlineIndex
|
||||
return newlineIndex
|
||||
}
|
||||
|
||||
func (self *TextArea) GoToEndOfLine() {
|
||||
@@ -281,22 +198,12 @@ func (self *TextArea) GoToEndOfLine() {
|
||||
}
|
||||
|
||||
self.cursor = self.closestNewlineOnRight()
|
||||
|
||||
// If the end of line is a soft line break, we need to move left by one so
|
||||
// that we end up at the last whitespace before the line break. Otherwise
|
||||
// we'd be at the start of the next line, since the newline character
|
||||
// doesn't really exist in the real content.
|
||||
if self.cursor < len(self.content) && self.content[self.cursor] != '\n' {
|
||||
self.cursor--
|
||||
}
|
||||
}
|
||||
|
||||
func (self *TextArea) closestNewlineOnRight() int {
|
||||
wrappedCursor := self.origCursorToWrappedCursor(self.cursor)
|
||||
|
||||
for i, r := range self.wrappedContent[wrappedCursor:] {
|
||||
for i, r := range self.content[self.cursor:] {
|
||||
if r == '\n' {
|
||||
return self.wrappedCursorToOrigCursor(wrappedCursor + i)
|
||||
return self.cursor + i
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,23 +215,11 @@ func (self *TextArea) atLineStart() bool {
|
||||
(len(self.content) > self.cursor-1 && self.content[self.cursor-1] == '\n')
|
||||
}
|
||||
|
||||
func (self *TextArea) atSoftLineStart() bool {
|
||||
wrappedCursor := self.origCursorToWrappedCursor(self.cursor)
|
||||
return wrappedCursor == 0 ||
|
||||
(len(self.wrappedContent) > wrappedCursor-1 && self.wrappedContent[wrappedCursor-1] == '\n')
|
||||
}
|
||||
|
||||
func (self *TextArea) atLineEnd() bool {
|
||||
return self.atEnd() ||
|
||||
(len(self.content) > self.cursor && self.content[self.cursor] == '\n')
|
||||
}
|
||||
|
||||
func (self *TextArea) atSoftLineEnd() bool {
|
||||
wrappedCursor := self.origCursorToWrappedCursor(self.cursor)
|
||||
return wrappedCursor == len(self.wrappedContent) ||
|
||||
(len(self.wrappedContent) > wrappedCursor+1 && self.wrappedContent[wrappedCursor+1] == '\n')
|
||||
}
|
||||
|
||||
func (self *TextArea) BackSpaceWord() {
|
||||
if self.cursor == 0 {
|
||||
return
|
||||
@@ -351,50 +246,16 @@ func (self *TextArea) BackSpaceWord() {
|
||||
|
||||
self.clipboard = string(self.content[self.cursor:right])
|
||||
self.content = append(self.content[:self.cursor], self.content[right:]...)
|
||||
self.autoWrapContent()
|
||||
}
|
||||
|
||||
func (self *TextArea) Yank() {
|
||||
self.TypeString(self.clipboard)
|
||||
}
|
||||
|
||||
func origCursorToWrappedCursor(origCursor int, cursorMapping []CursorMapping) int {
|
||||
prevMapping := CursorMapping{0, 0}
|
||||
for _, mapping := range cursorMapping {
|
||||
if origCursor < mapping.Orig {
|
||||
break
|
||||
}
|
||||
prevMapping = mapping
|
||||
}
|
||||
|
||||
return origCursor + prevMapping.Wrapped - prevMapping.Orig
|
||||
}
|
||||
|
||||
func (self *TextArea) origCursorToWrappedCursor(origCursor int) int {
|
||||
return origCursorToWrappedCursor(origCursor, self.cursorMapping)
|
||||
}
|
||||
|
||||
func wrappedCursorToOrigCursor(wrappedCursor int, cursorMapping []CursorMapping) int {
|
||||
prevMapping := CursorMapping{0, 0}
|
||||
for _, mapping := range cursorMapping {
|
||||
if wrappedCursor < mapping.Wrapped {
|
||||
break
|
||||
}
|
||||
prevMapping = mapping
|
||||
}
|
||||
|
||||
return wrappedCursor + prevMapping.Orig - prevMapping.Wrapped
|
||||
}
|
||||
|
||||
func (self *TextArea) wrappedCursorToOrigCursor(wrappedCursor int) int {
|
||||
return wrappedCursorToOrigCursor(wrappedCursor, self.cursorMapping)
|
||||
}
|
||||
|
||||
func (self *TextArea) GetCursorXY() (int, int) {
|
||||
cursorX := 0
|
||||
cursorY := 0
|
||||
wrappedCursor := self.origCursorToWrappedCursor(self.cursor)
|
||||
for _, r := range self.wrappedContent[0:wrappedCursor] {
|
||||
for _, r := range self.content[0:self.cursor] {
|
||||
if r == '\n' {
|
||||
cursorY++
|
||||
cursorX = 0
|
||||
@@ -417,15 +278,15 @@ func (self *TextArea) SetCursor2D(x int, y int) {
|
||||
}
|
||||
|
||||
newCursor := 0
|
||||
for _, r := range self.wrappedContent {
|
||||
for _, r := range self.content {
|
||||
if x <= 0 && y == 0 {
|
||||
self.cursor = self.wrappedCursorToOrigCursor(newCursor)
|
||||
self.cursor = newCursor
|
||||
return
|
||||
}
|
||||
|
||||
if r == '\n' {
|
||||
if y == 0 {
|
||||
self.cursor = self.wrappedCursorToOrigCursor(newCursor)
|
||||
self.cursor = newCursor
|
||||
return
|
||||
}
|
||||
y--
|
||||
@@ -443,12 +304,11 @@ func (self *TextArea) SetCursor2D(x int, y int) {
|
||||
return
|
||||
}
|
||||
|
||||
self.cursor = self.wrappedCursorToOrigCursor(newCursor)
|
||||
self.cursor = newCursor
|
||||
}
|
||||
|
||||
func (self *TextArea) Clear() {
|
||||
self.content = []rune{}
|
||||
self.wrappedContent = []rune{}
|
||||
self.cursor = 0
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
|
||||
//go:build (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos) && go1.9
|
||||
|
||||
package unix
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin
|
||||
//go:build darwin && go1.12
|
||||
|
||||
package unix
|
||||
|
||||
|
||||
+5
-7
@@ -13,7 +13,6 @@
|
||||
package unix
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
@@ -170,26 +169,25 @@ func Getfsstat(buf []Statfs_t, flags int) (n int, err error) {
|
||||
func Uname(uname *Utsname) error {
|
||||
mib := []_C_int{CTL_KERN, KERN_OSTYPE}
|
||||
n := unsafe.Sizeof(uname.Sysname)
|
||||
// Suppress ENOMEM errors to be compatible with the C library __xuname() implementation.
|
||||
if err := sysctl(mib, &uname.Sysname[0], &n, nil, 0); err != nil && !errors.Is(err, ENOMEM) {
|
||||
if err := sysctl(mib, &uname.Sysname[0], &n, nil, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mib = []_C_int{CTL_KERN, KERN_HOSTNAME}
|
||||
n = unsafe.Sizeof(uname.Nodename)
|
||||
if err := sysctl(mib, &uname.Nodename[0], &n, nil, 0); err != nil && !errors.Is(err, ENOMEM) {
|
||||
if err := sysctl(mib, &uname.Nodename[0], &n, nil, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mib = []_C_int{CTL_KERN, KERN_OSRELEASE}
|
||||
n = unsafe.Sizeof(uname.Release)
|
||||
if err := sysctl(mib, &uname.Release[0], &n, nil, 0); err != nil && !errors.Is(err, ENOMEM) {
|
||||
if err := sysctl(mib, &uname.Release[0], &n, nil, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mib = []_C_int{CTL_KERN, KERN_VERSION}
|
||||
n = unsafe.Sizeof(uname.Version)
|
||||
if err := sysctl(mib, &uname.Version[0], &n, nil, 0); err != nil && !errors.Is(err, ENOMEM) {
|
||||
if err := sysctl(mib, &uname.Version[0], &n, nil, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -207,7 +205,7 @@ func Uname(uname *Utsname) error {
|
||||
|
||||
mib = []_C_int{CTL_HW, HW_MACHINE}
|
||||
n = unsafe.Sizeof(uname.Machine)
|
||||
if err := sysctl(mib, &uname.Machine[0], &n, nil, 0); err != nil && !errors.Is(err, ENOMEM) {
|
||||
if err := sysctl(mib, &uname.Machine[0], &n, nil, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
-99
@@ -1849,105 +1849,6 @@ func Dup2(oldfd, newfd int) error {
|
||||
//sys Fsmount(fd int, flags int, mountAttrs int) (fsfd int, err error)
|
||||
//sys Fsopen(fsName string, flags int) (fd int, err error)
|
||||
//sys Fspick(dirfd int, pathName string, flags int) (fd int, err error)
|
||||
|
||||
//sys fsconfig(fd int, cmd uint, key *byte, value *byte, aux int) (err error)
|
||||
|
||||
func fsconfigCommon(fd int, cmd uint, key string, value *byte, aux int) (err error) {
|
||||
var keyp *byte
|
||||
if keyp, err = BytePtrFromString(key); err != nil {
|
||||
return
|
||||
}
|
||||
return fsconfig(fd, cmd, keyp, value, aux)
|
||||
}
|
||||
|
||||
// FsconfigSetFlag is equivalent to fsconfig(2) called
|
||||
// with cmd == FSCONFIG_SET_FLAG.
|
||||
//
|
||||
// fd is the filesystem context to act upon.
|
||||
// key the parameter key to set.
|
||||
func FsconfigSetFlag(fd int, key string) (err error) {
|
||||
return fsconfigCommon(fd, FSCONFIG_SET_FLAG, key, nil, 0)
|
||||
}
|
||||
|
||||
// FsconfigSetString is equivalent to fsconfig(2) called
|
||||
// with cmd == FSCONFIG_SET_STRING.
|
||||
//
|
||||
// fd is the filesystem context to act upon.
|
||||
// key the parameter key to set.
|
||||
// value is the parameter value to set.
|
||||
func FsconfigSetString(fd int, key string, value string) (err error) {
|
||||
var valuep *byte
|
||||
if valuep, err = BytePtrFromString(value); err != nil {
|
||||
return
|
||||
}
|
||||
return fsconfigCommon(fd, FSCONFIG_SET_STRING, key, valuep, 0)
|
||||
}
|
||||
|
||||
// FsconfigSetBinary is equivalent to fsconfig(2) called
|
||||
// with cmd == FSCONFIG_SET_BINARY.
|
||||
//
|
||||
// fd is the filesystem context to act upon.
|
||||
// key the parameter key to set.
|
||||
// value is the parameter value to set.
|
||||
func FsconfigSetBinary(fd int, key string, value []byte) (err error) {
|
||||
if len(value) == 0 {
|
||||
return EINVAL
|
||||
}
|
||||
return fsconfigCommon(fd, FSCONFIG_SET_BINARY, key, &value[0], len(value))
|
||||
}
|
||||
|
||||
// FsconfigSetPath is equivalent to fsconfig(2) called
|
||||
// with cmd == FSCONFIG_SET_PATH.
|
||||
//
|
||||
// fd is the filesystem context to act upon.
|
||||
// key the parameter key to set.
|
||||
// path is a non-empty path for specified key.
|
||||
// atfd is a file descriptor at which to start lookup from or AT_FDCWD.
|
||||
func FsconfigSetPath(fd int, key string, path string, atfd int) (err error) {
|
||||
var valuep *byte
|
||||
if valuep, err = BytePtrFromString(path); err != nil {
|
||||
return
|
||||
}
|
||||
return fsconfigCommon(fd, FSCONFIG_SET_PATH, key, valuep, atfd)
|
||||
}
|
||||
|
||||
// FsconfigSetPathEmpty is equivalent to fsconfig(2) called
|
||||
// with cmd == FSCONFIG_SET_PATH_EMPTY. The same as
|
||||
// FconfigSetPath but with AT_PATH_EMPTY implied.
|
||||
func FsconfigSetPathEmpty(fd int, key string, path string, atfd int) (err error) {
|
||||
var valuep *byte
|
||||
if valuep, err = BytePtrFromString(path); err != nil {
|
||||
return
|
||||
}
|
||||
return fsconfigCommon(fd, FSCONFIG_SET_PATH_EMPTY, key, valuep, atfd)
|
||||
}
|
||||
|
||||
// FsconfigSetFd is equivalent to fsconfig(2) called
|
||||
// with cmd == FSCONFIG_SET_FD.
|
||||
//
|
||||
// fd is the filesystem context to act upon.
|
||||
// key the parameter key to set.
|
||||
// value is a file descriptor to be assigned to specified key.
|
||||
func FsconfigSetFd(fd int, key string, value int) (err error) {
|
||||
return fsconfigCommon(fd, FSCONFIG_SET_FD, key, nil, value)
|
||||
}
|
||||
|
||||
// FsconfigCreate is equivalent to fsconfig(2) called
|
||||
// with cmd == FSCONFIG_CMD_CREATE.
|
||||
//
|
||||
// fd is the filesystem context to act upon.
|
||||
func FsconfigCreate(fd int) (err error) {
|
||||
return fsconfig(fd, FSCONFIG_CMD_CREATE, nil, nil, 0)
|
||||
}
|
||||
|
||||
// FsconfigReconfigure is equivalent to fsconfig(2) called
|
||||
// with cmd == FSCONFIG_CMD_RECONFIGURE.
|
||||
//
|
||||
// fd is the filesystem context to act upon.
|
||||
func FsconfigReconfigure(fd int) (err error) {
|
||||
return fsconfig(fd, FSCONFIG_CMD_RECONFIGURE, nil, nil, 0)
|
||||
}
|
||||
|
||||
//sys Getdents(fd int, buf []byte) (n int, err error) = SYS_GETDENTS64
|
||||
//sysnb Getpgid(pid int) (pgid int, err error)
|
||||
|
||||
|
||||
-10
@@ -906,16 +906,6 @@ func Fspick(dirfd int, pathName string, flags int) (fd int, err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func fsconfig(fd int, cmd uint, key *byte, value *byte, aux int) (err error) {
|
||||
_, _, e1 := Syscall6(SYS_FSCONFIG, uintptr(fd), uintptr(cmd), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(value)), uintptr(aux), 0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Getdents(fd int, buf []byte) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(buf) > 0 {
|
||||
|
||||
-60
@@ -836,15 +836,6 @@ const (
|
||||
FSPICK_EMPTY_PATH = 0x8
|
||||
|
||||
FSMOUNT_CLOEXEC = 0x1
|
||||
|
||||
FSCONFIG_SET_FLAG = 0x0
|
||||
FSCONFIG_SET_STRING = 0x1
|
||||
FSCONFIG_SET_BINARY = 0x2
|
||||
FSCONFIG_SET_PATH = 0x3
|
||||
FSCONFIG_SET_PATH_EMPTY = 0x4
|
||||
FSCONFIG_SET_FD = 0x5
|
||||
FSCONFIG_CMD_CREATE = 0x6
|
||||
FSCONFIG_CMD_RECONFIGURE = 0x7
|
||||
)
|
||||
|
||||
type OpenHow struct {
|
||||
@@ -1559,7 +1550,6 @@ const (
|
||||
IFLA_DEVLINK_PORT = 0x3e
|
||||
IFLA_GSO_IPV4_MAX_SIZE = 0x3f
|
||||
IFLA_GRO_IPV4_MAX_SIZE = 0x40
|
||||
IFLA_DPLL_PIN = 0x41
|
||||
IFLA_PROTO_DOWN_REASON_UNSPEC = 0x0
|
||||
IFLA_PROTO_DOWN_REASON_MASK = 0x1
|
||||
IFLA_PROTO_DOWN_REASON_VALUE = 0x2
|
||||
@@ -1575,7 +1565,6 @@ const (
|
||||
IFLA_INET6_ICMP6STATS = 0x6
|
||||
IFLA_INET6_TOKEN = 0x7
|
||||
IFLA_INET6_ADDR_GEN_MODE = 0x8
|
||||
IFLA_INET6_RA_MTU = 0x9
|
||||
IFLA_BR_UNSPEC = 0x0
|
||||
IFLA_BR_FORWARD_DELAY = 0x1
|
||||
IFLA_BR_HELLO_TIME = 0x2
|
||||
@@ -1623,9 +1612,6 @@ const (
|
||||
IFLA_BR_MCAST_MLD_VERSION = 0x2c
|
||||
IFLA_BR_VLAN_STATS_PER_PORT = 0x2d
|
||||
IFLA_BR_MULTI_BOOLOPT = 0x2e
|
||||
IFLA_BR_MCAST_QUERIER_STATE = 0x2f
|
||||
IFLA_BR_FDB_N_LEARNED = 0x30
|
||||
IFLA_BR_FDB_MAX_LEARNED = 0x31
|
||||
IFLA_BRPORT_UNSPEC = 0x0
|
||||
IFLA_BRPORT_STATE = 0x1
|
||||
IFLA_BRPORT_PRIORITY = 0x2
|
||||
@@ -1663,14 +1649,6 @@ const (
|
||||
IFLA_BRPORT_BACKUP_PORT = 0x22
|
||||
IFLA_BRPORT_MRP_RING_OPEN = 0x23
|
||||
IFLA_BRPORT_MRP_IN_OPEN = 0x24
|
||||
IFLA_BRPORT_MCAST_EHT_HOSTS_LIMIT = 0x25
|
||||
IFLA_BRPORT_MCAST_EHT_HOSTS_CNT = 0x26
|
||||
IFLA_BRPORT_LOCKED = 0x27
|
||||
IFLA_BRPORT_MAB = 0x28
|
||||
IFLA_BRPORT_MCAST_N_GROUPS = 0x29
|
||||
IFLA_BRPORT_MCAST_MAX_GROUPS = 0x2a
|
||||
IFLA_BRPORT_NEIGH_VLAN_SUPPRESS = 0x2b
|
||||
IFLA_BRPORT_BACKUP_NHID = 0x2c
|
||||
IFLA_INFO_UNSPEC = 0x0
|
||||
IFLA_INFO_KIND = 0x1
|
||||
IFLA_INFO_DATA = 0x2
|
||||
@@ -1692,9 +1670,6 @@ const (
|
||||
IFLA_MACVLAN_MACADDR = 0x4
|
||||
IFLA_MACVLAN_MACADDR_DATA = 0x5
|
||||
IFLA_MACVLAN_MACADDR_COUNT = 0x6
|
||||
IFLA_MACVLAN_BC_QUEUE_LEN = 0x7
|
||||
IFLA_MACVLAN_BC_QUEUE_LEN_USED = 0x8
|
||||
IFLA_MACVLAN_BC_CUTOFF = 0x9
|
||||
IFLA_VRF_UNSPEC = 0x0
|
||||
IFLA_VRF_TABLE = 0x1
|
||||
IFLA_VRF_PORT_UNSPEC = 0x0
|
||||
@@ -1718,22 +1693,9 @@ const (
|
||||
IFLA_XFRM_UNSPEC = 0x0
|
||||
IFLA_XFRM_LINK = 0x1
|
||||
IFLA_XFRM_IF_ID = 0x2
|
||||
IFLA_XFRM_COLLECT_METADATA = 0x3
|
||||
IFLA_IPVLAN_UNSPEC = 0x0
|
||||
IFLA_IPVLAN_MODE = 0x1
|
||||
IFLA_IPVLAN_FLAGS = 0x2
|
||||
NETKIT_NEXT = -0x1
|
||||
NETKIT_PASS = 0x0
|
||||
NETKIT_DROP = 0x2
|
||||
NETKIT_REDIRECT = 0x7
|
||||
NETKIT_L2 = 0x0
|
||||
NETKIT_L3 = 0x1
|
||||
IFLA_NETKIT_UNSPEC = 0x0
|
||||
IFLA_NETKIT_PEER_INFO = 0x1
|
||||
IFLA_NETKIT_PRIMARY = 0x2
|
||||
IFLA_NETKIT_POLICY = 0x3
|
||||
IFLA_NETKIT_PEER_POLICY = 0x4
|
||||
IFLA_NETKIT_MODE = 0x5
|
||||
IFLA_VXLAN_UNSPEC = 0x0
|
||||
IFLA_VXLAN_ID = 0x1
|
||||
IFLA_VXLAN_GROUP = 0x2
|
||||
@@ -1764,8 +1726,6 @@ const (
|
||||
IFLA_VXLAN_GPE = 0x1b
|
||||
IFLA_VXLAN_TTL_INHERIT = 0x1c
|
||||
IFLA_VXLAN_DF = 0x1d
|
||||
IFLA_VXLAN_VNIFILTER = 0x1e
|
||||
IFLA_VXLAN_LOCALBYPASS = 0x1f
|
||||
IFLA_GENEVE_UNSPEC = 0x0
|
||||
IFLA_GENEVE_ID = 0x1
|
||||
IFLA_GENEVE_REMOTE = 0x2
|
||||
@@ -1780,7 +1740,6 @@ const (
|
||||
IFLA_GENEVE_LABEL = 0xb
|
||||
IFLA_GENEVE_TTL_INHERIT = 0xc
|
||||
IFLA_GENEVE_DF = 0xd
|
||||
IFLA_GENEVE_INNER_PROTO_INHERIT = 0xe
|
||||
IFLA_BAREUDP_UNSPEC = 0x0
|
||||
IFLA_BAREUDP_PORT = 0x1
|
||||
IFLA_BAREUDP_ETHERTYPE = 0x2
|
||||
@@ -1793,8 +1752,6 @@ const (
|
||||
IFLA_GTP_FD1 = 0x2
|
||||
IFLA_GTP_PDP_HASHSIZE = 0x3
|
||||
IFLA_GTP_ROLE = 0x4
|
||||
IFLA_GTP_CREATE_SOCKETS = 0x5
|
||||
IFLA_GTP_RESTART_COUNT = 0x6
|
||||
IFLA_BOND_UNSPEC = 0x0
|
||||
IFLA_BOND_MODE = 0x1
|
||||
IFLA_BOND_ACTIVE_SLAVE = 0x2
|
||||
@@ -1824,9 +1781,6 @@ const (
|
||||
IFLA_BOND_AD_ACTOR_SYSTEM = 0x1a
|
||||
IFLA_BOND_TLB_DYNAMIC_LB = 0x1b
|
||||
IFLA_BOND_PEER_NOTIF_DELAY = 0x1c
|
||||
IFLA_BOND_AD_LACP_ACTIVE = 0x1d
|
||||
IFLA_BOND_MISSED_MAX = 0x1e
|
||||
IFLA_BOND_NS_IP6_TARGET = 0x1f
|
||||
IFLA_BOND_AD_INFO_UNSPEC = 0x0
|
||||
IFLA_BOND_AD_INFO_AGGREGATOR = 0x1
|
||||
IFLA_BOND_AD_INFO_NUM_PORTS = 0x2
|
||||
@@ -1842,7 +1796,6 @@ const (
|
||||
IFLA_BOND_SLAVE_AD_AGGREGATOR_ID = 0x6
|
||||
IFLA_BOND_SLAVE_AD_ACTOR_OPER_PORT_STATE = 0x7
|
||||
IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE = 0x8
|
||||
IFLA_BOND_SLAVE_PRIO = 0x9
|
||||
IFLA_VF_INFO_UNSPEC = 0x0
|
||||
IFLA_VF_INFO = 0x1
|
||||
IFLA_VF_UNSPEC = 0x0
|
||||
@@ -1901,16 +1854,8 @@ const (
|
||||
IFLA_STATS_LINK_XSTATS_SLAVE = 0x3
|
||||
IFLA_STATS_LINK_OFFLOAD_XSTATS = 0x4
|
||||
IFLA_STATS_AF_SPEC = 0x5
|
||||
IFLA_STATS_GETSET_UNSPEC = 0x0
|
||||
IFLA_STATS_GET_FILTERS = 0x1
|
||||
IFLA_STATS_SET_OFFLOAD_XSTATS_L3_STATS = 0x2
|
||||
IFLA_OFFLOAD_XSTATS_UNSPEC = 0x0
|
||||
IFLA_OFFLOAD_XSTATS_CPU_HIT = 0x1
|
||||
IFLA_OFFLOAD_XSTATS_HW_S_INFO = 0x2
|
||||
IFLA_OFFLOAD_XSTATS_L3_STATS = 0x3
|
||||
IFLA_OFFLOAD_XSTATS_HW_S_INFO_UNSPEC = 0x0
|
||||
IFLA_OFFLOAD_XSTATS_HW_S_INFO_REQUEST = 0x1
|
||||
IFLA_OFFLOAD_XSTATS_HW_S_INFO_USED = 0x2
|
||||
IFLA_XDP_UNSPEC = 0x0
|
||||
IFLA_XDP_FD = 0x1
|
||||
IFLA_XDP_ATTACHED = 0x2
|
||||
@@ -1940,11 +1885,6 @@ const (
|
||||
IFLA_RMNET_UNSPEC = 0x0
|
||||
IFLA_RMNET_MUX_ID = 0x1
|
||||
IFLA_RMNET_FLAGS = 0x2
|
||||
IFLA_MCTP_UNSPEC = 0x0
|
||||
IFLA_MCTP_NET = 0x1
|
||||
IFLA_DSA_UNSPEC = 0x0
|
||||
IFLA_DSA_CONDUIT = 0x1
|
||||
IFLA_DSA_MASTER = 0x1
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
Vendored
+4
-4
@@ -40,7 +40,7 @@ github.com/fsmiamoto/git-todo-parser/todo
|
||||
# github.com/gdamore/encoding v1.0.0
|
||||
## explicit; go 1.9
|
||||
github.com/gdamore/encoding
|
||||
# github.com/gdamore/tcell/v2 v2.7.4
|
||||
# github.com/gdamore/tcell/v2 v2.7.3
|
||||
## explicit; go 1.12
|
||||
github.com/gdamore/tcell/v2
|
||||
github.com/gdamore/tcell/v2/terminfo
|
||||
@@ -172,7 +172,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem
|
||||
github.com/jesseduffield/go-git/v5/utils/merkletrie/index
|
||||
github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame
|
||||
github.com/jesseduffield/go-git/v5/utils/merkletrie/noder
|
||||
# github.com/jesseduffield/gocui v0.3.1-0.20240309085756-86e0d5a312de
|
||||
# github.com/jesseduffield/gocui v0.3.1-0.20240303173746-f2b0f1f68dd8
|
||||
## explicit; go 1.12
|
||||
github.com/jesseduffield/gocui
|
||||
# github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
||||
@@ -313,13 +313,13 @@ golang.org/x/exp/slices
|
||||
golang.org/x/net/context
|
||||
golang.org/x/net/internal/socks
|
||||
golang.org/x/net/proxy
|
||||
# golang.org/x/sys v0.18.0
|
||||
# golang.org/x/sys v0.17.0
|
||||
## explicit; go 1.18
|
||||
golang.org/x/sys/cpu
|
||||
golang.org/x/sys/plan9
|
||||
golang.org/x/sys/unix
|
||||
golang.org/x/sys/windows
|
||||
# golang.org/x/term v0.18.0
|
||||
# golang.org/x/term v0.17.0
|
||||
## explicit; go 1.18
|
||||
golang.org/x/term
|
||||
# golang.org/x/text v0.14.0
|
||||
|
||||
Reference in New Issue
Block a user