Skip to content

Commit

Permalink
Add "select with mouse to copy" / "right click to paste" functionality (
Browse files Browse the repository at this point in the history
  • Loading branch information
vadymeng0 committed Jan 29, 2019
1 parent 23cf1e8 commit 97fe736
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 50 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -113,6 +113,7 @@ slomo = false # Enable slow motion output mode, useful for debuggi
shell = "/bin/bash" # The shell to run for the terminal session. Defaults to the users shell.
search_url = "https://www.google.com/search?q=$QUERY" # The search engine to use for the "search selected text" action. Defaults to google. Set this to your own search url using $QUERY as the keywords to replace when searching.
max_lines = 1000 # Maximum number of lines in the terminal buffer.
copy_and_paste_with_mouse = true # Text selected with the mouse is copied to the clipboard on end selection, and is pasted on right mouse button click.

[colours]
cursor = "#e8dfd6"
Expand Down
61 changes: 28 additions & 33 deletions buffer/buffer.go
Expand Up @@ -6,7 +6,6 @@ import (
"io/ioutil"
"net/url"
"os"
"time"
)

type Buffer struct {
Expand All @@ -18,8 +17,6 @@ type Buffer struct {
selectionStart *Position
selectionEnd *Position
selectionComplete bool // whether the selected text can update or whether it is final
selectionExpanded bool // whether the selection to word expansion has already run on this point
selectionClickTime time.Time
terminalState *TerminalState
}

Expand Down Expand Up @@ -48,7 +45,7 @@ func (buffer *Buffer) GetURLAtPosition(col uint16, viewRow uint16) string {

candidate := ""

for i := col; i >= 0; i-- {
for i := col; i >= uint16(0); i-- {
cell := buffer.GetRawCell(i, row)
if cell == nil {
break
Expand Down Expand Up @@ -82,6 +79,26 @@ func (buffer *Buffer) GetURLAtPosition(col uint16, viewRow uint16) string {
return candidate
}

func (buffer *Buffer) IsSelectionComplete() bool {
return buffer.selectionComplete
}

func (buffer *Buffer) SelectLineAtPosition(col uint16, viewRow uint16) {
row := buffer.convertViewLineToRawLine(viewRow) - uint64(buffer.terminalState.scrollLinesFromBottom)

buffer.selectionStart = &Position {
Col: 0,
Line: int(row),
}
buffer.selectionEnd = &Position {
Col: int(buffer.ViewWidth() - 1),
Line: int(row),
}

buffer.selectionComplete = true
buffer.emitDisplayChange()
}

func (buffer *Buffer) SelectWordAtPosition(col uint16, viewRow uint16) {

row := buffer.convertViewLineToRawLine(viewRow) - uint64(buffer.terminalState.scrollLinesFromBottom)
Expand All @@ -94,7 +111,7 @@ func (buffer *Buffer) SelectWordAtPosition(col uint16, viewRow uint16) {
start := col
end := col

for i := col; i >= 0; i-- {
for i := col; i >= uint16(0); i-- {
cell := buffer.GetRawCell(i, row)
if cell == nil {
break
Expand Down Expand Up @@ -124,8 +141,9 @@ func (buffer *Buffer) SelectWordAtPosition(col uint16, viewRow uint16) {
Col: int(end),
Line: int(row),
}
buffer.emitDisplayChange()

buffer.selectionComplete = true
buffer.emitDisplayChange()
}

// bounds for word selection
Expand Down Expand Up @@ -202,37 +220,14 @@ func (buffer *Buffer) GetSelectedText() string {

func (buffer *Buffer) StartSelection(col uint16, viewRow uint16) {
row := buffer.convertViewLineToRawLine(viewRow) - uint64(buffer.terminalState.scrollLinesFromBottom)
if buffer.selectionComplete {
buffer.selectionEnd = nil

if buffer.selectionStart != nil && time.Since(buffer.selectionClickTime) < time.Millisecond*500 {
if buffer.selectionExpanded {
//select whole line!
buffer.selectionStart = &Position{
Col: 0,
Line: int(row),
}
buffer.selectionEnd = &Position{
Col: int(buffer.ViewWidth() - 1),
Line: int(row),
}
buffer.emitDisplayChange()
} else {
buffer.SelectWordAtPosition(col, viewRow)
buffer.selectionExpanded = true
}
return
}

buffer.selectionExpanded = false
}

buffer.selectionComplete = false
buffer.selectionStart = &Position{

buffer.selectionStart = &Position {
Col: int(col),
Line: int(row),
}
buffer.selectionClickTime = time.Now()

buffer.selectionEnd = nil
}

func (buffer *Buffer) EndSelection(col uint16, viewRow uint16, complete bool) {
Expand Down
1 change: 1 addition & 0 deletions config/config.go
Expand Up @@ -14,6 +14,7 @@ type Config struct {
KeyMapping KeyMappingConfig `toml:"keys"`
SearchURL string `toml:"search_url"`
MaxLines uint64 `toml:"max_lines"`
CopyAndPasteWithMouse bool `toml:"copy_and_paste_with_mouse"`
}

type KeyMappingConfig map[string]string
Expand Down
1 change: 1 addition & 0 deletions config/defaults.go
Expand Up @@ -29,6 +29,7 @@ var DefaultConfig = Config{
KeyMapping: KeyMappingConfig(map[string]string{}),
SearchURL: "https://www.google.com/search?q=$QUERY",
MaxLines: 1000,
CopyAndPasteWithMouse: true,
}

func init() {
Expand Down
5 changes: 5 additions & 0 deletions gui/gui.go
Expand Up @@ -48,6 +48,11 @@ type GUI struct {
resizeLock *sync.Mutex
handCursor *glfw.Cursor
arrowCursor *glfw.Cursor

prevLeftClickX uint16
prevLeftClickY uint16
leftClickTime time.Time
leftClickCount int // number of clicks in a serie - single click, double click, or triple click
}

func Min(x, y int) int {
Expand Down
89 changes: 72 additions & 17 deletions gui/mouse.go
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/go-gl/glfw/v3.2/glfw"
"github.com/liamg/aminal/terminal"
"time"
)

func (gui *GUI) glfwScrollCallback(w *glfw.Window, xoff float64, yoff float64) {
Expand Down Expand Up @@ -35,12 +36,7 @@ func (gui *GUI) getArrowCursor() *glfw.Cursor {

func (gui *GUI) mouseMoveCallback(w *glfw.Window, px float64, py float64) {

scale := gui.scale()
px = px / float64(scale)
py = py / float64(scale)

x := uint16(math.Floor((px - float64(gui.renderer.areaX)) / float64(gui.renderer.CellWidth())))
y := uint16(math.Floor((py - float64(gui.renderer.areaY)) / float64(gui.renderer.CellHeight())))
x, y := gui.convertMouseCoordinates(px, py)

if gui.mouseDown {
gui.terminal.ActiveBuffer().EndSelection(x, y, false)
Expand All @@ -62,6 +58,35 @@ func (gui *GUI) mouseMoveCallback(w *glfw.Window, px float64, py float64) {
}
}

func (gui *GUI) convertMouseCoordinates(px float64, py float64) (uint16, uint16) {
scale := gui.scale()
px = px / float64(scale)
py = py / float64(scale)
x := uint16(math.Floor((px - float64(gui.renderer.areaX)) / float64(gui.renderer.CellWidth())))
y := uint16(math.Floor((py - float64(gui.renderer.areaY)) / float64(gui.renderer.CellHeight())))

return x, y
}

func (gui *GUI) updateLeftClickCount(x uint16, y uint16) int {
defer func() {
gui.leftClickTime = time.Now()
gui.prevLeftClickX = x
gui.prevLeftClickY = y
}()

if gui.prevLeftClickX == x && gui.prevLeftClickY == y && time.Since(gui.leftClickTime) < time.Millisecond * 500 {
gui.leftClickCount++
if gui.leftClickCount > 3 {
gui.leftClickCount = 3
}
} else {
gui.leftClickCount = 1
}

return gui.leftClickCount
}

func (gui *GUI) mouseButtonCallback(w *glfw.Window, button glfw.MouseButton, action glfw.Action, mod glfw.ModifierKey) {

if gui.overlay != nil {
Expand All @@ -72,28 +97,58 @@ func (gui *GUI) mouseButtonCallback(w *glfw.Window, button glfw.MouseButton, act
}

// before we forward clicks on (below), we need to handle them locally for url clicking, text highlighting etc.
px, py := w.GetCursorPos()
scale := gui.scale()
px = px / float64(scale)
py = py / float64(scale)
x := uint16(math.Floor((px - float64(gui.renderer.areaX)) / float64(gui.renderer.CellWidth())))
y := uint16(math.Floor((py - float64(gui.renderer.areaY)) / float64(gui.renderer.CellHeight())))
x, y := gui.convertMouseCoordinates(w.GetCursorPos())
tx := int(x) + 1 // vt100 is 1 indexed
ty := int(y) + 1

if button == glfw.MouseButtonLeft {
activeBuffer := gui.terminal.ActiveBuffer()

switch button {
case glfw.MouseButtonLeft:
if action == glfw.Press {
gui.mouseDown = true
gui.terminal.ActiveBuffer().StartSelection(x, y)

clickCount := gui.updateLeftClickCount(x, y)
if clickCount == 1 || !activeBuffer.IsSelectionComplete() {
activeBuffer.StartSelection(x, y)
} else {
switch clickCount {
case 2:
activeBuffer.SelectWordAtPosition(x, y)
case 3:
activeBuffer.SelectLineAtPosition(x, y)
}
}
} else if action == glfw.Release {
gui.mouseDown = false
gui.terminal.ActiveBuffer().EndSelection(x, y, true)
if url := gui.terminal.ActiveBuffer().GetURLAtPosition(x, y); url != "" {
go gui.launchTarget(url)
activeBuffer.EndSelection(x, y, true)

handled := false
if gui.config.CopyAndPasteWithMouse {
selectedText := activeBuffer.GetSelectedText()
if selectedText != "" {
gui.window.SetClipboardString(selectedText)
handled = true
}
}

if !handled {
if url := activeBuffer.GetURLAtPosition(x, y); url != "" {
go gui.launchTarget(url)
}
}
}

case glfw.MouseButtonRight:
if gui.config.CopyAndPasteWithMouse && action == glfw.Press {
str, err := gui.window.GetClipboardString()
if err == nil {
activeBuffer.ClearSelection()
_ = gui.terminal.Paste([]byte(str))
}
}
}

// https://www.xfree86.org/4.8.0/ctlseqs.html

/*
Expand Down

0 comments on commit 97fe736

Please sign in to comment.