mirror of
https://github.com/zyedidia/micro.git
synced 2026-02-06 07:00:24 +09:00
Add autocomplete
This commit is contained in:
@@ -560,10 +560,22 @@ func (h *BufPane) OutdentSelection() bool {
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
|
||||
@@ -227,6 +227,9 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
|
||||
func (h *BufPane) DoKeyEvent(e Event) bool {
|
||||
if action, ok := BufKeyBindings[e]; ok {
|
||||
estr := BufKeyStrings[e]
|
||||
if estr != "InsertTab" {
|
||||
h.Buf.HasSuggestions = false
|
||||
}
|
||||
for _, s := range MultiActions {
|
||||
if s == estr {
|
||||
cursors := h.Buf.GetCursors()
|
||||
|
||||
@@ -157,12 +157,17 @@ var InfoOverrides = map[string]InfoKeyAction{
|
||||
"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 {
|
||||
@@ -187,22 +192,32 @@ func (h *InfoPane) InsertTab() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
// 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)
|
||||
|
||||
@@ -24,15 +24,18 @@ func (b *Buffer) GetSuggestions() {
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -53,7 +56,7 @@ func (b *Buffer) CycleAutocomplete(forward bool) {
|
||||
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])
|
||||
@@ -62,6 +65,27 @@ func (b *Buffer) CycleAutocomplete(forward bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
@@ -128,3 +152,55 @@ func FileComplete(b *Buffer) ([]string, []string) {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -69,6 +69,43 @@ func (s *StatusLine) Display() {
|
||||
// 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")) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/blang/semver"
|
||||
@@ -403,3 +404,7 @@ func Clamp(val, min, max int) int {
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func IsNonAlphaNumeric(c rune) bool {
|
||||
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user