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.
* [#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

@ -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.
| Setting | Type | Description |
|--------------------|------------|----------------------------------------------------------------------------|
| `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-detail` | `template` | Additional information about a completion item |
| Setting | Type | Description |
|-----------------------------|------------|---------------------------------------------------------------------------------------|
| `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-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:

@ -22,13 +22,14 @@ import (
// Server holds the state of the Language Server.
type Server struct {
server *glspserv.Server
notebooks *core.NotebookStore
documents *documentStore
noteContentParser core.NoteContentParser
templateLoader core.TemplateLoader
fs core.FileStorage
logger util.Logger
server *glspserv.Server
notebooks *core.NotebookStore
documents *documentStore
noteContentParser core.NoteContentParser
templateLoader core.TemplateLoader
fs core.FileStorage
logger util.Logger
useAdditionalTextEdits opt.Bool
}
// ServerOpts holds the options to create a new Server.
@ -60,12 +61,13 @@ func NewServer(opts ServerOpts) *Server {
}
server := &Server{
server: glspServer,
notebooks: opts.Notebooks,
documents: newDocumentStore(fs, opts.Logger),
templateLoader: opts.TemplateLoader,
fs: fs,
logger: opts.Logger,
server: glspServer,
notebooks: opts.Notebooks,
documents: newDocumentStore(fs, opts.Logger),
templateLoader: opts.TemplateLoader,
fs: fs,
logger: opts.Logger,
useAdditionalTextEdits: opt.NullBool,
}
var clientCapabilities protocol.ClientCapabilities
@ -79,6 +81,15 @@ func NewServer(opts ServerOpts) *Server {
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.HoverProvider = true
capabilities.DefinitionProvider = true
@ -792,17 +803,19 @@ func (s *Server) newCompletionItem(notebook *core.Notebook, note core.MinimalNot
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
// characters with the main TextEdit. So let's add an additional
// TextEdit for that.
addTextEdits = append(addTextEdits, protocol.TextEdit{
NewText: "",
Range: rangeFromPosition(pos, -2, 0),
})
// Some LSP clients (e.g. VSCode) don't support deleting the trigger
// characters with the main TextEdit. So let's add an additional
// TextEdit for that.
addTextEdits = append(addTextEdits, protocol.TextEdit{
NewText: "",
Range: rangeFromPosition(pos, -2, 0),
})
item.AdditionalTextEdits = addTextEdits
item.AdditionalTextEdits = addTextEdits
}
return item, nil
}
@ -822,6 +835,12 @@ func (s *Server) newTextEditForLink(notebook *core.Notebook, note core.MinimalNo
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
// remove the closing ]] or )) after the completion.
endOffset := 0
@ -832,10 +851,17 @@ func (s *Server) newTextEditForLink(notebook *core.Notebook, note core.MinimalNo
return protocol.TextEdit{
NewText: link,
Range: rangeFromPosition(pos, 0, endOffset),
Range: rangeFromPosition(pos, startOffset, endOffset),
}, 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 {
start, end := rng.IndexesIn(content)
i := pos.IndexIn(content)

@ -161,7 +161,8 @@ type LSPConfig struct {
// LSPCompletionConfig holds the LSP auto-completion configuration.
type LSPCompletionConfig struct {
Note LSPCompletionTemplates
Note LSPCompletionTemplates
UseAdditionalTextEdits opt.Bool
}
// 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 {
config.LSP.Completion.Note.Detail = opt.NewNotEmptyString(*lspCompl.NoteDetail)
}
config.LSP.Completion.UseAdditionalTextEdits = opt.NewBoolWithPtr(lspCompl.UseAdditionalTextEdits)
// LSP diagnostics
lspDiags := tomlConf.LSP.Diagnostics
@ -508,9 +510,10 @@ type tomlToolConfig struct {
type tomlLSPConfig struct {
Completion struct {
NoteLabel *string `toml:"note-label"`
NoteFilterText *string `toml:"note-filter-text"`
NoteDetail *string `toml:"note-detail"`
NoteLabel *string `toml:"note-label"`
NoteFilterText *string `toml:"note-filter-text"`
NoteDetail *string `toml:"note-detail"`
UseAdditionalTextEdits *bool `toml:"use-additional-text-edits"`
}
Diagnostics struct {
WikiTitle *string `toml:"wiki-title"`

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

@ -91,3 +91,76 @@ func (s String) String() string {
func (s String) MarshalJSON() ([]byte, error) {
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