#217 rectangular select and copy (#241)

* #217 rectangular select and copy
This commit is contained in:
rrrooommmaaa 2019-03-05 09:55:29 +03:00 committed by GitHub
parent 84cc84f103
commit 4ec83c54a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 82 additions and 19 deletions

View File

@ -10,11 +10,15 @@ import (
)
type SelectionMode int
type SelectionRegionMode int
const (
SelectionChar SelectionMode = iota // char-by-char selection
SelectionWord SelectionMode = iota // by word selection
SelectionLine SelectionMode = iota // whole line selection
SelectionRegionNormal SelectionRegionMode = iota
SelectionRegionRectangular
)
type Buffer struct {
@ -160,12 +164,38 @@ func isRuneURLSelectionMarker(r rune) bool {
return false
}
func (buffer *Buffer) GetSelectedText() string {
start, end := buffer.getActualSelection()
func (buffer *Buffer) getRectangleText(start *Position, end *Position) string {
var builder strings.Builder
builder.Grow((end.Col - start.Col + 2) * (end.Line - start.Line + 1)) // reserve space to minimize allocations
for row := start.Line; row <= end.Line; row++ {
for col := start.Col; col <= end.Col; col++ {
if row < len(buffer.lines) && col < len(buffer.lines[row].cells) {
r := buffer.lines[row].cells[col].Rune()
if r == 0x00 {
r = ' '
}
builder.WriteRune(r)
} else {
builder.WriteRune(' ')
}
}
builder.WriteString("\n")
}
return builder.String()
}
func (buffer *Buffer) GetSelectedText(selectionRegionMode SelectionRegionMode) string {
start, end := buffer.getActualSelection(selectionRegionMode)
if start == nil || end == nil {
return ""
}
if selectionRegionMode == SelectionRegionRectangular {
return buffer.getRectangleText(start, end)
}
var builder strings.Builder
builder.Grow(int(buffer.terminalState.viewWidth) * (end.Line - start.Line + 1)) // reserve space to minimize allocations
@ -258,7 +288,7 @@ func (buffer *Buffer) ClearSelection() {
buffer.emitDisplayChange()
}
func (buffer *Buffer) getActualSelection() (*Position, *Position) {
func (buffer *Buffer) getActualSelection(selectionRegionMode SelectionRegionMode) (*Position, *Position) {
if buffer.selectionStart == nil || buffer.selectionEnd == nil {
return nil, nil
}
@ -266,7 +296,23 @@ func (buffer *Buffer) getActualSelection() (*Position, *Position) {
start := &Position{}
end := &Position{}
if comparePositions(buffer.selectionStart, buffer.selectionEnd) >= 0 {
if selectionRegionMode == SelectionRegionRectangular {
if buffer.selectionStart.Col > buffer.selectionEnd.Col {
start.Col = buffer.selectionEnd.Col
end.Col = buffer.selectionStart.Col
} else {
start.Col = buffer.selectionStart.Col
end.Col = buffer.selectionEnd.Col
}
if buffer.selectionStart.Line > buffer.selectionEnd.Line {
start.Line = buffer.selectionEnd.Line
end.Line = buffer.selectionStart.Line
} else {
start.Line = buffer.selectionStart.Line
end.Line = buffer.selectionEnd.Line
}
return start, end
} else if comparePositions(buffer.selectionStart, buffer.selectionEnd) >= 0 {
start.Col = buffer.selectionStart.Col
start.Line = buffer.selectionStart.Line
@ -306,14 +352,18 @@ func (buffer *Buffer) getActualSelection() (*Position, *Position) {
return start, end
}
func (buffer *Buffer) InSelection(col uint16, row uint16) bool {
start, end := buffer.getActualSelection()
func (buffer *Buffer) InSelection(col uint16, row uint16, selectionRegionMode SelectionRegionMode) bool {
start, end := buffer.getActualSelection(selectionRegionMode)
if start == nil || end == nil {
return false
}
rawY := int(buffer.convertViewLineToRawLine(row) - uint64(buffer.terminalState.scrollLinesFromBottom))
if selectionRegionMode == SelectionRegionRectangular {
return rawY >= start.Line && rawY <= end.Line && int(col) >= start.Col && int(col) <= end.Col
}
return (rawY > start.Line || (rawY == start.Line && int(col) >= start.Col)) &&
(rawY < end.Line || (rawY == end.Line && int(col) <= end.Col))
}

View File

@ -659,7 +659,7 @@ func TestSelectingChars(t *testing.T) {
b.StartSelection(2, 0, SelectionChar)
b.ExtendSelection(4, 1, true)
assert.Equal(t, "e quick brown\nfox j", b.GetSelectedText())
assert.Equal(t, "e quick brown\nfox j", b.GetSelectedText(SelectionRegionNormal))
}
func TestSelectingWordsDown(t *testing.T) {
@ -668,7 +668,7 @@ func TestSelectingWordsDown(t *testing.T) {
b.StartSelection(6, 1, SelectionWord)
b.ExtendSelection(5, 2, true)
assert.Equal(t, "jumps over\nthe lazy", b.GetSelectedText())
assert.Equal(t, "jumps over\nthe lazy", b.GetSelectedText(SelectionRegionNormal))
}
func TestSelectingWordsUp(t *testing.T) {
@ -677,7 +677,7 @@ func TestSelectingWordsUp(t *testing.T) {
b.StartSelection(5, 2, SelectionWord)
b.ExtendSelection(6, 1, true)
assert.Equal(t, "jumps over\nthe lazy", b.GetSelectedText())
assert.Equal(t, "jumps over\nthe lazy", b.GetSelectedText(SelectionRegionNormal))
}
func TestSelectingLinesDown(t *testing.T) {
@ -686,7 +686,7 @@ func TestSelectingLinesDown(t *testing.T) {
b.StartSelection(6, 1, SelectionLine)
b.ExtendSelection(4, 2, true)
assert.Equal(t, "fox jumps over\nthe lazy dog", b.GetSelectedText())
assert.Equal(t, "fox jumps over\nthe lazy dog", b.GetSelectedText(SelectionRegionNormal))
}
func TestSelectingLineUp(t *testing.T) {
@ -695,7 +695,7 @@ func TestSelectingLineUp(t *testing.T) {
b.StartSelection(8, 2, SelectionLine)
b.ExtendSelection(3, 1, true)
assert.Equal(t, "fox jumps over\nthe lazy dog", b.GetSelectedText())
assert.Equal(t, "fox jumps over\nthe lazy dog", b.GetSelectedText(SelectionRegionNormal))
}
func TestSelectingAfterText(t *testing.T) {
@ -704,7 +704,7 @@ func TestSelectingAfterText(t *testing.T) {
b.StartSelection(6, 3, SelectionChar)
b.ExtendSelection(6, 3, true)
start, end := b.getActualSelection()
start, end := b.getActualSelection(SelectionRegionNormal)
assert.Equal(t, start.Col, 0)
assert.Equal(t, start.Line, 3)

View File

@ -18,7 +18,7 @@ var actionMap = map[config.UserAction]func(gui *GUI){
}
func actionCopy(gui *GUI) {
selectedText := gui.terminal.ActiveBuffer().GetSelectedText()
selectedText := gui.terminal.ActiveBuffer().GetSelectedText(gui.selectionRegionMode)
if selectedText != "" {
gui.window.SetClipboardString(selectedText)
@ -37,7 +37,7 @@ func actionToggleDebug(gui *GUI) {
}
func actionSearchSelection(gui *GUI) {
keywords := gui.terminal.ActiveBuffer().GetSelectedText()
keywords := gui.terminal.ActiveBuffer().GetSelectedText(gui.selectionRegionMode)
if keywords != "" && gui.config.SearchURL != "" && strings.Contains(gui.config.SearchURL, "$QUERY") {
gui.launchTarget(fmt.Sprintf(strings.Replace(gui.config.SearchURL, "$QUERY", "%s", 1), url.QueryEscape(keywords)))
}

View File

@ -58,6 +58,7 @@ type GUI struct {
leftClickCount int // number of clicks in a serie - single click, double click, or triple click
mouseMovedAfterSelectionStarted bool
internalResize bool
selectionRegionMode buffer.SelectionRegionMode
}
func Min(x, y int) int {
@ -488,7 +489,7 @@ func (gui *GUI) redraw() {
cursor = cx == uint(x) && cy == uint(y)
}
if gui.terminal.ActiveBuffer().InSelection(uint16(x), uint16(y)) {
if gui.terminal.ActiveBuffer().InSelection(uint16(x), uint16(y), gui.selectionRegionMode) {
colour = &gui.config.ColourScheme.Selection
} else {
colour = nil

View File

@ -5,6 +5,7 @@ import (
"strings"
"github.com/go-gl/glfw/v3.2/glfw"
"github.com/liamg/aminal/buffer"
)
// send typed runes straight through to the pty
@ -44,8 +45,18 @@ func getModStr(mods glfw.ModifierKey) string {
return ""
}
func (gui *GUI) key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
func (gui *GUI) updateSelectionMode(mods glfw.ModifierKey) {
mode := buffer.SelectionRegionNormal
if modsPressed(mods, glfw.ModAlt) {
mode = buffer.SelectionRegionRectangular
}
if gui.selectionRegionMode != mode {
gui.selectionRegionMode = mode
gui.terminal.SetDirty()
}
}
func (gui *GUI) key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
if action == glfw.Repeat || action == glfw.Press {
if gui.overlay != nil {

View File

@ -143,7 +143,7 @@ func (gui *GUI) mouseButtonCallback(w *glfw.Window, button glfw.MouseButton, act
gui.mouseDown = true
if gui.terminal.GetMouseMode() != terminal.MouseModeButtonEvent {
gui.handleSelectionButtonPress(x, y)
gui.handleSelectionButtonPress(x, y, mod)
}
} else if action == glfw.Release {
gui.mouseDown = false
@ -255,9 +255,10 @@ func (gui *GUI) mouseButtonCallback(w *glfw.Window, button glfw.MouseButton, act
}
func (gui *GUI) handleSelectionButtonPress(x uint16, y uint16) {
func (gui *GUI) handleSelectionButtonPress(x uint16, y uint16, mod glfw.ModifierKey) {
activeBuffer := gui.terminal.ActiveBuffer()
clickCount := gui.updateLeftClickCount(x, y)
gui.updateSelectionMode(mod)
switch clickCount {
case 1:
activeBuffer.StartSelection(x, y, buffer.SelectionChar)
@ -282,7 +283,7 @@ func (gui *GUI) handleSelectionButtonRelease(x uint16, y uint16) {
// Do copy to clipboard *or* open URL, but not both.
handled := false
if gui.config.CopyAndPasteWithMouse {
selectedText := activeBuffer.GetSelectedText()
selectedText := activeBuffer.GetSelectedText(gui.selectionRegionMode)
if selectedText != "" {
gui.window.SetClipboardString(selectedText)
handled = true