diff --git a/.travis.yml b/.travis.yml index eaa21a7..64fe1cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,7 +34,7 @@ env: global: - secure: "pdRpTOGQSUgbC9tK37voxUYJHMWDPJEmdMhNBsljpP9VnxxbR6JEFwvOQEmUHGlsYv8jma6a17jE60ngVQk8QP12cPh48i2bdbVgym/zTUOKFawCtPAzs8i7evh0di5eZ3uoyc42kG4skc+ePuVHbXC8jDxwaPpMqSHD7QyQc1/6ckI9LLkyWUqhnJJXkVwhmI74Aa1Im6QhywAWFMeTBRRL02cwr6k7VKSYOn6yrtzJRCALFGpZ/n58lPrpDxN7W8o+HRQP89wIDy8FyNeEPdmqGFNfMHDvI3oJRN4dGC4H9EkKf/iGuNJia1Bs+MgaG9kKlMHsI6Fkh5uw9KNTvC1llx43VRQJzm26cn1CpRxxRtF4F8lqkpY4tHjxxCitV+98ddW8jdmQYyx+LeueC5wqlO9g2M5L3oXsGMqZ++mDRDa8oQoQAVUSVtimeO8ODXFuVNR8TlupP0Cthgucil63VUZfAD8EHc2zpRSFxfYByDH53uMEinn20uovL6W42fqgboC43HOnR6aVfSANPsBFDlcpZFa2BY5RkcKyYdaLkucy0DKJ946UDfhOu6FNm0GPHq5HcgWkLojNF0dEFgG6J+SGQGiPjxTlHP/zoe61qMlWu+fYRXQnKWZN5Kk0T1TbAk6pKSE6wRLG8ddxvMg+eVpGLT+gAvQdrrkMFvs=" before_deploy: - - export CHANGELOG=$(git log $(git describe --abbrev=0 --tags "${TRAVIS_TAG}^")..$TRAVIS_TAG --pretty=format:'
  • view commit • %s
  • ' --reverse | while read line; do echo -n "$line "; done) + - export CHANGELOG=$(git log $(git describe --abbrev=0 --tags "${TRAVIS_TAG}^")..$TRAVIS_TAG --pretty=format:'
  • view commit • %s
  • %n ' --reverse | while read line; do echo -n "$line "; done) deploy: - provider: releases skip_cleanup: true diff --git a/buffer/buffer.go b/buffer/buffer.go index 0e7ae9a..9cc52d8 100644 --- a/buffer/buffer.go +++ b/buffer/buffer.go @@ -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)) } diff --git a/buffer/buffer_test.go b/buffer/buffer_test.go index ad866ec..43064bf 100644 --- a/buffer/buffer_test.go +++ b/buffer/buffer_test.go @@ -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) diff --git a/ci/push-nightly-tag.sh b/ci/push-nightly-tag.sh index 51ad09b..334e294 100755 --- a/ci/push-nightly-tag.sh +++ b/ci/push-nightly-tag.sh @@ -8,7 +8,7 @@ if [ -z "$MY_TAG" ] ; then git config --global user.email "travis@travis-ci.org" git config --global user.name "Travis CI" - NEW_TAG="Nightly-$(date +%Y-%m-%d)-$(git rev-parse --short HEAD)" + NEW_TAG="Nightly-$TRAVIS_BRANCH-$(date +%Y-%m-%d)-$(git rev-parse --short HEAD)" git tag -a $NEW_TAG -m "Nightly Build Tag $NEW_TAG" echo "New generated nightly build tag: $NEW_TAG" diff --git a/gui/actions.go b/gui/actions.go index 64c030f..4134f7c 100644 --- a/gui/actions.go +++ b/gui/actions.go @@ -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))) } diff --git a/gui/gui.go b/gui/gui.go index a35b4b5..0a1e94e 100644 --- a/gui/gui.go +++ b/gui/gui.go @@ -75,6 +75,7 @@ type GUI struct { prevMouseEventHandler mouseEventsHandler internalResize bool + selectionRegionMode buffer.SelectionRegionMode vScrollbar *scrollbar } @@ -546,7 +547,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 diff --git a/gui/input.go b/gui/input.go index bc016c2..c5caada 100644 --- a/gui/input.go +++ b/gui/input.go @@ -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 { diff --git a/gui/mouse.go b/gui/mouse.go index ddf9533..19703e9 100644 --- a/gui/mouse.go +++ b/gui/mouse.go @@ -219,7 +219,7 @@ func (gui *GUI) mouseButtonCallback(g *GUI, button glfw.MouseButton, action glfw 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 @@ -334,9 +334,10 @@ func (gui *GUI) cursorEnterCallback(g *GUI, entered bool) { // empty, just to conform to the mouseEventsHandler interface } -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) @@ -361,7 +362,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 diff --git a/main_test.go b/main_test.go index 33d564b..3a8479e 100644 --- a/main_test.go +++ b/main_test.go @@ -146,14 +146,7 @@ func TestScreenFeatures(t *testing.T) { validateScreen("test-screen-features-8.png") validateScreen("test-screen-features-9.png") validateScreen("test-screen-features-10.png") - - // 11th screen test is not passing https://github.com/liamg/aminal/issues/207 - //g.Screenshot("vttest/test-screen-features-11.png") - //compareImages("vttest/test-screen-features-11.png", "vttest/test-screen-features-11.png") - - enter(term) - sleep() - + validateScreen("test-screen-features-11.png") validateScreen("test-screen-features-12.png") validateScreen("test-screen-features-13.png") validateScreen("test-screen-features-14.png") diff --git a/platform/winpty.go b/platform/winpty.go index d330745..b792f85 100644 --- a/platform/winpty.go +++ b/platform/winpty.go @@ -7,6 +7,7 @@ import ( "syscall" "time" + "fmt" "github.com/MaxRis/w32" ) @@ -135,15 +136,20 @@ func (pty *winConPty) Close() error { func (pty *winConPty) CreateGuestProcess(imagePath string) (Process, error) { process, err := createPtyChildProcess(imagePath, pty.hcon) + if err != nil { + return nil, err + } - if err == nil { - setupChildConsole(C.DWORD(process.processID), C.STD_OUTPUT_HANDLE, C.ENABLE_PROCESSED_OUTPUT|C.ENABLE_WRAP_AT_EOL_OUTPUT) + err = setupChildConsole(C.DWORD(process.processID), C.STD_OUTPUT_HANDLE, C.ENABLE_PROCESSED_OUTPUT|C.ENABLE_WRAP_AT_EOL_OUTPUT) + if err != nil { + process.Close() + return nil, err } return process, err } -func setupChildConsole(processID C.DWORD, nStdHandle C.DWORD, mode uint) bool { +func setupChildConsole(processID C.DWORD, nStdHandle C.DWORD, mode uint) error { C.FreeConsole() defer C.AttachConsole(^C.DWORD(0)) // attach to parent process console @@ -158,7 +164,7 @@ func setupChildConsole(processID C.DWORD, nStdHandle C.DWORD, mode uint) bool { } lastError := C.GetLastError() if lastError != C.ERROR_GEN_FAILURE || count <= 0 { - return false + return fmt.Errorf("Was not able to attach to the child prosess' console") } time.Sleep(time.Millisecond * time.Duration(waitStepMilliSeconds)) @@ -169,7 +175,7 @@ func setupChildConsole(processID C.DWORD, nStdHandle C.DWORD, mode uint) bool { C.SetConsoleMode(h, C.DWORD(mode)) C.FreeConsole() - return true + return nil } func (pty *winConPty) Resize(x, y int) error { diff --git a/vttest/test-screen-features-11.png b/vttest/test-screen-features-11.png new file mode 100644 index 0000000..a1d7055 Binary files /dev/null and b/vttest/test-screen-features-11.png differ