Skip to content

Commit

Permalink
Add zk.link LSP command (#284)
Browse files Browse the repository at this point in the history
  • Loading branch information
psanker authored Jan 18, 2023
1 parent 150c82f commit f7d4db0
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 32 deletions.
8 changes: 5 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ All notable changes to this project will be documented in this file.

### Added

* LSP: `zk.new` now returns the created note's content in its output (`content`), and has two new options:
* `dryRun` will prevent `zk.new` from creating the note on the file system.
* `insertContentAtLocation` can be used to insert the created note's content into an arbitrary location.
* LSP:
* `zk.new` now returns the created note's content in its output (`content`), and has two new options:
* `dryRun` will prevent `zk.new` from creating the note on the file system.
* `insertContentAtLocation` can be used to insert the created note's content into an arbitrary location.
* A new `zk.link` command to insert a link to a given note (contributed by [@psanker](https://github.com/mickael-menu/zk/pull/284)).

## 0.12.0

Expand Down
12 changes: 12 additions & 0 deletions docs/editors-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,18 @@ This LSP command calls `zk new` to create a new note. It can be useful to quickl
* `path` containing the absolute path to the created note.
* `content` containing the raw content of the created note.

#### `zk.link`

This LSP command allows editors to tap into the note linking mechanism. It takes three arguments:

1. A `path` to any file in the notebook that will be linked to
2. An LSP `location` object that points to where the link will be inserted
3. An optional title of the link. If `title` is not provided, the title of the note will be inserted instead

`zk.link` returns a JSON object with the path to the linked note, if the linking was successful.

**Note**: This command is _not_ exposed in the command line. This command is targeted at editor / plugin authors to extend zk functionality.

#### `zk.list`

This LSP command calls `zk list` to search a notebook. It takes two arguments:
Expand Down
64 changes: 64 additions & 0 deletions internal/adapter/lsp/cmd_link.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package lsp

import (
"fmt"
"path/filepath"

"github.com/mickael-menu/zk/internal/core"
"github.com/mickael-menu/zk/internal/util/errors"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)

const cmdLink = "zk.link"

type cmdLinkOpts struct {
Path *string `json:"path"`
Location *protocol.Location `json:"location"`
Title *string `json:"title"`
}

func executeCommandLink(notebook *core.Notebook, documents *documentStore, context *glsp.Context, args []interface{}) (interface{}, error) {
var opts cmdLinkOpts

if len(args) > 1 {
arg, ok := args[1].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("%s expects a dictionary of options as second argument, got: %v", cmdLink, args[1])
}
err := unmarshalJSON(arg, &opts)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse %s args, got: %v", cmdLink, arg)
}
}

if opts.Path == nil {
return nil, errors.New("'path' not provided")
}

note, err := notebook.FindByHref(*opts.Path, false)

if err != nil {
return nil, err
}

if note == nil {
return nil, errors.New("Requested note to link to not found!")
}

info := &linkInfo{
note: note,
location: opts.Location,
title: opts.Title,
}

err = linkNote(notebook, documents, context, info)

if err != nil {
return nil, err
}

return map[string]interface{}{
"path": filepath.Join(notebook.Path, note.Path),
}, nil
}
39 changes: 10 additions & 29 deletions internal/adapter/lsp/cmd_new.go
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -83,37 +83,18 @@ func executeCommandNew(notebook *core.Notebook, documents *documentStore, contex
}

if !opts.DryRun && opts.InsertLinkAtLocation != nil {
doc, ok := documents.Get(opts.InsertLinkAtLocation.URI)
if !ok {
return nil, fmt.Errorf("can't insert link in %s", opts.InsertLinkAtLocation.URI)
}
linkFormatter, err := notebook.NewLinkFormatter()
if err != nil {
return nil, err
}
minNote := note.AsMinimalNote()

path := core.NotebookPath{
Path: note.Path,
BasePath: notebook.Path,
WorkingDir: filepath.Dir(doc.Path),
}
linkFormatterContext, err := core.NewLinkFormatterContext(path, note.Title, note.Metadata)
if err != nil {
return nil, err
}
info := &linkInfo{
note: &minNote,
location: opts.InsertLinkAtLocation,
title: &opts.Title,
}
err := linkNote(notebook, documents, context, info)

link, err := linkFormatter(linkFormatterContext)
if err != nil {
return nil, err
}

go context.Call(protocol.ServerWorkspaceApplyEdit, protocol.ApplyWorkspaceEditParams{
Edit: protocol.WorkspaceEdit{
Changes: map[string][]protocol.TextEdit{
opts.InsertLinkAtLocation.URI: {{Range: opts.InsertLinkAtLocation.Range, NewText: link}},
},
},
}, nil)
if err != nil {
return nil, err
}
}

absPath := filepath.Join(notebook.Path, note.Path)
Expand Down
7 changes: 7 additions & 0 deletions internal/adapter/lsp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,13 @@ func NewServer(opts ServerOpts) *Server {
}
return executeCommandNew(nb, server.documents, context, params.Arguments)

case cmdLink:
nb, err := openNotebook()
if err != nil {
return nil, err
}
return executeCommandLink(nb, server.documents, context, params.Arguments)

case cmdList:
nb, err := openNotebook()
if err != nil {
Expand Down
60 changes: 60 additions & 0 deletions internal/adapter/lsp/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ package lsp
import (
"fmt"
"net/url"
"path/filepath"
"runtime"
"strings"

"github.com/mickael-menu/zk/internal/core"
"github.com/mickael-menu/zk/internal/util/errors"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)

func pathToURI(path string) string {
Expand Down Expand Up @@ -56,3 +60,59 @@ func (b *jsonBoolean) UnmarshalJSON(data []byte) error {
}
return nil
}

type linkInfo struct {
note *core.MinimalNote
location *protocol.Location
title *string
}

func linkNote(notebook *core.Notebook, documents *documentStore, context *glsp.Context, info *linkInfo) error {
if info.location == nil {
return errors.New("'location' not provided")
}

// Get current document to edit
doc, ok := documents.Get(info.location.URI)
if !ok {
return fmt.Errorf("Cannot insert link in '%s'", info.location.URI)
}

formatter, err := notebook.NewLinkFormatter()
if err != nil {
return err
}

path := core.NotebookPath{
Path: info.note.Path,
BasePath: notebook.Path,
WorkingDir: filepath.Dir(doc.Path),
}

var title *string
title = info.title

if title == nil {
title = &info.note.Title
}

formatterContext, err := core.NewLinkFormatterContext(path, *title, info.note.Metadata)
if err != nil {
return err
}

link, err := formatter(formatterContext)
if err != nil {
return err
}

go context.Call(protocol.ServerWorkspaceApplyEdit, protocol.ApplyWorkspaceEditParams{
Edit: protocol.WorkspaceEdit{
Changes: map[string][]protocol.TextEdit{
info.location.URI: {{Range: info.location.Range, NewText: link}},
},
},
}, nil)

return nil
}

0 comments on commit f7d4db0

Please sign in to comment.