Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable additionalTextEdits for completion items by default #160

Merged
merged 1 commit into from
Jan 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
}
}