Skip to content

Commit

Permalink
Disable additionalTextEdits for completion items by default (#160)
Browse files Browse the repository at this point in the history
  • Loading branch information
mickael-menu committed Jan 23, 2022
1 parent a4b31b4 commit 7b92ca0
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 32 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
11 changes: 6 additions & 5 deletions docs/config-lsp.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
72 changes: 49 additions & 23 deletions internal/adapter/lsp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand All @@ -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)
Expand Down
11 changes: 7 additions & 4 deletions internal/core/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"`
Expand Down
2 changes: 2 additions & 0 deletions internal/core/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -236,6 +237,7 @@ func TestParseComplete(t *testing.T) {
FilterText: opt.NewString("notefiltertext"),
Detail: opt.NewString("notedetail"),
},
UseAdditionalTextEdits: opt.True,
},
Diagnostics: LSPDiagnosticConfig{
WikiTitle: LSPDiagnosticHint,
Expand Down
73 changes: 73 additions & 0 deletions internal/util/opt/opt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

0 comments on commit 7b92ca0

Please sign in to comment.