Implement location mode.

This fixes #27.
This commit is contained in:
Qi Xiao 2016-02-27 04:11:00 +01:00
parent d149955c1d
commit 7209b48bf8
6 changed files with 128 additions and 5 deletions

View File

@ -40,6 +40,7 @@ var defaultBindings = map[bufferMode]map[Key]string{
Key{Up, 0}: "start-history",
Key{'N', Ctrl}: "start-navigation",
Key{'H', Ctrl}: "start-history-listing",
Key{'L', Ctrl}: "start-location",
},
modeCommand: map[Key]string{
Default: "default-command",
@ -85,6 +86,14 @@ var defaultBindings = map[bufferMode]map[Key]string{
modeHistoryListing: map[Key]string{
Default: "default-history-listing",
},
modeLocation: map[Key]string{
Key{Up, 0}: "location-prev",
Key{Down, 0}: "location-next",
Key{Tab, 0}: "location-next",
Key{Enter, 0}: "accept-location",
Key{'[', Ctrl}: "cancel-location",
Default: "location-default",
},
}
var keyBindings = map[bufferMode]map[Key]Caller{}

View File

@ -74,6 +74,14 @@ var builtins = []Builtin{
{"start-history-listing", startHistoryListing},
{"default-history-listing", defaultHistoryListing},
// Location mode
{"start-location", startLocation},
{"location-prev", locationPrev},
{"location-next", locationNext},
{"accept-location", acceptLocation},
{"cancel-location", cancelLocation},
{"location-default", locationDefault},
// Misc
{"redraw", redraw},
}

View File

@ -49,6 +49,7 @@ type editorState struct {
navigation *navigation
history historyState
historyListing *historyListing
location location
isExternal map[string]bool
parseErrorAtEnd bool
// Used for builtins.
@ -65,6 +66,7 @@ const (
modeNavigation
modeHistory
modeHistoryListing
modeLocation
)
type actionType int

74
edit/location.go Normal file
View File

@ -0,0 +1,74 @@
package edit
import (
"os"
"unicode"
"github.com/elves/elvish/store"
)
type location struct {
filter string
candidates []store.Dir
current int
}
func (l *location) updateCandidates(store *store.Store) error {
dirs, err := store.FindDirs(l.filter)
if err != nil {
return err
}
l.candidates = dirs
if len(l.candidates) > 0 {
l.current = 0
} else {
l.current = -1
}
return nil
}
func startLocation(ed *Editor) {
ed.location = location{}
ed.location.updateCandidates(ed.store)
ed.mode = modeLocation
}
func locationPrev(ed *Editor) {
if len(ed.location.candidates) > 0 && ed.location.current > 0 {
ed.location.current--
}
}
func locationNext(ed *Editor) {
if len(ed.location.candidates) > 0 && ed.location.current < len(ed.location.candidates)-1 {
ed.location.current++
}
}
func acceptLocation(ed *Editor) {
// XXX Maybe we want to use eval.cdInner and increase the score?
loc := &ed.location
if len(loc.candidates) > 0 {
err := os.Chdir(loc.candidates[loc.current].Path)
if err != nil {
ed.notify("%v", err)
}
}
ed.mode = modeInsert
}
func cancelLocation(ed *Editor) {
ed.mode = modeInsert
}
func locationDefault(ed *Editor) {
k := ed.lastKey
if k.Mod == 0 && k.Rune > 0 && unicode.IsGraphic(k.Rune) {
ed.location.filter += string(k.Rune)
ed.location.updateCandidates(ed.store)
} else {
cancelLocation(ed)
ed.nextAction = action{actionType: reprocessKey}
}
}

View File

@ -10,6 +10,8 @@ var (
styleForCurrentCompletion = ";7"
styleForCompletedHistory = "2"
styleForSelectedFile = ";7"
styleForLocation = "4"
styleForSelectedLocation = ";7"
)
var styleForType = map[TokenKind]string{

View File

@ -8,6 +8,7 @@ import (
"unicode"
"unicode/utf8"
"github.com/elves/elvish/parse"
"github.com/elves/elvish/sys"
)
@ -483,9 +484,15 @@ tokens:
case modeHistory:
text = fmt.Sprintf("HISTORY #%d", es.history.current)
case modeHistoryListing:
text = fmt.Sprintf("HISTORY LISTING")
text = "HISTORY LISTING"
case modeLocation:
text = "LOCATION"
}
b.writes(TrimWcWidth(" "+text+" ", width), styleForMode)
if es.mode == modeLocation {
b.writes(" ", "")
b.writes(es.location.filter, styleForLocation)
}
}
// bufTips
@ -528,10 +535,11 @@ tokens:
// Render bufListing under the maximum height constraint
nav := es.navigation
hist := es.historyListing
if hListing > 0 && (comp != nil || nav != nil || hist != nil) {
if hListing > 0 {
b := newBuffer(width)
bufListing = b
if comp != nil { // Completion listing
switch es.mode {
case modeCompletion:
// Layout candidates in multiple columns
cands := comp.candidates
@ -574,7 +582,7 @@ tokens:
b.writes(ForceWcWidth(text, colWidth), style)
}
}
} else if nav != nil { // Navigation listing
case modeNavigation:
margin := navigationListingColMargin
var ratioParent, ratioCurrent, ratioPreview int
if nav.dirPreview != nil {
@ -603,7 +611,7 @@ tokens:
bPreview := renderNavColumn(nav.dirPreview, wPreview, hListing)
b.extendHorizontal(bPreview, wParent+wCurrent+margin, margin)
}
} else if hist != nil {
case modeHistoryListing:
n := len(hist.all)
i := 0
@ -625,6 +633,26 @@ tokens:
if len(b.cells) > 0 {
b.trimToLines(startIndex, n)
}
case modeLocation:
loc := &es.location
if len(loc.candidates) == 0 {
b.writes("(no match)", "")
break
}
low, high := findWindow(len(loc.candidates), loc.current, hListing)
for i := low; i < high; i++ {
if i > low {
b.newline()
}
text := fmt.Sprintf("%4.0f %s", loc.candidates[i].Score, parse.Quote(loc.candidates[i].Path))
style := ""
if i == loc.current {
style = styleForSelectedLocation
}
b.writes(TrimWcWidth(text, width), style)
}
default:
bufListing = nil
}
}