mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-06-15 00:15:32 +02:00
Add range selection ability on list contexts
This adds range select ability in two ways: 1) Sticky: like what we already have with the staging view i.e. press v then use arrow keys 2) Non-sticky: where you just use shift+up/down to expand the range The state machine works like this: (no range, press 'v') -> sticky range (no range, press arrow) -> no range (no range, press shift+arrow) -> nonsticky range (sticky range, press 'v') -> no range (sticky range, press arrow) -> sticky range (sticky range, press shift+arrow) -> nonsticky range (nonsticky range, press 'v') -> no range (nonsticky range, press arrow) -> no range (nonsticky range, press shift+arrow) -> nonsticky range
This commit is contained in:
48
vendor/github.com/jesseduffield/gocui/keybinding.go
generated
vendored
48
vendor/github.com/jesseduffield/gocui/keybinding.go
generated
vendored
@ -143,7 +143,9 @@ var translate = map[string]Key{
|
||||
"Pgup": KeyPgup,
|
||||
"Pgdn": KeyPgdn,
|
||||
"ArrowUp": KeyArrowUp,
|
||||
"ShiftArrowUp": KeyShiftArrowUp,
|
||||
"ArrowDown": KeyArrowDown,
|
||||
"ShiftArrowDown": KeyShiftArrowDown,
|
||||
"ArrowLeft": KeyArrowLeft,
|
||||
"ArrowRight": KeyArrowRight,
|
||||
"CtrlTilde": KeyCtrlTilde,
|
||||
@ -203,28 +205,30 @@ var translate = map[string]Key{
|
||||
|
||||
// Special keys.
|
||||
const (
|
||||
KeyF1 Key = Key(tcell.KeyF1)
|
||||
KeyF2 = Key(tcell.KeyF2)
|
||||
KeyF3 = Key(tcell.KeyF3)
|
||||
KeyF4 = Key(tcell.KeyF4)
|
||||
KeyF5 = Key(tcell.KeyF5)
|
||||
KeyF6 = Key(tcell.KeyF6)
|
||||
KeyF7 = Key(tcell.KeyF7)
|
||||
KeyF8 = Key(tcell.KeyF8)
|
||||
KeyF9 = Key(tcell.KeyF9)
|
||||
KeyF10 = Key(tcell.KeyF10)
|
||||
KeyF11 = Key(tcell.KeyF11)
|
||||
KeyF12 = Key(tcell.KeyF12)
|
||||
KeyInsert = Key(tcell.KeyInsert)
|
||||
KeyDelete = Key(tcell.KeyDelete)
|
||||
KeyHome = Key(tcell.KeyHome)
|
||||
KeyEnd = Key(tcell.KeyEnd)
|
||||
KeyPgdn = Key(tcell.KeyPgDn)
|
||||
KeyPgup = Key(tcell.KeyPgUp)
|
||||
KeyArrowUp = Key(tcell.KeyUp)
|
||||
KeyArrowDown = Key(tcell.KeyDown)
|
||||
KeyArrowLeft = Key(tcell.KeyLeft)
|
||||
KeyArrowRight = Key(tcell.KeyRight)
|
||||
KeyF1 Key = Key(tcell.KeyF1)
|
||||
KeyF2 = Key(tcell.KeyF2)
|
||||
KeyF3 = Key(tcell.KeyF3)
|
||||
KeyF4 = Key(tcell.KeyF4)
|
||||
KeyF5 = Key(tcell.KeyF5)
|
||||
KeyF6 = Key(tcell.KeyF6)
|
||||
KeyF7 = Key(tcell.KeyF7)
|
||||
KeyF8 = Key(tcell.KeyF8)
|
||||
KeyF9 = Key(tcell.KeyF9)
|
||||
KeyF10 = Key(tcell.KeyF10)
|
||||
KeyF11 = Key(tcell.KeyF11)
|
||||
KeyF12 = Key(tcell.KeyF12)
|
||||
KeyInsert = Key(tcell.KeyInsert)
|
||||
KeyDelete = Key(tcell.KeyDelete)
|
||||
KeyHome = Key(tcell.KeyHome)
|
||||
KeyEnd = Key(tcell.KeyEnd)
|
||||
KeyPgdn = Key(tcell.KeyPgDn)
|
||||
KeyPgup = Key(tcell.KeyPgUp)
|
||||
KeyArrowUp = Key(tcell.KeyUp)
|
||||
KeyShiftArrowUp = Key(tcell.KeyF62)
|
||||
KeyArrowDown = Key(tcell.KeyDown)
|
||||
KeyShiftArrowDown = Key(tcell.KeyF63)
|
||||
KeyArrowLeft = Key(tcell.KeyLeft)
|
||||
KeyArrowRight = Key(tcell.KeyRight)
|
||||
)
|
||||
|
||||
// Keys combinations.
|
||||
|
8
vendor/github.com/jesseduffield/gocui/tcell_driver.go
generated
vendored
8
vendor/github.com/jesseduffield/gocui/tcell_driver.go
generated
vendored
@ -300,6 +300,14 @@ func (g *Gui) pollEvent() GocuiEvent {
|
||||
mod = 0
|
||||
ch = rune(0)
|
||||
k = tcell.KeyCtrlSpace
|
||||
} else if mod == tcell.ModShift && k == tcell.KeyUp {
|
||||
mod = 0
|
||||
ch = rune(0)
|
||||
k = tcell.KeyF62
|
||||
} else if mod == tcell.ModShift && k == tcell.KeyDown {
|
||||
mod = 0
|
||||
ch = rune(0)
|
||||
k = tcell.KeyF63
|
||||
} else if mod == tcell.ModCtrl || mod == tcell.ModShift {
|
||||
// remove Ctrl or Shift if specified
|
||||
// - shift - will be translated to the final code of rune
|
||||
|
150
vendor/github.com/jesseduffield/gocui/view.go
generated
vendored
150
vendor/github.com/jesseduffield/gocui/view.go
generated
vendored
@ -41,6 +41,14 @@ type View struct {
|
||||
wx, wy int // Write() offsets
|
||||
lines [][]cell // All the data
|
||||
outMode OutputMode
|
||||
// The y position of the first line of a range selection.
|
||||
// This is not relative to the view's origin: it is relative to the first line
|
||||
// of the view's content, so you can scroll the view and this value will remain
|
||||
// the same, unlike the view's cy value.
|
||||
// A value of -1 means that there is no range selection.
|
||||
// This value can be greater than the selected line index, in the event that
|
||||
// a user starts a range select and then moves the cursor up.
|
||||
rangeSelectStartY int
|
||||
|
||||
// readBuffer is used for storing unread bytes
|
||||
readBuffer []byte
|
||||
@ -284,6 +292,14 @@ func (v *View) FocusPoint(cx int, cy int) {
|
||||
v.cy = cy - v.oy
|
||||
}
|
||||
|
||||
func (v *View) SetRangeSelectStart(rangeSelectStartY int) {
|
||||
v.rangeSelectStartY = rangeSelectStartY
|
||||
}
|
||||
|
||||
func (v *View) CancelRangeSelect() {
|
||||
v.rangeSelectStartY = -1
|
||||
}
|
||||
|
||||
func calculateNewOrigin(selectedLine int, oldOrigin int, lineCount int, viewHeight int) int {
|
||||
if viewHeight > lineCount {
|
||||
return 0
|
||||
@ -349,19 +365,20 @@ func (l lineType) String() string {
|
||||
// newView returns a new View object.
|
||||
func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
|
||||
v := &View{
|
||||
name: name,
|
||||
x0: x0,
|
||||
y0: y0,
|
||||
x1: x1,
|
||||
y1: y1,
|
||||
Visible: true,
|
||||
Frame: true,
|
||||
Editor: DefaultEditor,
|
||||
tainted: true,
|
||||
outMode: mode,
|
||||
ei: newEscapeInterpreter(mode),
|
||||
searcher: &searcher{},
|
||||
TextArea: &TextArea{},
|
||||
name: name,
|
||||
x0: x0,
|
||||
y0: y0,
|
||||
x1: x1,
|
||||
y1: y1,
|
||||
Visible: true,
|
||||
Frame: true,
|
||||
Editor: DefaultEditor,
|
||||
tainted: true,
|
||||
outMode: mode,
|
||||
ei: newEscapeInterpreter(mode),
|
||||
searcher: &searcher{},
|
||||
TextArea: &TextArea{},
|
||||
rangeSelectStartY: -1,
|
||||
}
|
||||
|
||||
v.FgColor, v.BgColor = ColorDefault, ColorDefault
|
||||
@ -428,11 +445,17 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
|
||||
if x < 0 || x >= maxX || y < 0 || y >= maxY {
|
||||
return ErrInvalidPoint
|
||||
}
|
||||
var (
|
||||
ry, rcy int
|
||||
err error
|
||||
)
|
||||
if v.Highlight {
|
||||
|
||||
if v.Mask != 0 {
|
||||
fgColor = v.FgColor
|
||||
bgColor = v.BgColor
|
||||
ch = v.Mask
|
||||
} else if v.Highlight {
|
||||
var (
|
||||
ry, rcy int
|
||||
err error
|
||||
)
|
||||
|
||||
_, ry, err = v.realPosition(x, y)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -442,20 +465,28 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
|
||||
if err == nil {
|
||||
rcy = rrcy
|
||||
}
|
||||
}
|
||||
|
||||
if v.Mask != 0 {
|
||||
fgColor = v.FgColor
|
||||
bgColor = v.BgColor
|
||||
ch = v.Mask
|
||||
} else if v.Highlight && ry == rcy {
|
||||
// this ensures we use the bright variant of a colour upon highlight
|
||||
fgColorComponent := fgColor & ^AttrAll
|
||||
if fgColorComponent >= AttrIsValidColor && fgColorComponent < AttrIsValidColor+8 {
|
||||
fgColor += 8
|
||||
rangeSelectStart := rcy
|
||||
rangeSelectEnd := rcy
|
||||
if v.rangeSelectStartY != -1 {
|
||||
_, realRangeSelectStart, err := v.realPosition(0, v.rangeSelectStartY-v.oy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rangeSelectStart = min(realRangeSelectStart, rcy)
|
||||
rangeSelectEnd = max(realRangeSelectStart, rcy)
|
||||
}
|
||||
|
||||
if ry >= rangeSelectStart && ry <= rangeSelectEnd {
|
||||
// this ensures we use the bright variant of a colour upon highlight
|
||||
fgColorComponent := fgColor & ^AttrAll
|
||||
if fgColorComponent >= AttrIsValidColor && fgColorComponent < AttrIsValidColor+8 {
|
||||
fgColor += 8
|
||||
}
|
||||
fgColor = fgColor | AttrBold
|
||||
bgColor = bgColor | v.SelBgColor
|
||||
}
|
||||
fgColor = fgColor | AttrBold
|
||||
bgColor = bgColor | v.SelBgColor
|
||||
}
|
||||
|
||||
// Don't display NUL characters
|
||||
@ -468,6 +499,20 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// SetCursor sets the cursor position of the view at the given point,
|
||||
// relative to the view. It checks if the position is valid.
|
||||
func (v *View) SetCursor(x, y int) error {
|
||||
@ -1388,7 +1433,31 @@ func (v *View) SelectedLine() string {
|
||||
if len(v.lines) == 0 {
|
||||
return ""
|
||||
}
|
||||
line := v.lines[v.SelectedLineIdx()]
|
||||
|
||||
return v.lineContentAtIdx(v.SelectedLineIdx())
|
||||
}
|
||||
|
||||
// expected to only be used in tests
|
||||
func (v *View) SelectedLines() []string {
|
||||
v.writeMutex.Lock()
|
||||
defer v.writeMutex.Unlock()
|
||||
|
||||
if len(v.lines) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
startIdx, endIdx := v.SelectedLineRange()
|
||||
|
||||
lines := make([]string, 0, endIdx-startIdx+1)
|
||||
for i := startIdx; i <= endIdx; i++ {
|
||||
lines = append(lines, v.lineContentAtIdx(i))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (v *View) lineContentAtIdx(idx int) string {
|
||||
line := v.lines[idx]
|
||||
str := lineType(line).String()
|
||||
return strings.Replace(str, "\x00", "", -1)
|
||||
}
|
||||
@ -1399,6 +1468,25 @@ func (v *View) SelectedPoint() (int, int) {
|
||||
return cx + ox, cy + oy
|
||||
}
|
||||
|
||||
func (v *View) SelectedLineRange() (int, int) {
|
||||
_, cy := v.Cursor()
|
||||
_, oy := v.Origin()
|
||||
|
||||
start := cy + oy
|
||||
|
||||
if v.rangeSelectStartY == -1 {
|
||||
return start, start
|
||||
}
|
||||
|
||||
end := v.rangeSelectStartY
|
||||
|
||||
if start > end {
|
||||
return end, start
|
||||
} else {
|
||||
return start, end
|
||||
}
|
||||
}
|
||||
|
||||
func (v *View) RenderTextArea() {
|
||||
v.Clear()
|
||||
fmt.Fprint(v, v.TextArea.GetContent())
|
||||
|
Reference in New Issue
Block a user