Disable `additionalTextEdits` for completion items by default (#160)

pull/156/head^2
Mickaël Menu 2 years ago committed by GitHub
parent a4b31b4794
commit 7b92ca06cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -8,6 +8,8 @@ All notable changes to this project will be documented in this file.
* New `--date` flag for `zk new` to set the current date manually. * New `--date` flag for `zk new` to set the current date manually.
* [#144](https://github.com/mickael-menu/zk/issues/144) LSP auto-completion of YAML frontmatter tags. * [#144](https://github.com/mickael-menu/zk/issues/144) LSP auto-completion of YAML frontmatter tags.
* [zk-nvim#26](https://github.com/mickael-menu/zk-nvim/issues/26) The LSP server doesn't use `additionalTextEdits` anymore to remove the trigger characters when completing links.
* You can customize the default behavior with the [`use-additional-text-edits` configuration key](docs/config-lsp.md).
### Fixed ### Fixed

@ -6,11 +6,12 @@ The `[lsp]` [configuration file](config.md) section provides settings to fine-tu
Customize how completion items appear in your editor when auto-completing links with the `[lsp.completion]` sub-section. Customize how completion items appear in your editor when auto-completing links with the `[lsp.completion]` sub-section.
| Setting | Type | Description | | Setting | Type | Description |
|--------------------|------------|----------------------------------------------------------------------------| |-----------------------------|------------|---------------------------------------------------------------------------------------|
| `note-label` | `template` | Label displayed in the completion pop-up for each note | | `note-label` | `template` | Label displayed in the completion pop-up for each note |
| `note-filter-text` | `template` | Text used as a source when filtering the completion pop-up with keystrokes | | `note-filter-text` | `template` | Text used as a source when filtering the completion pop-up with keystrokes |
| `note-detail` | `template` | Additional information about a completion item | | `note-detail` | `template` | Additional information about a completion item |
| `use-additional-text-edits` | `boolean` | Indicates whether `additionalTextEdits` will be used to remove the trigger characters |
Each key accepts a [template](template.md) with the following context: Each key accepts a [template](template.md) with the following context:

@ -22,13 +22,14 @@ import (
// Server holds the state of the Language Server. // Server holds the state of the Language Server.
type Server struct { type Server struct {
server *glspserv.Server server *glspserv.Server
notebooks *core.NotebookStore notebooks *core.NotebookStore
documents *documentStore documents *documentStore
noteContentParser core.NoteContentParser noteContentParser core.NoteContentParser
templateLoader core.TemplateLoader templateLoader core.TemplateLoader
fs core.FileStorage fs core.FileStorage
logger util.Logger logger util.Logger
useAdditionalTextEdits opt.Bool
} }
// ServerOpts holds the options to create a new Server. // ServerOpts holds the options to create a new Server.
@ -60,12 +61,13 @@ func NewServer(opts ServerOpts) *Server {
} }
server := &Server{ server := &Server{
server: glspServer, server: glspServer,
notebooks: opts.Notebooks, notebooks: opts.Notebooks,
documents: newDocumentStore(fs, opts.Logger), documents: newDocumentStore(fs, opts.Logger),
templateLoader: opts.TemplateLoader, templateLoader: opts.TemplateLoader,
fs: fs, fs: fs,
logger: opts.Logger, logger: opts.Logger,
useAdditionalTextEdits: opt.NullBool,
} }
var clientCapabilities protocol.ClientCapabilities var clientCapabilities protocol.ClientCapabilities
@ -79,6 +81,15 @@ func NewServer(opts ServerOpts) *Server {
protocol.SetTraceValue(*params.Trace) protocol.SetTraceValue(*params.Trace)
} }
if params.ClientInfo != nil {
if params.ClientInfo.Name == "Visual Studio Code" {
// Visual Studio Code doesn't seem to support inl
// VSCode doesn't support deleting the trigger characters with
// the main TextEdit. We'll use additional text edits instead.
server.useAdditionalTextEdits = opt.True
}
}
capabilities := handler.CreateServerCapabilities() capabilities := handler.CreateServerCapabilities()
capabilities.HoverProvider = true capabilities.HoverProvider = true
capabilities.DefinitionProvider = true capabilities.DefinitionProvider = true
@ -792,17 +803,19 @@ func (s *Server) newCompletionItem(notebook *core.Notebook, note core.MinimalNot
return item, err return item, err
} }
addTextEdits := []protocol.TextEdit{} if s.useAdditionalTextEditsWithNotebook(notebook) {
addTextEdits := []protocol.TextEdit{}
// Some LSP clients (e.g. VSCode) don't support deleting the trigger // Some LSP clients (e.g. VSCode) don't support deleting the trigger
// characters with the main TextEdit. So let's add an additional // characters with the main TextEdit. So let's add an additional
// TextEdit for that. // TextEdit for that.
addTextEdits = append(addTextEdits, protocol.TextEdit{ addTextEdits = append(addTextEdits, protocol.TextEdit{
NewText: "", NewText: "",
Range: rangeFromPosition(pos, -2, 0), Range: rangeFromPosition(pos, -2, 0),
}) })
item.AdditionalTextEdits = addTextEdits item.AdditionalTextEdits = addTextEdits
}
return item, nil return item, nil
} }
@ -822,6 +835,12 @@ func (s *Server) newTextEditForLink(notebook *core.Notebook, note core.MinimalNo
return nil, err return nil, err
} }
// Overwrite [[ trigger directly if the additional text edits are disabled.
startOffset := 0
if !s.useAdditionalTextEditsWithNotebook(notebook) {
startOffset = -2
}
// Some LSP clients (e.g. VSCode) auto-pair brackets, so we need to // Some LSP clients (e.g. VSCode) auto-pair brackets, so we need to
// remove the closing ]] or )) after the completion. // remove the closing ]] or )) after the completion.
endOffset := 0 endOffset := 0
@ -832,10 +851,17 @@ func (s *Server) newTextEditForLink(notebook *core.Notebook, note core.MinimalNo
return protocol.TextEdit{ return protocol.TextEdit{
NewText: link, NewText: link,
Range: rangeFromPosition(pos, 0, endOffset), Range: rangeFromPosition(pos, startOffset, endOffset),
}, nil }, nil
} }
func (s *Server) useAdditionalTextEditsWithNotebook(nb *core.Notebook) bool {
return nb.Config.LSP.Completion.UseAdditionalTextEdits.
Or(s.useAdditionalTextEdits).
OrBool(false).
Unwrap()
}
func positionInRange(content string, rng protocol.Range, pos protocol.Position) bool { func positionInRange(content string, rng protocol.Range, pos protocol.Position) bool {
start, end := rng.IndexesIn(content) start, end := rng.IndexesIn(content)
i := pos.IndexIn(content) i := pos.IndexIn(content)

@ -161,7 +161,8 @@ type LSPConfig struct {
// LSPCompletionConfig holds the LSP auto-completion configuration. // LSPCompletionConfig holds the LSP auto-completion configuration.
type LSPCompletionConfig struct { type LSPCompletionConfig struct {
Note LSPCompletionTemplates Note LSPCompletionTemplates
UseAdditionalTextEdits opt.Bool
} }
// LSPCompletionConfig holds the LSP completion templates for a particular // LSPCompletionConfig holds the LSP completion templates for a particular
@ -373,6 +374,7 @@ func ParseConfig(content []byte, path string, parentConfig Config) (Config, erro
if lspCompl.NoteDetail != nil { if lspCompl.NoteDetail != nil {
config.LSP.Completion.Note.Detail = opt.NewNotEmptyString(*lspCompl.NoteDetail) config.LSP.Completion.Note.Detail = opt.NewNotEmptyString(*lspCompl.NoteDetail)
} }
config.LSP.Completion.UseAdditionalTextEdits = opt.NewBoolWithPtr(lspCompl.UseAdditionalTextEdits)
// LSP diagnostics // LSP diagnostics
lspDiags := tomlConf.LSP.Diagnostics lspDiags := tomlConf.LSP.Diagnostics
@ -508,9 +510,10 @@ type tomlToolConfig struct {
type tomlLSPConfig struct { type tomlLSPConfig struct {
Completion struct { Completion struct {
NoteLabel *string `toml:"note-label"` NoteLabel *string `toml:"note-label"`
NoteFilterText *string `toml:"note-filter-text"` NoteFilterText *string `toml:"note-filter-text"`
NoteDetail *string `toml:"note-detail"` NoteDetail *string `toml:"note-detail"`
UseAdditionalTextEdits *bool `toml:"use-additional-text-edits"`
} }
Diagnostics struct { Diagnostics struct {
WikiTitle *string `toml:"wiki-title"` WikiTitle *string `toml:"wiki-title"`

@ -126,6 +126,7 @@ func TestParseComplete(t *testing.T) {
paths = [] paths = []
[lsp.completion] [lsp.completion]
use-additional-text-edits = true
note-label = "notelabel" note-label = "notelabel"
note-filter-text = "notefiltertext" note-filter-text = "notefiltertext"
note-detail = "notedetail" note-detail = "notedetail"
@ -236,6 +237,7 @@ func TestParseComplete(t *testing.T) {
FilterText: opt.NewString("notefiltertext"), FilterText: opt.NewString("notefiltertext"),
Detail: opt.NewString("notedetail"), Detail: opt.NewString("notedetail"),
}, },
UseAdditionalTextEdits: opt.True,
}, },
Diagnostics: LSPDiagnosticConfig{ Diagnostics: LSPDiagnosticConfig{
WikiTitle: LSPDiagnosticHint, WikiTitle: LSPDiagnosticHint,

@ -91,3 +91,76 @@ func (s String) String() string {
func (s String) MarshalJSON() ([]byte, error) { func (s String) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%v"`, s)), nil return []byte(fmt.Sprintf(`"%v"`, s)), nil
} }
// Bool holds an optional boolean value.
type Bool struct {
Value *bool
}
// NullBool represents an empty optional Bool.
var NullBool = Bool{nil}
// True represents a true optional Bool.
var True = NewBool(true)
// False represents a false optional Bool.
var False = NewBool(false)
// NewBool creates a new optional Bool with the given value.
func NewBool(value bool) Bool {
return Bool{&value}
}
// NewBool creates a new optional Bool with the given pointer.
// When nil, the Bool is considered null.
func NewBoolWithPtr(value *bool) Bool {
return Bool{value}
}
// IsNull returns whether the optional Bool has no value.
func (s Bool) IsNull() bool {
return s.Value == nil
}
// Or returns the receiver if it is not null, otherwise the given optional
// Bool.
func (s Bool) Or(other Bool) Bool {
if s.IsNull() {
return other
} else {
return s
}
}
// OrBool returns the optional Bool value or the given default boolean if
// it is null.
func (s Bool) OrBool(alt bool) Bool {
if s.IsNull() {
return NewBool(alt)
} else {
return s
}
}
// Unwrap returns the optional Bool value or false if none is set.
func (s Bool) Unwrap() bool {
if s.IsNull() {
return false
} else {
return *s.Value
}
}
func (s Bool) Equal(other Bool) bool {
return s.Value == other.Value ||
(s.Value != nil && other.Value != nil && *s.Value == *other.Value)
}
func (s Bool) MarshalJSON() ([]byte, error) {
value := s.Unwrap()
if value {
return []byte("true"), nil
} else {
return []byte("false"), nil
}
}

Loading…
Cancel
Save