From 87d36072a32bb27f25b9a7ac1155324f73c54ec9 Mon Sep 17 00:00:00 2001 From: Max Risuhin Date: Mon, 4 Mar 2019 16:56:55 +0200 Subject: [PATCH 1/5] Include source branch title in nightly build tag (#243) --- ci/push-nightly-tag.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From 84cc84f1031e659fcc82a9d43dfb55717c00edaa Mon Sep 17 00:00:00 2001 From: Max Risuhin Date: Mon, 4 Mar 2019 19:08:38 +0200 Subject: [PATCH 2/5] [travis] Insert new line after each git log commit message to be correctly parsed by bash read command (#244) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2c621ec..c30aaba 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 From 4ec83c54a43c84438e48242e652a4d1fe358621e Mon Sep 17 00:00:00 2001 From: rrrooommmaaa Date: Tue, 5 Mar 2019 09:55:29 +0300 Subject: [PATCH 3/5] #217 rectangular select and copy (#241) * #217 rectangular select and copy --- buffer/buffer.go | 62 ++++++++++++++++++++++++++++++++++++++----- buffer/buffer_test.go | 12 ++++----- gui/actions.go | 4 +-- gui/gui.go | 3 ++- gui/input.go | 13 ++++++++- gui/mouse.go | 7 ++--- 6 files changed, 82 insertions(+), 19 deletions(-) diff --git a/buffer/buffer.go b/buffer/buffer.go index c721e3e..cb4544f 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/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 2d968c6..bbeecdf 100644 --- a/gui/gui.go +++ b/gui/gui.go @@ -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 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 3c8962d..14c71c5 100644 --- a/gui/mouse.go +++ b/gui/mouse.go @@ -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 From a68b693bfded779fd3a86727952594906c9bdf96 Mon Sep 17 00:00:00 2001 From: nikitar020 <42252263+nikitar020@users.noreply.github.com> Date: Tue, 5 Mar 2019 09:00:34 +0200 Subject: [PATCH 4/5] Add correct handling of attaching to child console errors (#239) --- platform/winpty.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) 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 { From ae7c84b32217e083c85130be292ba82e8e2a8b38 Mon Sep 17 00:00:00 2001 From: Max Risuhin Date: Wed, 6 Mar 2019 00:02:49 +0200 Subject: [PATCH 5/5] Add screenshot to validate 11th subtest of vttest "Screen feature" test suite (#232) --- main_test.go | 9 +-------- vttest/test-screen-features-11.png | Bin 0 -> 6006 bytes 2 files changed, 1 insertion(+), 8 deletions(-) create mode 100644 vttest/test-screen-features-11.png 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/vttest/test-screen-features-11.png b/vttest/test-screen-features-11.png new file mode 100644 index 0000000000000000000000000000000000000000..a1d705581432917f3f32bb77263f11b7571476f7 GIT binary patch literal 6006 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 MAmmufkH1{|FJUa$IRF3v literal 0 HcmV?d00001