Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mouse support for selecting and opening files #963

Merged
merged 3 commits into from
Oct 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ func (app *app) loop() {

app.ui.draw(app.nav)
case ev := <-app.ui.evChan:
e := app.ui.readEvent(ev)
e := app.ui.readEvent(ev, app.nav)
if e == nil {
continue
}
Expand All @@ -444,7 +444,7 @@ func (app *app) loop() {
for {
select {
case ev := <-app.ui.evChan:
e = app.ui.readEvent(ev)
e = app.ui.readEvent(ev, app.nav)
if e == nil {
continue
}
Expand Down
2 changes: 1 addition & 1 deletion colors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func TestApplyAnsiCodes(t *testing.T) {

for _, test := range tests {
if stGot := applyAnsiCodes(test.s, test.st); stGot != test.stExp {
t.Errorf("at input '%s' with '%d' expected '%d' but got '%d'",
t.Errorf("at input '%s' with '%v' expected '%v' but got '%v'",
test.s, test.st, test.stExp, stGot)
}
}
Expand Down
11 changes: 11 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,17 @@ The following additional keybindings are provided by default:
map gh cd ~
map <space> :toggle; down

If the 'mouse' option is enabled, mouse buttons have the following default effects:

Left mouse button
Click on a file or directory to select it. To open a file, click on the preview.

Right mouse button
Enter a directory or open a file.

Scroll wheel
Scroll up or down.

# Configuration

Configuration files should be located at:
Expand Down
12 changes: 12 additions & 0 deletions docstring.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ module github.com/gokcehan/lf
go 1.12

require (
github.com/gdamore/tcell/v2 v2.3.1
github.com/mattn/go-runewidth v0.0.10
golang.org/x/sys v0.0.0-20220209214540-3681064d5158
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d
github.com/gdamore/tcell/v2 v2.5.4-0.20221019011350-d3cbfcfb7aa3
github.com/mattn/go-runewidth v0.0.14
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
gopkg.in/djherbis/times.v1 v1.2.0
)
47 changes: 33 additions & 14 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,20 +1,39 @@
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.3.1 h1:htEXmKMzurgcnNH3VqQA7GYlCpdl9LCSNpzFpZOKhJE=
github.com/gdamore/tcell/v2 v2.3.1/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/gdamore/tcell/v2 v2.5.4-0.20221019011350-d3cbfcfb7aa3 h1:e5DXSPPfOBDJY9Z0geFZjEDok0/cUEI5QlPeTmqPV4I=
github.com/gdamore/tcell/v2 v2.5.4-0.20221019011350-d3cbfcfb7aa3/go.mod h1:XmCynGHvvGG7UFI6az9zzoEHBvZB1PGf5APwOJMFUyE=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/djherbis/times.v1 v1.2.0 h1:UCvDKl1L/fmBygl2Y7hubXCnY7t4Yj46ZrBFNUipFbM=
gopkg.in/djherbis/times.v1 v1.2.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv6mUY0P8=
17 changes: 17 additions & 0 deletions lf.1
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,23 @@ The following additional keybindings are provided by default:
map gh cd ~
map <space> :toggle; down
.EE
.PP
If the 'mouse' option is enabled, mouse buttons have the following default effects:
.PP
.EX
Left mouse button
Click on a file or directory to select it. To open a file, click on the preview.
.EE
.PP
.EX
Right mouse button
Enter a directory or open a file.
.EE
.PP
.EX
Scroll wheel
Scroll up or down.
.EE
.SH CONFIGURATION
Configuration files should be located at:
.PP
Expand Down
2 changes: 2 additions & 0 deletions opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,14 @@ func init() {

gOpts.keys["k"] = &callExpr{"up", nil, 1}
gOpts.keys["<up>"] = &callExpr{"up", nil, 1}
gOpts.keys["<m-up>"] = &callExpr{"up", nil, 1}
gOpts.keys["<c-u>"] = &callExpr{"half-up", nil, 1}
gOpts.keys["<c-b>"] = &callExpr{"page-up", nil, 1}
gOpts.keys["<pgup>"] = &callExpr{"page-up", nil, 1}
gOpts.keys["<c-y>"] = &callExpr{"scroll-up", nil, 1}
gOpts.keys["j"] = &callExpr{"down", nil, 1}
gOpts.keys["<down>"] = &callExpr{"down", nil, 1}
gOpts.keys["<m-down>"] = &callExpr{"down", nil, 1}
gOpts.keys["<c-d>"] = &callExpr{"half-down", nil, 1}
gOpts.keys["<c-f>"] = &callExpr{"page-down", nil, 1}
gOpts.keys["<pgdn>"] = &callExpr{"page-down", nil, 1}
Expand Down
103 changes: 83 additions & 20 deletions ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,16 @@ func newUI(screen tcell.Screen) *ui {
return ui
}

func (ui *ui) winAt(x, y int) (int, *win) {
for i := len(ui.wins) - 1; i >= 0; i-- {
w := ui.wins[i]
if x >= w.x && y >= w.y && y < w.y+w.h {
return i, w
}
}
return -1, nil
}

func (ui *ui) pollEvents() {
var ev tcell.Event
for {
Expand Down Expand Up @@ -874,6 +884,18 @@ func (ui *ui) drawBox() {
}
}

func (ui *ui) dirOfWin(nav *nav, wind int) *dir {
wins := len(ui.wins)
if gOpts.preview {
wins--
}
ind := len(nav.dirs) - wins + wind
if ind < 0 {
return nil
}
return nav.dirs[ind]
}

func (ui *ui) draw(nav *nav) {
st := tcell.StyleDefault
context := dirContext{selections: nav.selections, saves: nav.saves, tags: nav.tags}
Expand All @@ -887,18 +909,15 @@ func (ui *ui) draw(nav *nav) {

ui.drawPromptLine(nav)

length := min(len(ui.wins), len(nav.dirs))
woff := len(ui.wins) - length

wins := len(ui.wins)
if gOpts.preview {
length = min(len(ui.wins)-1, len(nav.dirs))
woff = len(ui.wins) - 1 - length
wins--
}

doff := len(nav.dirs) - length
for i := 0; i < length; i++ {
ui.wins[woff+i].printDir(ui.screen, nav.dirs[doff+i], &context,
&dirStyle{colors: ui.styles, icons: ui.icons, previewing: false})
for i := 0; i < wins; i++ {
if dir := ui.dirOfWin(nav, i); dir != nil {
ui.wins[i].printDir(ui.screen, dir, &context,
&dirStyle{colors: ui.styles, icons: ui.icons, previewing: false})
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No functional change here, just refactored to use dirOfWin.

}

switch ui.cmdPrefix {
Expand Down Expand Up @@ -1062,7 +1081,7 @@ func (ui *ui) pollEvent() tcell.Event {
// This function is used to read a normal event on the client side. For keys,
// digits are interpreted as command counts but this is only done for digits
// preceding any non-digit characters (e.g. "42y2k" as 42 times "y2k").
func (ui *ui) readNormalEvent(ev tcell.Event) expr {
func (ui *ui) readNormalEvent(ev tcell.Event, nav *nav) expr {
draw := &callExpr{"draw", nil, 1}
count := 0

Expand Down Expand Up @@ -1136,6 +1155,10 @@ func (ui *ui) readNormalEvent(ev tcell.Event) expr {
return draw
}
case *tcell.EventMouse:
if ui.cmdPrefix != "" {
return nil
}

var button string

switch tev.Buttons() {
Expand Down Expand Up @@ -1166,22 +1189,62 @@ func (ui *ui) readNormalEvent(ev tcell.Event) expr {
case tcell.ButtonNone:
return nil
}
if expr, ok := gOpts.keys[button]; ok {
return expr
}

expr, ok := gOpts.keys[button]
if !ok {
ui.echoerrf("unknown mapping: %s", button)
return draw
if tev.Buttons() != tcell.Button1 && tev.Buttons() != tcell.Button2 {
return nil
}

x, y := tev.Position()
wind, w := ui.winAt(x, y)
if wind == -1 {
return nil
}

var dir *dir
if gOpts.preview && wind == len(ui.wins)-1 {
curr, err := nav.currFile()
if err != nil {
return nil
} else if !curr.IsDir() || gOpts.dirpreviews {
return &callExpr{"open", nil, 1}
}
dir = ui.dirPrev
} else {
dir = ui.dirOfWin(nav, wind)
if dir == nil {
return nil
}
}

return expr
var file *file
ind := dir.ind - dir.pos + y - w.y
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw max(dir.ind-dir.pos, 0) elsewhere, but I'm pretty sure dir.pos will never be greater than dir.ind.

if ind < len(dir.files) {
file = dir.files[ind]
}

if file != nil {
sel := &callExpr{"select", []string{file.path}, 1}

if tev.Buttons() == tcell.Button1 {
return sel
}
if file.IsDir() {
return &callExpr{"cd", []string{file.path}, 1}
}
return &listExpr{[]expr{sel, &callExpr{"open", nil, 1}}, 1}
}
if tev.Buttons() == tcell.Button1 {
return &callExpr{"cd", []string{dir.path}, 1}
}
case *tcell.EventResize:
return &callExpr{"redraw", nil, 1}
case *tcell.EventError:
log.Printf("Got EventError: '%s' at %s", tev.Error(), tev.When())
return nil
case *tcell.EventInterrupt:
log.Printf("Got EventInterrupt: at %s", tev.When())
return nil
}
return nil
}
Expand All @@ -1208,7 +1271,7 @@ func readCmdEvent(ev tcell.Event) expr {
return nil
}

func (ui *ui) readEvent(ev tcell.Event) expr {
func (ui *ui) readEvent(ev tcell.Event, nav *nav) expr {
if ev == nil {
return nil
}
Expand All @@ -1217,7 +1280,7 @@ func (ui *ui) readEvent(ev tcell.Event) expr {
return readCmdEvent(ev)
}

return ui.readNormalEvent(ev)
return ui.readNormalEvent(ev, nav)
}

func (ui *ui) readExpr() {
Expand Down