From b4e0ab867f2375ccdf22b160891165a96ca2c254 Mon Sep 17 00:00:00 2001 From: Gregor Best Date: Sun, 2 Jul 2023 18:38:49 +0200 Subject: [PATCH] Add first completion target with multiple matches When a completion target has multiple matches, such as when editing Go code that uses generated Protobuf packages that have the same method name at multiple levels of hierarchy, insert the first match always, and display the rest in an Acme window called `/LSP/Completions` similar to `/LSP/Diagnostics`. This behaviour can be triggered by passing `-E` to `L comp` instead of `-e`. The existing behaviour for `-e` is preserved as is. --- cmd/L/main.go | 20 ++++++++++++--- cmd/Lone/main.go | 2 +- internal/lsp/acmelsp/assist.go | 2 +- internal/lsp/acmelsp/remote.go | 46 ++++++++++++++++++++++++++++++---- internal/lsp/text/edit.go | 1 + 5 files changed, 61 insertions(+), 10 deletions(-) diff --git a/cmd/L/main.go b/cmd/L/main.go index 50d4dcd..b590701 100644 --- a/cmd/L/main.go +++ b/cmd/L/main.go @@ -40,10 +40,13 @@ attempt to find the focused window ID by connecting to acmefocused List of sub-commands: - comp [-e] + comp [-e] [-E] Print candidate completions at the cursor position. If -e (edit) flag is given and there is only one candidate, - the completion is applied instead of being printed. + the completion is applied instead of being printed. If + -E (Edit) flag is given, the first matching candidate is + applied, and all matches will be displayed in a dedicated + Acme window named /LSP/Completions. def [-p] Find where the symbol at the cursor position is defined @@ -206,7 +209,18 @@ func run(cfg *config.Config, args []string) error { switch args[0] { case "comp": args = args[1:] - return rc.Completion(ctx, len(args) > 0 && args[0] == "-e") + + var kind acmelsp.CompletionKind + if len(args) > 0 { + switch args[0] { + case "-e": + kind = acmelsp.CompleteInsertOnlyMatch + case "-E": + kind = acmelsp.CompleteInsertFirstMatch + } + } + + return rc.Completion(ctx, kind) case "def": args = args[1:] return rc.Definition(ctx, len(args) > 0 && args[0] == "-p") diff --git a/cmd/Lone/main.go b/cmd/Lone/main.go index 0d87707..9452c66 100644 --- a/cmd/Lone/main.go +++ b/cmd/Lone/main.go @@ -142,7 +142,7 @@ func run(cfg *config.Config, args []string) error { switch args[0] { case "comp": - err = rc.Completion(ctx, false) + err = rc.Completion(ctx, acmelsp.CompleteNoEdit) case "def": err = rc.Definition(ctx, false) case "fmt": diff --git a/internal/lsp/acmelsp/assist.go b/internal/lsp/acmelsp/assist.go index 5c38ea9..106fad6 100644 --- a/internal/lsp/acmelsp/assist.go +++ b/internal/lsp/acmelsp/assist.go @@ -220,7 +220,7 @@ func (w *outputWin) Update(fw *focusWin, server proxy.Server, cmd string) { w.Clear() switch cmd { case "comp": - err := rc.Completion(ctx, false) + err := rc.Completion(ctx, CompleteNoEdit) if err != nil { dprintf("Completion failed: %v\n", err) } diff --git a/internal/lsp/acmelsp/remote.go b/internal/lsp/acmelsp/remote.go index e0dc300..b81456b 100644 --- a/internal/lsp/acmelsp/remote.go +++ b/internal/lsp/acmelsp/remote.go @@ -73,7 +73,15 @@ func (rc *RemoteCmd) DidChange(ctx context.Context) error { }) } -func (rc *RemoteCmd) Completion(ctx context.Context, edit bool) error { +type CompletionKind int + +const ( + CompleteNoEdit CompletionKind = iota + CompleteInsertOnlyMatch + CompleteInsertFirstMatch +) + +func (rc *RemoteCmd) Completion(ctx context.Context, kind CompletionKind) error { w, err := acmeutil.OpenWin(rc.winid) if err != nil { return err @@ -90,7 +98,8 @@ func (rc *RemoteCmd) Completion(ctx context.Context, edit bool) error { if err != nil { return err } - if edit && len(result.Items) == 1 { + + if (kind == CompleteInsertFirstMatch && len(result.Items) >= 1) || (kind == CompleteInsertOnlyMatch && len(result.Items) == 1) { textEdit := result.Items[0].TextEdit if textEdit == nil { // TODO(fhs): Use insertText or label instead. @@ -99,14 +108,41 @@ func (rc *RemoteCmd) Completion(ctx context.Context, edit bool) error { if err := text.Edit(w, []protocol.TextEdit{*textEdit}); err != nil { return fmt.Errorf("failed to apply completion edit: %v", err) } - return nil + + if len(result.Items) == 1 { + return nil + } } + + var sb strings.Builder + if len(result.Items) == 0 { - fmt.Fprintf(rc.Stderr, "no completion\n") + fmt.Fprintf(&sb, "no completion\n") } + for _, item := range result.Items { - fmt.Fprintf(rc.Stdout, "%v %v\n", item.Label, item.Detail) + fmt.Fprintf(&sb, "%v\t%v\n", item.Label, item.Detail) } + + if kind == CompleteInsertFirstMatch { + cw, err := acmeutil.Hijack("/LSP/Completions") + if err != nil { + cw, err = acmeutil.NewWin() + if err != nil { + return err + } + + cw.Name("/LSP/Completions") + } + + defer cw.Win.Ctl("clean") + + cw.Clear() + cw.PrintTabbed(sb.String()) + } else { + fmt.Println(sb.String()) + } + return nil } diff --git a/internal/lsp/text/edit.go b/internal/lsp/text/edit.go index 8d98517..3848358 100644 --- a/internal/lsp/text/edit.go +++ b/internal/lsp/text/edit.go @@ -65,6 +65,7 @@ func Edit(f File, edits []protocol.TextEdit) error { q1 := off.LineToOffset(int(e.Range.End.Line), int(e.Range.End.Character)) f.WriteAt(q0, q1, []byte(e.NewText)) } + return nil }