From e82f32fb515dbb2bac8eb8b0a5121f0dd2a515a0 Mon Sep 17 00:00:00 2001 From: kmarius Date: Sat, 21 Nov 2020 14:21:49 +0100 Subject: [PATCH] add experimental visual-selection mode as in https://github.com/gokcehan/lf/issues/477 --- eval.go | 18 +++++++++ nav.go | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) diff --git a/eval.go b/eval.go index e74205c4..05ff4a84 100644 --- a/eval.go +++ b/eval.go @@ -602,6 +602,24 @@ func insert(app *app, arg string) { func (e *callExpr) eval(app *app, args []string) { switch e.name { + case "visual": + if !app.nav.visual { + app.nav.visualReverse = false + app.nav.enterVisual() + app.ui.echo("mode: visual") + } else { + app.nav.exitVisual() + app.ui.echo("mode: normal") + } + case "visual-reverse": + if !app.nav.visual { + app.nav.visualReverse = true + app.nav.enterVisual() + app.ui.echo("mode: visual") + } else { + app.nav.exitVisual() + app.ui.echo("mode: normal") + } case "up": if app.ui.cmdPrefix != "" && app.ui.cmdPrefix != ">" { normal(app) diff --git a/nav.go b/nav.go index 6510ed74..e89f0c45 100644 --- a/nav.go +++ b/nav.go @@ -304,6 +304,10 @@ type nav struct { searchInd int searchPos int volatilePreview bool + visual bool + visualStart int + visualReverse bool + oldSelections map[string]int } func (nav *nav) loadDir(path string) *dir { @@ -409,6 +413,10 @@ func newNav(height int) *nav { selections: make(map[string]int), selectionInd: 0, height: height, + visual: false, + visualReverse: false, + visualStart: 0, + oldSelections: make(map[string]int), } nav.getDirs(wd) @@ -416,6 +424,36 @@ func newNav(height int) *nav { return nav } +func (nav *nav) enterVisual() { + dir := nav.currDir() + nav.visual = true + nav.visualStart = dir.ind + + curr, err := nav.currFile() + if err != nil { + return + } + + // add starting file to the old selection/unselect it + if nav.visualReverse { + nav.unselectFile(curr.path) + } else { + nav.selectFile(curr.path) + } + + // make a copy of already selected files + for key, value := range nav.selections { + nav.oldSelections[key] = value + } +} + +func (nav *nav) exitVisual() { + nav.oldSelections = make(map[string]int) + nav.visualStart = 0 + nav.visual = false + nav.visualReverse = false +} + func (nav *nav) renew() { for _, d := range nav.dirs { nav.checkDir(d) @@ -600,6 +638,10 @@ func (nav *nav) up(dist int) { return } + if nav.visual { + nav.visualSelectRange(dir.ind, max(0, dir.ind-dist)) + } + dir.ind -= dist dir.ind = max(0, dir.ind) @@ -620,6 +662,10 @@ func (nav *nav) down(dist int) { return } + if nav.visual { + nav.visualSelectRange(dir.ind, min(maxind, dir.ind+dist)) + } + dir.ind += dist dir.ind = min(maxind, dir.ind) @@ -655,6 +701,7 @@ func (nav *nav) open() error { if err != nil { return fmt.Errorf("open: %s", err) } + nav.exitVisual() path := curr.path @@ -671,6 +718,7 @@ func (nav *nav) open() error { func (nav *nav) top() { dir := nav.currDir() + nav.visualSelectRange(dir.ind, 0) dir.ind = 0 dir.pos = 0 @@ -678,11 +726,82 @@ func (nav *nav) top() { func (nav *nav) bottom() { dir := nav.currDir() + nav.visualSelectRange(dir.ind, len(dir.files)-1) dir.ind = len(dir.files) - 1 dir.pos = min(dir.ind, nav.height-1) } +func (nav *nav) visualSelectRange(from int, to int) { + dir := nav.currDir() + hi := nav.visualStart + lo := nav.visualStart + if from >= nav.visualStart { + if to > from { + lo = from + 1 + hi = to + } else if to < nav.visualStart { + hi = from + lo = to + } else { + hi = from + lo = to + 1 + } + } + if from < nav.visualStart { + if to < from { + lo = to + hi = from - 1 + } else if to > nav.visualStart { + lo = from + hi = to + } else { + lo = from + hi = to - 1 + } + } + for i := lo; i <= hi; i++ { + path := filepath.Join(dir.path, dir.files[i].Name()) + if nav.visualReverse { + if _, ok := nav.selections[path]; ok { + // file is currently selected. unselect it + nav.unselectFile(path) + } else { + // file is not currently selected. if it is form the old selection, select + if _, ok := nav.oldSelections[path]; ok { + nav.selectFile(path) + } + } + } else { + if _, ok := nav.selections[path]; !ok { + // file is not currently selected. select it + nav.selectFile(path) + } else { + // file is currently selected. unselect only if it is not in the old selection + if _, ok := nav.oldSelections[path]; !ok { + nav.unselectFile(path) + } + } + } + } +} + +func (nav *nav) selectFile(path string) { + if _, ok := nav.selections[path]; !ok { + nav.selections[path] = nav.selectionInd + nav.selectionInd++ + } +} + +func (nav *nav) unselectFile(path string) { + if _, ok := nav.selections[path]; ok { + delete(nav.selections, path) + if len(nav.selections) == 0 { + nav.selectionInd = 0 + } + } +} + func (nav *nav) toggleSelection(path string) { if _, ok := nav.selections[path]; ok { delete(nav.selections, path) @@ -715,6 +834,7 @@ func (nav *nav) invert() { func (nav *nav) unselect() { nav.selections = make(map[string]int) nav.selectionInd = 0 + nav.exitVisual() } func (nav *nav) save(cp bool) error { @@ -987,6 +1107,7 @@ func (nav *nav) sync() error { } func (nav *nav) cd(wd string) error { + nav.exitVisual() wd = replaceTilde(wd) wd = filepath.Clean(wd)