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

Cannot follow links to internal headers #193

Open
1 of 2 tasks
shadowcomer opened this issue Sep 30, 2024 · 5 comments
Open
1 of 2 tasks

Cannot follow links to internal headers #193

shadowcomer opened this issue Sep 30, 2024 · 5 comments

Comments

@shadowcomer
Copy link

Check if applicable

  • I have searched the existing issues (required)
  • I'm willing to help fix the problem and contribute a pull request

Describe the bug

When following a markdown link to an internal header, such as [my link](#some-existing-link), by using <CR> or :lua vim.lsp.buf.definition(), the cursor and the view should move to the header.

Instead, a new buffer is opened with a different note. It seems that it is always the same note, regardless of the link. It also seems that it is the first note ever created.

Requirements.

  1. nvim.
  2. telescope.
  3. zk.
  4. zk-nvim.

Additional details.

Contents of LspLog after :lua vim.lsp.set_log_level("debug"):

[START][2024-09-30 16:25:52] LSP logging initiated
[INFO][2024-09-30 16:25:52] .../vim/lsp/rpc.lua:731	"Starting RPC client"	{  cmd = { "zk", "lsp" },  extra = {}}
[DEBUG][2024-09-30 16:25:52] .../vim/lsp/rpc.lua:286	"rpc.send"	{  id = 1,  jsonrpc = "2.0",  method = "initialize",  params = {    capabilities = {      general = {        positionEncodings = { "utf-16" }      },      textDocument = {        callHierarchy = {          dynamicRegistration = false        },        codeAction = {          codeActionLiteralSupport = {            codeActionKind = {              valueSet = { "", "quickfix", "refactor", "refactor.extract", "refactor.inline", "refactor.rewrite", "source", "source.organizeImports" }            }          },          dataSupport = true,          dynamicRegistration = true,          isPreferredSupport = true,          resolveSupport = {            properties = { "edit" }          }        },        completion = {          completionItem = {            commitCharactersSupport = false,            deprecatedSupport = false,            documentationFormat = { "markdown", "plaintext" },            preselectSupport = false,            snippetSupport = false          },          completionItemKind = {            valueSet = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 }          },          completionList = {            itemDefaults = { "editRange", "insertTextFormat", "insertTextMode", "data" }          },          contextSupport = false,          dynamicRegistration = false        },        declaration = {          linkSupport = true        },        definition = {          dynamicRegistration = true,          linkSupport = true        },        diagnostic = {          dynamicRegistration = false        },        documentHighlight = {          dynamicRegistration = false        },        documentSymbol = {          dynamicRegistration = false,          hierarchicalDocumentSymbolSupport = true,          symbolKind = {            valueSet = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 }          }        },        formatting = {          dynamicRegistration = true        },        hover = {          contentFormat = { "markdown", "plaintext" },          dynamicRegistration = true        },        implementation = {          linkSupport = true        },        inlayHint = {          dynamicRegistration = true,          resolveSupport = {            properties = { "textEdits", "tooltip", "location", "command" }          }        },        publishDiagnostics = {          dataSupport = true,          relatedInformation = true,          tagSupport = {            valueSet = { 1, 2 }          }        },        rangeFormatting = {          dynamicRegistration = true        },        references = {          dynamicRegistration = false        },        rename = {          dynamicRegistration = true,          prepareSupport = true        },        semanticTokens = {          augmentsSyntaxTokens = true,          dynamicRegistration = false,          formats = { "relative" },          multilineTokenSupport = false,          overlappingTokenSupport = true,          requests = {            full = {              delta = true            },            range = false          },          serverCancelSupport = false,          tokenModifiers = { "declaration", "definition", "readonly", "static", "deprecated", "abstract", "async", "modification", "documentation", "defaultLibrary" },          tokenTypes = { "namespace", "type", "class", "enum", "interface", "struct", "typeParameter", "parameter", "variable", "property", "enumMember", "event", "function", "method", "macro", "keyword", "modifier", "comment", "string", "number", "regexp", "operator", "decorator" }        },        signatureHelp = {          dynamicRegistration = false,          signatureInformation = {            activeParameterSupport = true,            documentationFormat = { "markdown", "plaintext" },            parameterInformation = {              labelOffsetSupport = true            }          }        },        synchronization = {          didSave = true,          dynamicRegistration = false,          willSave = true,          willSaveWaitUntil = true        },        typeDefinition = {          linkSupport = true        }      },      window = {        showDocument = {          support = true        },        showMessage = {          messageActionItem = {            additionalPropertiesSupport = false          }        },        workDoneProgress = true      },      workspace = {        applyEdit = true,        configuration = true,        didChangeConfiguration = {          dynamicRegistration = false        },        didChangeWatchedFiles = {          dynamicRegistration = false,          relativePatternSupport = true        },        inlayHint = {          refreshSupport = true        },        semanticTokens = {          refreshSupport = true        },        symbol = {          dynamicRegistration = false,          symbolKind = {            valueSet = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 }          }        },        workspaceEdit = {          resourceOperations = { "rename", "create", "delete" }        },        workspaceFolders = true      }    },    clientInfo = {      name = "Neovim",      version = "0.10.1"    },    processId = 20754,    rootPath = vim.NIL,    rootUri = vim.NIL,    trace = "off",    workDoneToken = "1",    workspaceFolders = vim.NIL  }}
[DEBUG][2024-09-30 16:25:52] ...m/lsp/client.lua:678	"LSP[zk]"	"client.request"	1	"workspace/executeCommand"	{  arguments = { "/home/jlambda/projects/reproduce-zkbug", {      select = { "title", "absPath", "path" },      sort = { "modified" }    } },  command = "zk.list"}	<function 1>	1
[DEBUG][2024-09-30 16:25:52] .../vim/lsp/rpc.lua:286	"rpc.send"	{  id = 2,  jsonrpc = "2.0",  method = "workspace/executeCommand",  params = {    arguments = { "/home/jlambda/projects/reproduce-zkbug", {        select = { "title", "absPath", "path" },        sort = { "modified" }      } },    command = "zk.list"  }}
[DEBUG][2024-09-30 16:25:52] .../vim/lsp/rpc.lua:408	"rpc.receive"	{  id = 1,  jsonrpc = "2.0",  result = {    capabilities = {      codeActionProvider = true,      completionProvider = {        resolveProvider = true,        triggerCharacters = { "(", "[", "#", ":" }      },      definitionProvider = true,      documentLinkProvider = {        resolveProvider = true      },      executeCommandProvider = {        commands = { "zk.index", "zk.new", "zk.list", "zk.tag.list" }      },      hoverProvider = true,      referencesProvider = vim.empty_dict(),      textDocumentSync = {        change = 2,        openClose = true,        save = true      }    },    serverInfo = {      name = "zk",      version = "0.14.1"    }  }}
[DEBUG][2024-09-30 16:25:52] .../vim/lsp/rpc.lua:286	"rpc.send"	{  jsonrpc = "2.0",  method = "initialized",  params = vim.empty_dict()}
[INFO][2024-09-30 16:25:52] ...m/lsp/client.lua:620	"LSP[zk]"	"server_capabilities"	{  server_capabilities = {    codeActionProvider = true,    completionProvider = {      resolveProvider = true,      triggerCharacters = { "(", "[", "#", ":" }    },    definitionProvider = true,    documentLinkProvider = {      resolveProvider = true    },    executeCommandProvider = {      commands = { "zk.index", "zk.new", "zk.list", "zk.tag.list" }    },    hoverProvider = true,    referencesProvider = vim.empty_dict(),    textDocumentSync = {      change = 2,      openClose = true,      save = true    }  }}
[DEBUG][2024-09-30 16:25:52] .../vim/lsp/rpc.lua:408	"rpc.receive"	{  id = 2,  jsonrpc = "2.0",  result = { {      absPath = "/home/jlambda/projects/reproduce-zkbug/8mem.md",      path = "8mem.md",      title = "Second note"    }, {      absPath = "/home/jlambda/projects/reproduce-zkbug/wruv.md",      path = "wruv.md",      title = "First note"    } }}
[DEBUG][2024-09-30 16:25:52] .../vim/lsp/rpc.lua:286	"rpc.send"	{  jsonrpc = "2.0",  method = "textDocument/didOpen",  params = {    textDocument = {      languageId = "markdown",      text = "# Second note\n\nSecond content.\n\nMy [faulty link](#some-header).\n\n## Some header\n",      uri = "file:///home/jlambda/projects/reproduce-zkbug/8mem.md",      version = 0    }  }}
[DEBUG][2024-09-30 16:25:52] .../vim/lsp/rpc.lua:408	"rpc.receive"	{  jsonrpc = "2.0",  method = "textDocument/publishDiagnostics",  params = {    diagnostics = {},    uri = "file:///home/jlambda/projects/reproduce-zkbug/8mem.md"  }}
[DEBUG][2024-09-30 16:25:58] ...m/lsp/client.lua:678	"LSP[zk]"	"client.request"	1	"textDocument/definition"	{  position = {    character = 18,    line = 4  },  textDocument = {    uri = "file:///home/jlambda/projects/reproduce-zkbug/8mem.md"  }}	<function 1>	1
[DEBUG][2024-09-30 16:25:58] .../vim/lsp/rpc.lua:286	"rpc.send"	{  id = 3,  jsonrpc = "2.0",  method = "textDocument/definition",  params = {    position = {      character = 18,      line = 4    },    textDocument = {      uri = "file:///home/jlambda/projects/reproduce-zkbug/8mem.md"    }  }}
[DEBUG][2024-09-30 16:25:58] .../vim/lsp/rpc.lua:408	"rpc.receive"	{  id = 3,  jsonrpc = "2.0",  result = {    range = {      ["end"] = {        character = 0,        line = 0      },      start = {        character = 0,        line = 0      }    },    uri = "file:///home/jlambda/projects/reproduce-zkbug/wruv.md"  }}
[DEBUG][2024-09-30 16:25:58] .../vim/lsp/rpc.lua:286	"rpc.send"	{  jsonrpc = "2.0",  method = "textDocument/didOpen",  params = {    textDocument = {      languageId = "markdown",      text = "# First note\n\nSome content.\n",      uri = "file:///home/jlambda/projects/reproduce-zkbug/wruv.md",      version = 0    }  }}
[DEBUG][2024-09-30 16:25:58] .../vim/lsp/rpc.lua:408	"rpc.receive"	{  jsonrpc = "2.0",  method = "textDocument/publishDiagnostics",  params = {    diagnostics = {},    uri = "file:///home/jlambda/projects/reproduce-zkbug/wruv.md"  }}

