// InsertTab inserts a tab or spaces
func (h *BufPane) InsertTab() bool {
- indent := h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))
- tabBytes := len(indent)
- bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
- h.Buf.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
+ b := h.Buf
+ if b.HasSuggestions {
+ b.CycleAutocomplete(true)
+ return true
+ }
+
+ l := b.LineBytes(h.Cursor.Y)
+ l = util.SliceStart(l, h.Cursor.X)
+ hasComplete := b.Autocomplete(buffer.BufferComplete)
+ if !hasComplete {
+ indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
+ tabBytes := len(indent)
+ bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
+ b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
+ return true
+ }
return true
}
"QuitAll": (*InfoPane).QuitAll,
}
+// CursorUp cycles history up
func (h *InfoPane) CursorUp() {
h.UpHistory(h.History[h.PromptType])
}
+
+// CursorDown cycles history down
func (h *InfoPane) CursorDown() {
h.DownHistory(h.History[h.PromptType])
}
+
+// InsertTab begins autocompletion
func (h *InfoPane) InsertTab() {
b := h.Buf
if b.HasSuggestions {
}
}
}
+
+// CycleBack cycles back in the autocomplete suggestion list
func (h *InfoPane) CycleBack() {
if h.Buf.HasSuggestions {
h.Buf.CycleAutocomplete(false)
}
}
+
+// InsertNewline completes the prompt
func (h *InfoPane) InsertNewline() {
if !h.HasYN {
h.DonePrompt(false)
}
}
+
+// Quit cancels the prompt
func (h *InfoPane) Quit() {
h.DonePrompt(true)
}
+
+// QuitAll cancels the prompt
func (h *InfoPane) QuitAll() {
h.DonePrompt(true)
}
+
+// Escape cancels the prompt
func (h *InfoPane) Escape() {
h.DonePrompt(true)
}
// cursor location
// It returns a list of string suggestions which will be inserted at
// the current cursor location if selected as well as a list of
-// suggestion names which can be displayed in a autocomplete box or
+// suggestion names which can be displayed in an autocomplete box or
// other UI element
type Completer func(*Buffer) ([]string, []string)
}
-func (b *Buffer) Autocomplete(c Completer) {
+// Autocomplete starts the autocomplete process
+func (b *Buffer) Autocomplete(c Completer) bool {
b.Completions, b.Suggestions = c(b)
if len(b.Completions) != len(b.Suggestions) || len(b.Completions) == 0 {
- return
+ return false
}
b.CurSuggestion = -1
b.CycleAutocomplete(true)
+ return true
}
+// CycleAutocomplete moves to the next suggestion
func (b *Buffer) CycleAutocomplete(forward bool) {
prevSuggestion := b.CurSuggestion
if prevSuggestion < len(b.Suggestions) && prevSuggestion >= 0 {
start = end.Move(-utf8.RuneCountInString(b.Completions[prevSuggestion]), b)
} else {
- end = start.Move(1, b)
+ // end = start.Move(1, b)
}
b.Replace(start, end, b.Completions[b.CurSuggestion])
}
}
+// GetWord gets the most recent word separated by any separator
+// (whitespace, punctuation, any non alphanumeric character)
+func GetWord(b *Buffer) ([]byte, int) {
+ c := b.GetActiveCursor()
+ l := b.LineBytes(c.Y)
+ l = util.SliceStart(l, c.X)
+
+ if c.X == 0 || util.IsWhitespace(b.RuneAt(c.Loc)) {
+ return []byte{}, -1
+ }
+
+ if util.IsNonAlphaNumeric(b.RuneAt(c.Loc)) {
+ return []byte{}, c.X
+ }
+
+ args := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
+ input := args[len(args)-1]
+ return input, c.X - utf8.RuneCount(input)
+}
+
+// GetArg gets the most recent word (separated by ' ' only)
func GetArg(b *Buffer) (string, int) {
c := b.GetActiveCursor()
l := b.LineBytes(c.Y)
return completions, suggestions
}
+
+// BufferComplete autocompletes based on previous words in the buffer
+func BufferComplete(b *Buffer) ([]string, []string) {
+ c := b.GetActiveCursor()
+ input, argstart := GetWord(b)
+
+ if argstart == -1 {
+ return []string{}, []string{}
+ }
+
+ inputLen := utf8.RuneCount(input)
+
+ suggestionsSet := make(map[string]struct{})
+
+ var suggestions []string
+ for i := c.Y; i >= 0; i-- {
+ l := b.LineBytes(i)
+ words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
+ for _, w := range words {
+ if bytes.HasPrefix(w, input) && utf8.RuneCount(w) > inputLen {
+ strw := string(w)
+ if _, ok := suggestionsSet[strw]; !ok {
+ suggestionsSet[strw] = struct{}{}
+ suggestions = append(suggestions, strw)
+ }
+ }
+ }
+ }
+ for i := c.Y + 1; i < b.LinesNum(); i++ {
+ l := b.LineBytes(i)
+ words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
+ for _, w := range words {
+ if bytes.HasPrefix(w, input) && utf8.RuneCount(w) > inputLen {
+ strw := string(w)
+ if _, ok := suggestionsSet[strw]; !ok {
+ suggestionsSet[strw] = struct{}{}
+ suggestions = append(suggestions, strw)
+ }
+ }
+ }
+ }
+ if len(suggestions) > 1 {
+ suggestions = append(suggestions, string(input))
+ }
+
+ completions := make([]string, len(suggestions))
+ for i := range suggestions {
+ completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
+ }
+
+ return completions, suggestions
+}
// We'll draw the line at the lowest line in the window
y := s.win.Height + s.win.Y - 1
+ b := s.win.Buf
+ if b.HasSuggestions && len(b.Suggestions) > 1 {
+ statusLineStyle := config.DefStyle.Reverse(true)
+ if style, ok := config.Colorscheme["statusline"]; ok {
+ statusLineStyle = style
+ }
+ keymenuOffset := 0
+ if config.GetGlobalOption("keymenu").(bool) {
+ keymenuOffset = len(keydisplay)
+ }
+ x := 0
+ for j, sug := range b.Suggestions {
+ style := statusLineStyle
+ if b.CurSuggestion == j {
+ style = style.Reverse(true)
+ }
+ for _, r := range sug {
+ screen.Screen.SetContent(x, y-keymenuOffset, r, nil, style)
+ x++
+ if x >= s.win.Width {
+ return
+ }
+ }
+ screen.Screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
+ x++
+ if x >= s.win.Width {
+ return
+ }
+ }
+
+ for x < s.win.Width {
+ screen.Screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
+ x++
+ }
+ return
+ }
+
formatter := func(match []byte) []byte {
name := match[2 : len(match)-1]
if bytes.HasPrefix(name, []byte("opt")) {