mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-18 23:07:13 +09:00
Use text edits for autocompletion
This commit is contained in:
@@ -666,6 +666,13 @@ func (h *BufPane) Autocomplete() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// if there is an existing completion, always cycle it
|
||||
if b.HasSuggestions {
|
||||
b.CycleAutocomplete(true)
|
||||
return true
|
||||
}
|
||||
|
||||
// don't start a new completion unless the correct conditions are met
|
||||
if h.Cursor.X == 0 {
|
||||
return false
|
||||
}
|
||||
@@ -675,11 +682,6 @@ func (h *BufPane) Autocomplete() bool {
|
||||
// don't autocomplete if cursor is on alpha numeric character (middle of a word)
|
||||
return false
|
||||
}
|
||||
|
||||
if b.HasSuggestions {
|
||||
b.CycleAutocomplete(true)
|
||||
return true
|
||||
}
|
||||
return b.Autocomplete(buffer.LSPComplete)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
// for example with `vsplit filename`.
|
||||
|
||||
// CommandComplete autocompletes commands
|
||||
func CommandComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
func CommandComplete(b *buffer.Buffer) []buffer.Completion {
|
||||
c := b.GetActiveCursor()
|
||||
input, argstart := buffer.GetArg(b)
|
||||
|
||||
@@ -32,11 +32,11 @@ func CommandComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
|
||||
}
|
||||
|
||||
return completions, suggestions
|
||||
return buffer.ConvertCompletions(completions, suggestions, c)
|
||||
}
|
||||
|
||||
// HelpComplete autocompletes help topics
|
||||
func HelpComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
func HelpComplete(b *buffer.Buffer) []buffer.Completion {
|
||||
c := b.GetActiveCursor()
|
||||
input, argstart := buffer.GetArg(b)
|
||||
|
||||
@@ -54,7 +54,7 @@ func HelpComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
for i := range suggestions {
|
||||
completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
|
||||
}
|
||||
return completions, suggestions
|
||||
return buffer.ConvertCompletions(completions, suggestions, c)
|
||||
}
|
||||
|
||||
// colorschemeComplete tab-completes names of colorschemes.
|
||||
@@ -87,7 +87,7 @@ func contains(s []string, e string) bool {
|
||||
}
|
||||
|
||||
// OptionComplete autocompletes options
|
||||
func OptionComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
func OptionComplete(b *buffer.Buffer) []buffer.Completion {
|
||||
c := b.GetActiveCursor()
|
||||
input, argstart := buffer.GetArg(b)
|
||||
|
||||
@@ -97,22 +97,17 @@ func OptionComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
suggestions = append(suggestions, option)
|
||||
}
|
||||
}
|
||||
// for option := range localSettings {
|
||||
// if strings.HasPrefix(option, input) && !contains(suggestions, option) {
|
||||
// suggestions = append(suggestions, option)
|
||||
// }
|
||||
// }
|
||||
|
||||
sort.Strings(suggestions)
|
||||
completions := make([]string, len(suggestions))
|
||||
for i := range suggestions {
|
||||
completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
|
||||
}
|
||||
return completions, suggestions
|
||||
return buffer.ConvertCompletions(completions, suggestions, c)
|
||||
}
|
||||
|
||||
// OptionValueComplete completes values for various options
|
||||
func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
func OptionValueComplete(b *buffer.Buffer) []buffer.Completion {
|
||||
c := b.GetActiveCursor()
|
||||
l := b.LineBytes(c.Y)
|
||||
l = util.SliceStart(l, c.X)
|
||||
@@ -128,12 +123,6 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
break
|
||||
}
|
||||
}
|
||||
// for option := range localSettings {
|
||||
// if option == string(args[len(args)-2]) {
|
||||
// completeValue = true
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
}
|
||||
if !completeValue {
|
||||
return OptionComplete(b)
|
||||
@@ -150,11 +139,6 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
optionVal = option
|
||||
}
|
||||
}
|
||||
// for k, option := range localSettings {
|
||||
// if k == inputOpt {
|
||||
// optionVal = option
|
||||
// }
|
||||
// }
|
||||
|
||||
switch optionVal.(type) {
|
||||
case bool:
|
||||
@@ -204,11 +188,11 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
for i := range suggestions {
|
||||
completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
|
||||
}
|
||||
return completions, suggestions
|
||||
return buffer.ConvertCompletions(completions, suggestions, c)
|
||||
}
|
||||
|
||||
// PluginCmdComplete autocompletes the plugin command
|
||||
func PluginCmdComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
func PluginCmdComplete(b *buffer.Buffer) []buffer.Completion {
|
||||
c := b.GetActiveCursor()
|
||||
input, argstart := buffer.GetArg(b)
|
||||
|
||||
@@ -224,11 +208,11 @@ func PluginCmdComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
for i := range suggestions {
|
||||
completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
|
||||
}
|
||||
return completions, suggestions
|
||||
return buffer.ConvertCompletions(completions, suggestions, c)
|
||||
}
|
||||
|
||||
// PluginComplete completes values for the plugin command
|
||||
func PluginComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
func PluginComplete(b *buffer.Buffer) []buffer.Completion {
|
||||
c := b.GetActiveCursor()
|
||||
l := b.LineBytes(c.Y)
|
||||
l = util.SliceStart(l, c.X)
|
||||
@@ -260,7 +244,7 @@ func PluginComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
for i := range suggestions {
|
||||
completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
|
||||
}
|
||||
return completions, suggestions
|
||||
return buffer.ConvertCompletions(completions, suggestions, c)
|
||||
}
|
||||
|
||||
// PluginNameComplete completes with the names of loaded plugins
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/lsp"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"go.lsp.dev/protocol"
|
||||
)
|
||||
|
||||
// A Completer is a function that takes a buffer and returns info
|
||||
@@ -18,49 +19,56 @@ import (
|
||||
// the current cursor location if selected as well as a list of
|
||||
// suggestion names which can be displayed in an autocomplete box or
|
||||
// other UI element
|
||||
type Completer func(*Buffer) ([]string, []string)
|
||||
|
||||
func (b *Buffer) GetSuggestions() {
|
||||
type Completer func(*Buffer) []Completion
|
||||
|
||||
type Completion struct {
|
||||
Edits []protocol.TextEdit
|
||||
Label string
|
||||
CommitChars []rune
|
||||
Kind int
|
||||
Filter string
|
||||
Detail string
|
||||
Doc string
|
||||
}
|
||||
|
||||
// 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 {
|
||||
b.Completions = c(b)
|
||||
if len(b.Completions) == 0 {
|
||||
return false
|
||||
}
|
||||
b.CurSuggestion = -1
|
||||
b.CurCompletion = -1
|
||||
b.CycleAutocomplete(true)
|
||||
return true
|
||||
}
|
||||
|
||||
// CycleAutocomplete moves to the next suggestion
|
||||
func (b *Buffer) CycleAutocomplete(forward bool) {
|
||||
prevSuggestion := b.CurSuggestion
|
||||
prevCompletion := b.CurCompletion
|
||||
|
||||
if forward {
|
||||
b.CurSuggestion++
|
||||
b.CurCompletion++
|
||||
} else {
|
||||
b.CurSuggestion--
|
||||
b.CurCompletion--
|
||||
}
|
||||
if b.CurSuggestion >= len(b.Suggestions) {
|
||||
b.CurSuggestion = 0
|
||||
} else if b.CurSuggestion < 0 {
|
||||
b.CurSuggestion = len(b.Suggestions) - 1
|
||||
if b.CurCompletion >= len(b.Completions) {
|
||||
b.CurCompletion = 0
|
||||
} else if b.CurCompletion < 0 {
|
||||
b.CurCompletion = len(b.Completions) - 1
|
||||
}
|
||||
|
||||
c := b.GetActiveCursor()
|
||||
start := c.Loc
|
||||
end := c.Loc
|
||||
if prevSuggestion < len(b.Suggestions) && prevSuggestion >= 0 {
|
||||
start = end.Move(-util.CharacterCountInString(b.Completions[prevSuggestion]), b)
|
||||
} else {
|
||||
// end = start.Move(1, b)
|
||||
// undo prev completion
|
||||
if prevCompletion != -1 {
|
||||
prev := b.Completions[prevCompletion]
|
||||
for i := 0; i < len(prev.Edits); i++ {
|
||||
b.UndoOneEvent()
|
||||
}
|
||||
}
|
||||
|
||||
b.Replace(start, end, b.Completions[b.CurSuggestion])
|
||||
if len(b.Suggestions) > 1 {
|
||||
// apply current completion
|
||||
comp := b.Completions[b.CurCompletion]
|
||||
b.ApplyEdits(comp.Edits)
|
||||
if len(b.Completions) > 1 {
|
||||
b.HasSuggestions = true
|
||||
}
|
||||
}
|
||||
@@ -105,7 +113,7 @@ func GetArg(b *Buffer) (string, int) {
|
||||
}
|
||||
|
||||
// FileComplete autocompletes filenames
|
||||
func FileComplete(b *Buffer) ([]string, []string) {
|
||||
func FileComplete(b *Buffer) []Completion {
|
||||
c := b.GetActiveCursor()
|
||||
input, argstart := GetArg(b)
|
||||
|
||||
@@ -124,7 +132,7 @@ func FileComplete(b *Buffer) ([]string, []string) {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
var suggestions []string
|
||||
@@ -150,16 +158,16 @@ func FileComplete(b *Buffer) ([]string, []string) {
|
||||
completions[i] = util.SliceEndStr(complete, c.X-argstart)
|
||||
}
|
||||
|
||||
return completions, suggestions
|
||||
return ConvertCompletions(completions, suggestions, c)
|
||||
}
|
||||
|
||||
// BufferComplete autocompletes based on previous words in the buffer
|
||||
func BufferComplete(b *Buffer) ([]string, []string) {
|
||||
func BufferComplete(b *Buffer) []Completion {
|
||||
c := b.GetActiveCursor()
|
||||
input, argstart := GetWord(b)
|
||||
|
||||
if argstart == -1 {
|
||||
return []string{}, []string{}
|
||||
return nil
|
||||
}
|
||||
|
||||
inputLen := util.CharacterCount(input)
|
||||
@@ -202,36 +210,69 @@ func BufferComplete(b *Buffer) ([]string, []string) {
|
||||
completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
|
||||
}
|
||||
|
||||
return completions, suggestions
|
||||
return ConvertCompletions(completions, suggestions, c)
|
||||
}
|
||||
|
||||
func LSPComplete(b *Buffer) ([]string, []string) {
|
||||
c := b.GetActiveCursor()
|
||||
_, argstart := GetWord(b)
|
||||
|
||||
if argstart == -1 {
|
||||
return []string{}, []string{}
|
||||
func LSPComplete(b *Buffer) []Completion {
|
||||
if !b.HasLSP() {
|
||||
return nil
|
||||
}
|
||||
|
||||
c := b.GetActiveCursor()
|
||||
pos := lsp.Position(c.X, c.Y)
|
||||
items, err := b.Server.Completion(b.AbsPath, pos)
|
||||
if err != nil {
|
||||
return []string{}, []string{}
|
||||
return nil
|
||||
}
|
||||
|
||||
suggestions := make([]string, len(items))
|
||||
completions := make([]string, len(items))
|
||||
completions := make([]Completion, len(items))
|
||||
|
||||
for i, item := range items {
|
||||
suggestions[i] = item.Label
|
||||
completions[i] = Completion{
|
||||
Label: item.Label,
|
||||
Detail: item.Detail,
|
||||
}
|
||||
|
||||
if item.TextEdit != nil && len(item.TextEdit.NewText) > 0 {
|
||||
completions[i] = util.SliceEndStr(item.TextEdit.NewText, c.X-argstart)
|
||||
} else if len(item.InsertText) > 0 {
|
||||
completions[i] = util.SliceEndStr(item.InsertText, c.X-argstart)
|
||||
completions[i].Edits = []protocol.TextEdit{*item.TextEdit}
|
||||
} else {
|
||||
completions[i] = util.SliceEndStr(item.Label, c.X-argstart)
|
||||
var t string
|
||||
if len(item.InsertText) > 0 {
|
||||
t = item.InsertText
|
||||
} else {
|
||||
t = item.Label
|
||||
}
|
||||
_, argstart := GetWord(b)
|
||||
str := util.SliceEndStr(t, c.X-argstart)
|
||||
completions[i].Edits = []protocol.TextEdit{protocol.TextEdit{
|
||||
NewText: str,
|
||||
Range: protocol.Range{
|
||||
Start: lsp.Position(c.X, c.Y),
|
||||
End: lsp.Position(c.X, c.Y),
|
||||
},
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
return completions, suggestions
|
||||
return completions
|
||||
}
|
||||
|
||||
// ConvertCompletions converts a list of insert text with suggestion labels
|
||||
// to an array of completion objects ready for autocompletion
|
||||
func ConvertCompletions(completions, suggestions []string, c *Cursor) []Completion {
|
||||
comp := make([]Completion, len(completions))
|
||||
|
||||
for i := 0; i < len(completions); i++ {
|
||||
comp[i] = Completion{
|
||||
Label: suggestions[i],
|
||||
}
|
||||
comp[i].Edits = []protocol.TextEdit{protocol.TextEdit{
|
||||
NewText: completions[i],
|
||||
Range: protocol.Range{
|
||||
Start: lsp.Position(c.X, c.Y),
|
||||
End: lsp.Position(c.X, c.Y),
|
||||
},
|
||||
}}
|
||||
}
|
||||
return comp
|
||||
}
|
||||
|
||||
@@ -93,9 +93,8 @@ type SharedBuffer struct {
|
||||
// Settings customized by the user
|
||||
Settings map[string]interface{}
|
||||
|
||||
Suggestions []string
|
||||
Completions []string
|
||||
CurSuggestion int
|
||||
Completions []Completion
|
||||
CurCompletion int
|
||||
|
||||
Messages []*Message
|
||||
|
||||
|
||||
@@ -179,8 +179,8 @@ func (i *InfoWindow) displayKeyMenu() {
|
||||
|
||||
func (i *InfoWindow) totalSize() int {
|
||||
sum := 0
|
||||
for _, n := range i.Suggestions {
|
||||
sum += runewidth.StringWidth(n) + 1
|
||||
for _, n := range i.Completions {
|
||||
sum += runewidth.StringWidth(n.Label) + 1
|
||||
}
|
||||
return sum
|
||||
}
|
||||
@@ -189,9 +189,9 @@ func (i *InfoWindow) scrollToSuggestion() {
|
||||
x := 0
|
||||
s := i.totalSize()
|
||||
|
||||
for j, n := range i.Suggestions {
|
||||
c := util.CharacterCountInString(n)
|
||||
if j == i.CurSuggestion {
|
||||
for j, n := range i.Completions {
|
||||
c := util.CharacterCountInString(n.Label)
|
||||
if j == i.CurCompletion {
|
||||
if x+c >= i.hscroll+i.Width {
|
||||
i.hscroll = util.Clamp(x+c+1-i.Width, 0, s-i.Width)
|
||||
} else if x < i.hscroll {
|
||||
@@ -236,7 +236,7 @@ func (i *InfoWindow) Display() {
|
||||
}
|
||||
}
|
||||
|
||||
if i.HasSuggestions && len(i.Suggestions) > 1 {
|
||||
if i.HasSuggestions && len(i.Completions) > 1 {
|
||||
i.scrollToSuggestion()
|
||||
|
||||
x := -i.hscroll
|
||||
@@ -273,12 +273,12 @@ func (i *InfoWindow) Display() {
|
||||
}
|
||||
}
|
||||
|
||||
for j, s := range i.Suggestions {
|
||||
for j, s := range i.Completions {
|
||||
style := statusLineStyle
|
||||
if i.CurSuggestion == j {
|
||||
if i.CurCompletion == j {
|
||||
style = style.Reverse(true)
|
||||
}
|
||||
for _, r := range s {
|
||||
for _, r := range s.Label {
|
||||
draw(r, style)
|
||||
// screen.SetContent(x, i.Y-keymenuOffset-1, r, nil, style)
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ func (s *StatusLine) Display() {
|
||||
|
||||
b := s.win.Buf
|
||||
// autocomplete suggestions (for the buffer, not for the infowindow)
|
||||
if b.HasSuggestions && len(b.Suggestions) > 1 {
|
||||
if b.HasSuggestions && len(b.Completions) > 1 {
|
||||
statusLineStyle := config.DefStyle.Reverse(true)
|
||||
if style, ok := config.Colorscheme["statusline"]; ok {
|
||||
statusLineStyle = style
|
||||
@@ -110,12 +110,12 @@ func (s *StatusLine) Display() {
|
||||
keymenuOffset = len(keydisplay)
|
||||
}
|
||||
x := 0
|
||||
for j, sug := range b.Suggestions {
|
||||
for j, sug := range b.Completions {
|
||||
style := statusLineStyle
|
||||
if b.CurSuggestion == j {
|
||||
if b.CurCompletion == j {
|
||||
style = style.Reverse(true)
|
||||
}
|
||||
for _, r := range sug {
|
||||
for _, r := range sug.Label {
|
||||
screen.SetContent(x, y-keymenuOffset, r, nil, style)
|
||||
x++
|
||||
if x >= s.win.Width {
|
||||
|
||||
Reference in New Issue
Block a user