Support pinning directories using $le:loc-pinned.

This fixes #342.
This commit is contained in:
Qi Xiao 2017-05-18 01:56:07 +01:00
parent d2c58c34cf
commit 1097ad5891
4 changed files with 71 additions and 23 deletions

View File

@ -35,14 +35,17 @@ type Editor struct {
evaler *eval.Evaler
cmdSeq int
prompt eval.Variable
rprompt eval.Variable
prompt eval.Variable
rprompt eval.Variable
rpromptPersistent eval.Variable
abbreviations map[string]string
locationHidden eval.Variable
rpromptPersistent eval.Variable
beforeReadLine eval.Variable
afterReadLine eval.Variable
locationHidden eval.Variable
locationPinned eval.Variable
beforeReadLine eval.Variable
afterReadLine eval.Variable
historyMutex sync.RWMutex
@ -104,20 +107,22 @@ func NewEditor(file *os.File, sigs chan os.Signal, ev *eval.Evaler, st *store.St
prompt, rprompt := defaultPrompts()
ed := &Editor{
file: file,
writer: newWriter(file),
reader: tty.NewReader(file),
sigs: sigs,
store: st,
evaler: ev,
cmdSeq: seq,
prompt: eval.NewPtrVariableWithValidator(prompt, eval.ShouldBeFn),
rprompt: eval.NewPtrVariableWithValidator(rprompt, eval.ShouldBeFn),
file: file,
writer: newWriter(file),
reader: tty.NewReader(file),
sigs: sigs,
store: st,
evaler: ev,
cmdSeq: seq,
prompt: eval.NewPtrVariableWithValidator(prompt, eval.ShouldBeFn),
rprompt: eval.NewPtrVariableWithValidator(rprompt, eval.ShouldBeFn),
rpromptPersistent: eval.NewPtrVariableWithValidator(eval.Bool(false), eval.ShouldBeBool),
abbreviations: make(map[string]string),
locationHidden: eval.NewPtrVariableWithValidator(eval.NewList(), eval.ShouldBeList),
rpromptPersistent: eval.NewPtrVariableWithValidator(eval.Bool(false), eval.ShouldBeBool),
locationHidden: eval.NewPtrVariableWithValidator(eval.NewList(), eval.ShouldBeList),
locationPinned: eval.NewPtrVariableWithValidator(eval.NewList(), eval.ShouldBeList),
beforeReadLine: eval.NewPtrVariableWithValidator(eval.NewList(), eval.ShouldBeList),
afterReadLine: eval.NewPtrVariableWithValidator(eval.NewList(), eval.ShouldBeList),

View File

@ -3,6 +3,7 @@ package edit
import (
"bytes"
"fmt"
"math"
"os"
"path/filepath"
"regexp"
@ -16,6 +17,10 @@ import (
// Location mode.
// PinnedScore is a special value of Score in store.Dir to represent that the
// directory is pinned.
var PinnedScore = math.Inf(1)
type location struct {
listing
home string // The home directory; leave empty if unknown.
@ -42,7 +47,13 @@ func (loc *location) Len() int {
}
func (loc *location) Show(i int) (string, styled) {
header := fmt.Sprintf("%.0f", loc.filtered[i].Score)
var header string
score := loc.filtered[i].Score
if score == PinnedScore {
header = "*"
} else {
header = fmt.Sprintf("%.0f", score)
}
return header, unstyled(showPath(loc.filtered[i].Path, loc.home))
}
@ -126,13 +137,28 @@ func startLocation(ed *Editor) {
ed.Notify("%v", ErrStoreOffline)
return
}
black := convertBlacklist(ed.locationHidden.Get().(eval.List))
black := convertListToSet(ed.locationHidden.Get().(eval.List))
dirs, err := ed.store.GetDirs(black)
if err != nil {
ed.Notify("store error: %v", err)
return
}
pinnedValue := ed.locationPinned.Get().(eval.List)
pinned := convertListToDirs(pinnedValue)
pinnedSet := convertListToSet(pinnedValue)
// TODO(xiaq): Optimize this by changing GetDirs to a callback API, and
// build dirs by first putting pinned directories and then appending those
// from store.
for _, d := range dirs {
_, inPinned := pinnedSet[d.Path]
if !inPinned {
pinned = append(pinned, d)
}
}
dirs = pinned
// Drop the error. When there is an error, home is "", which is used to
// signify "no home known" in location.
home, _ := util.GetHome("")
@ -140,14 +166,28 @@ func startLocation(ed *Editor) {
ed.mode = ed.location
}
func convertBlacklist(li eval.List) map[string]struct{} {
black := make(map[string]struct{})
// convertListToDirs converts a list of strings to []store.Dir. It uses the
// special score of PinnedScore to signify that the directory is pinned.
func convertListToDirs(li eval.List) []store.Dir {
pinned := make([]store.Dir, 0, li.Len())
// XXX(xiaq): silently drops non-string items.
li.Iterate(func(v eval.Value) bool {
if s, ok := v.(eval.String); ok {
black[string(s)] = struct{}{}
pinned = append(pinned, store.Dir{string(s), PinnedScore})
}
return true
})
return black
return pinned
}
func convertListToSet(li eval.List) map[string]struct{} {
set := make(map[string]struct{})
// XXX(xiaq): silently drops non-string items.
li.Iterate(func(v eval.Value) bool {
if s, ok := v.(eval.String); ok {
set[string(s)] = struct{}{}
}
return true
})
return set
}

View File

@ -8,6 +8,7 @@ import (
var (
theLocation = newLocation([]store.Dir{
{"/pinned", PinnedScore},
{"/src/github.com/elves/elvish", 300},
{"/src/home/xyz", 233},
{"/home/dir", 100},
@ -17,6 +18,7 @@ var (
locationFilterTests = []listingFilterTestCases{
{"", []shown{
{"*", unstyled("/pinned")},
{"300", unstyled("/src/github.com/elves/elvish")},
{"233", unstyled("/src/home/xyz")},
{"100", unstyled("~/dir")}, // home is abbreviated

View File

@ -78,6 +78,7 @@ func makeModule(ed *Editor) eval.Namespace {
ns["abbr"] = eval.NewRoVariable(eval.MapStringString(ed.abbreviations))
ns["loc-pinned"] = ed.locationPinned
ns["loc-hidden"] = ed.locationHidden
ns["before-readline"] = ed.beforeReadLine