How to reproduce?

  1. Create a new notebook with zk init. Configure links for markdown.
  2. Open neovim with nvim ..
  3. Create a note with any content.
# First note

Some content.
  1. Create a second note such as:
# Second note

Second content.

My [faulty link](#some-header).

## Some header
  1. Place the cursor on any part of the faulty link.
  2. Press <CR> or execute the command :lua vim.lsp.buf.definition().
  3. The cursor should move to ## Some header, but instead a buffer is opened with the file that contains # First note.

zk configuration

# zk configuration file
#
# Uncomment the properties you want to customize.

# NOTE SETTINGS
#
# Defines the default options used when generating new notes.
[note]

# Language used when writing notes.
# This is used to generate slugs or with date formats.
#language = "en"

# The default title used for new note, if no `--title` flag is provided.
#default-title = "Untitled"

# Template used to generate a note's filename, without extension.
#filename = "{{id}}"

# The file extension used for the notes.
#extension = "md"

# Template used to generate a note's content.
# If not an absolute path, it is relative to .zk/templates/
template = "default.md"

# Path globs ignored while indexing existing notes.
#ignore = [
#    "drafts/*",
#	"log.md"
#]

# Configure random ID generation.

# The charset used for random IDs. You can use:
#   * letters: only letters from a to z.
#   * numbers: 0 to 9
#   * alphanum: letters + numbers
#   * hex: hexadecimal, from a to f and 0 to 9
#   * custom string: will use any character from the provided value
#id-charset = "alphanum"

# Length of the generated IDs.
#id-length = 4

# Letter case for the random IDs, among lower, upper or mixed.
#id-case = "lower"


# EXTRA VARIABLES
#
# A dictionary of variables you can use for any custom values when generating
# new notes. They are accessible in templates with {{extra.<key>}}
[extra]

#key = "value"


# GROUP OVERRIDES
#
# You can override global settings from [note] and [extra] for a particular
# group of notes by declaring a [group."<name>"] section.
#
# Specify the list of directories which will automatically belong to the group
# with the optional `paths` property.
#
# Omitting `paths` is equivalent to providing a single path equal to the name of
# the group. This can be useful to quickly declare a group by the name of the
# directory it applies to.

#[group."<NAME>"]
#paths = ["<DIR1>", "<DIR2>"]
#[group."<NAME>".note]
#filename = "{{format-date now}}"
#[group."<NAME>".extra]
#key = "value"


# MARKDOWN SETTINGS
[format.markdown]

# Format used to generate links between notes.
# Either "wiki", "markdown" or a custom template. Default is "markdown".
#link-format = "wiki"
# Indicates whether a link's path will be percent-encoded.
# Defaults to true for "markdown" format and false for "wiki" format.
#link-encode-path = true
# Indicates whether a link's path file extension will be removed.
# Defaults to true.
#link-drop-extension = true

# Enable support for #hashtags.
hashtags = false
# Enable support for :colon:separated:tags:.
colon-tags = true
# Enable support for Bear's #multi-word tags#
# Hashtags must be enabled for multi-word tags to work.
multiword-tags = false


# EXTERNAL TOOLS
[tool]

# Default editor used to open notes. When not set, the EDITOR or VISUAL
# environment variables are used.
#editor = "vim"

# Pager used to scroll through long output. If you want to disable paging
# altogether, set it to an empty string "".
#pager = "less -FIRX"

# Command used to preview a note during interactive fzf mode.
# Set it to an empty string "" to disable preview.

# bat is a great tool to render Markdown document with syntax highlighting.
#https://github.com/sharkdp/bat
#fzf-preview = "bat -p --color always {-1}"


# LSP
#
#   Configure basic editor integration for LSP-compatible editors.
#   See https://github.com/zk-org/zk/blob/main/docs/editors-integration.md
#
[lsp]

[lsp.diagnostics]
# Each diagnostic can have for value: none, hint, info, warning, error

# Report titles of wiki-links as hints.
#wiki-title = "hint"
# Warn for dead links between notes.
dead-link = "error"

[lsp.completion]
# Customize the completion pop-up of your LSP client.

# Show the note title in the completion pop-up, or fallback on its path if empty.
#note-label = ""
# Filter out the completion pop-up using the note title or its path.
#note-filter-text = " "
# Show the note filename without extension as detail.
#note-detail = ""


# NAMED FILTERS
#
#    A named filter is a set of note filtering options used frequently together.
#
[filter]

# Matches the notes created the last two weeks. For example:
#    $ zk list recents --limit 15
#    $ zk edit recents --interactive
#recents = "--sort created- --created-after 'last two weeks'"


# COMMAND ALIASES
#
#   Aliases are user commands called with `zk <alias> [<flags>] [<args>]`.
#
#   The alias will be executed with `$SHELL -c`, please refer to your shell's
#   man page to see the available syntax. In most shells:
#     * $@ can be used to expand all the provided flags and arguments
#     * you can pipe commands together with the usual | character
#
[alias]
# Here are a few aliases to get you started.

# Shortcut to a command.
#ls = "zk list $@"

# Default flags for an existing command.
#list = "zk list --quiet $@"

# Edit the last modified note.
#editlast = "zk edit --limit 1 --sort modified- $@"

# Edit the notes selected interactively among the notes created the last two weeks.
# This alias doesn't take any argument, so we don't use $@.
#recent = "zk edit --sort created- --created-after 'last two weeks' --interactive"

# Print paths separated with colons for the notes found with the given
# arguments. This can be useful to expand a complex search query into a flag
# taking only paths. For example:
#   zk list --link-to "`zk path -m potatoe`"
#path = "zk list --quiet --format {{path}} --delimiter , $@"

# Show a random note.
#lucky = "zk list --quiet --format full --sort random --limit 1"

# Returns the Git history for the notes found with the given arguments.
# Note the use of a pipe and the location of $@.
#hist = "zk list --format path --delimiter0 --quiet $@ | xargs -t -0 git log --patch --"

# Edit this configuration file.
#conf = '$EDITOR "$ZK_NOTEBOOK_DIR/.zk/config.toml"'

Neovim configuration

vim.g.mapleader = " "

require("zk").setup({
      picker = "telescope",
      lsp = {
        config = {
          cmd = { "zk", "lsp" },
          name = "zk",
        },

        auto_attach = {
          enabled = true,
          filetypes = { "markdown" },
        },
      },
    })

local opts = { noremap=true, silent=false }

vim.api.nvim_set_keymap("n", "<leader>zn", "<Cmd>ZkNew { title = vim.fn.input('Title: ') }<CR>", opts)

vim.api.nvim_set_keymap("n", "<leader>zo", "<Cmd>ZkNotes { sort = { 'modified' } }<CR>", opts)
vim.api.nvim_set_keymap("n", "<leader>zf", "<Cmd>ZkNotes { sort = { 'modified' }, match = { vim.fn.input('Search: ') } }<CR>", opts)

vim.api.nvim_set_keymap("n", "<leader>zt", "<Cmd>ZkTags<CR>", opts)
vim.api.nvim_set_keymap("v", "<leader>zf", ":'<,'>ZkMatch<CR>", opts)

Environment

This is a NixOS distribution:

zk 0.14.1
system: Linux 6.6.52 x86_64 GNU/Linux
NVIM v0.10.1
Build type: Release
LuaJIT 2.1.1713773202

   system vimrc file: "$VIM/sysinit.vim"
  fall-back for $VIM: "
/nix/store/0w1cknj80ya23y468qmq911pwds5by0b-neovim-unwrapped-0.10.1/share/nvim
"
@tjex
Copy link
Member

tjex commented Sep 30, 2024

I can confirm this happens for me too. My initial thought is whether we even support go to definition for headings. But regardless, we shouldn't return anything if we don't.

@shadowcomer
Copy link
Author

I think it's important to support this. Markdown links are supported, but apparently only partially. This means that for internal note navigation a user has to prepare an alternative (find it or create it, install, configure, ...). This is complicated by the fact that you need to either:

  1. Have a separate binding for navigating this particular kind of links.
  2. Have a way to integrate between both link-following implementations, such as test-fail-fallback, or a dispatcher.

Thus, pushing it on the user would be an inconvenience for everyone. Furthermore, links are already being handled somewhere. Also, following the behavior of other LSP implementations, for example for programming languages, the user would expect the cursor to move to the definition of a function, even if it were in the same document, instead of doing nothing or opening an unrelated document.

All in all, I agree that in the worst case nothing should happen, maybe log a warning that it is unsupported.

@tjex
Copy link
Member

tjex commented Oct 1, 2024

Our lsp was designed to offer extra functionality that was either zk specific, or that other lsps don't. But not to implement regular markdown lsp functionality. See here.

It's expected that users are using another lsp for markdown, as zk lsp only launches when editing markdown documents within a zk notebook anyway. So it would never be an lsp to rely on for markdown editing in general.

So it's more a case that we should negate this behavior, rather than support it.

@shadowcomer
Copy link
Author

I see, thanks for the explanation.

I think what I have most issue with is that zk's LSP implementation covers following links, but not quite all of the supported links of the underlying document format (in this case, markdown).

If that's the chosen direction, so be it. I'll update my configuration to use a full markdown LSP implementation to fulfill that functionality instead.

That the definition function of zk's LSP opens an arbitrary note when encountering this kind of link would still be a problem, so I think there's value for this issue to remain open for that. The title can be updated to be more accurate to the problem.

Thank you for your time!

@tjex
Copy link
Member

tjex commented Oct 5, 2024

Yeah, it's a tricky balance to strike. As the lsp should be usable for the base requirements of writing a markdown wiki. But we're also not out to build and maintain a fully fledged LSP.

I mean it was one person, Mickael who was building and managing zk and the lsp himself for quite a while. Which still blows my mind how much he got done...

Now it's mainly me holding fort, and the reason I say that is that I'm still fairly new to the code bases, and I've been wrong about calling out features as not existing before when they actually do! So keep an eye on your notifications, because as I get around to looking into the lsp side of things (for which there are some other open issues) I'll double check on this.

Maybe it is implemented, but it's buggy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants