SidOfc/mkdx

Insert link completion deletes line

AckslD opened this issue ยท 17 comments

OS type:

  • Unix
  • Windows
  • Other ([SPECIFY])

Vim:

  • vim
  • neovim
  • Other ([SPECIFY])

Vim version:

NVIM v0.7.0-dev+907-gb781a6e50

Reproduce steps:

Example:

Typing an anchor, for example [section](#section) inline deletes the line, see following gif:

mkdx

Could this be a compatibility issue with cmp?

Expected:

The line not to be deleted.

Actual:

The line is deleted and replaced with the anchor.

Hi @AckslD, thanks for opening an issue :)

I'd like to verify that this is definitely not a compatibility issue before starting to look into it. Can you disable all plugins and only load mkdx and then check if the issue still occurs?

If it still happens even with all plugins disabled then it could be some other configuration which is interfering. In this case I would like to see the smallest vimrc possible which still causes this issue so that I can test it for myself.

If the issue indeed disappears when plugins are disabled then it is likely some plugin compatibility issue. In this case your best bet would be to see if you can disable the conflicting plugin for markdown files.

@SidOfc Thanks for your response! I tried first to just disable cmp at least but the issue is still there. I'm trying to reproduce with a minimal config. I'm trying to use the following:

vim.cmd [[set runtimepath=$VIMRUNTIME]]
vim.cmd [[set packpath=/tmp/nvim/site]]
local package_root = '/tmp/nvim/site/pack'
local install_path = package_root .. '/packer/start/packer.nvim'
local function load_plugins()
  require('packer').startup {
    {
      'wbthomason/packer.nvim',
      {
        'SidOfc/mkdx',
        setup = function()
          vim.g["mkdx#settings"] = {
            highlight = {enable = 1},
            enter = {shift = 1},
            links = {
              fragment = {complete = 1},
              external = {enable = 1},
            },
            toc = {text = 'Table of Contents', update_on_write = 1},
            fold = {enable = 1},
          }
        end
      },
    },
    config = {
      package_root = package_root,
      compile_path = install_path .. '/plugin/packer_compiled.lua',
      display = { non_interactive = true },
    },
  }
end
_G.load_config = function()
end
if vim.fn.isdirectory(install_path) == 0 then
  print("Installing dependencies.")
  vim.fn.system { 'git', 'clone', '--depth=1', 'https://github.com/wbthomason/packer.nvim', install_path }
end
load_plugins()
require('packer').sync()
vim.cmd [[autocmd User PackerComplete ++once echo "Ready!" | lua load_config()]]

but when I run this and type # in the link I get the error:

Error detected while processing function mkdx#Complete[2]..61[15]..56[9]..57[5]..44:                                                                                                 
line    5:
E716: Key not present in Dictionary: "tokens.header, '') | endif"

Any idea why?

In my own init.lua I do not use the setup function of packer.nvim. Instead I have a small "plugins" section in which I tell packer which plugins I want. Further down in my configuration, separate sections can be found for each plugin.

Feel free to take a look at how I configured mkdx:

Additionally, I had nvim-cmp in there for roughly two months, at the time of writing this comment it's only been 4 days ago since I decided to remove it from my configuration:

During my time with nvim-cmp I did not notice this issue and I did indeed use the same feature you are having issues with (fragment link autocomplete).

My guess is that initializing the configuration in the setup function of packer.nvim is interfering with the "natural loading order" I had in mind when building mkdx.

Hopefully this provides some useful information which you can use to resolve this issue. I'd like to ask you one last time to try out some of the methods I am using in my own configuration. If you still experience issues after that I'll try to produce a working configuration based on your minimal configuration example.

Thank you for the feedback so far and keep me posted ๐Ÿ‘

Thanks @SidOfc, if I put the mkdx settings directly in the load_plugins (not in setup) of the minimal config above as you do in your config the error indeed goes away and the completion works as expected. So I started including some more things from my config and finally managed to track down the issue to my setting for :set completeopt=menu,longest. See the following updated minimal example which at least for me reproduces the issue:

vim.cmd [[set runtimepath=$VIMRUNTIME]]
vim.cmd [[set packpath=/tmp/nvim/site]]
local package_root = '/tmp/nvim/site/pack'
local install_path = package_root .. '/packer/start/packer.nvim'
local function load_plugins()
  vim.opt.completeopt = {'menu', 'longest'}

  require('packer').startup {
    {
      'wbthomason/packer.nvim',
      'SidOfc/mkdx',
    },
    config = {
      package_root = package_root,
      compile_path = install_path .. '/plugin/packer_compiled.lua',
      display = { non_interactive = true },
    },
  }

  vim.g["mkdx#settings"] = {
    highlight = {enable = 1},
    enter = {shift = 1},
    links = {
      fragment = {complete = 1},
      external = {enable = 1},
    },
    toc = {text = 'Table of Contents', update_on_write = 1},
    fold = {enable = 1},
  }
end
_G.load_config = function()
end
if vim.fn.isdirectory(install_path) == 0 then
  print("Installing dependencies.")
  vim.fn.system { 'git', 'clone', '--depth=1', 'https://github.com/wbthomason/packer.nvim', install_path }
end
load_plugins()
require('packer').sync()
vim.cmd [[autocmd User PackerComplete ++once echo "Ready!" | lua load_config()]]

which I run as nvim -u minimal.lua after first removing ~/.config/nvim/plugin/packer_compiled.lua and ~/.local/share/nvim/site/pack/packer.

Let me know if you can also reproduce it.

Hello again @AckslD, good to hear that configuration is indeed working now. As for the next issue, thank you for supplying the reproduction steps. I did indeed manage to reproduce it.

Offending code in mkdx/markdown.vim.

The above code "appends" values to completeopt as to not overwrite user preferences. So far I've had few issues with it. I'll push a commit with a new g:mkdx#settings.links.fragment.completeopt setting to disable this behaviour as soon as I can ๐Ÿ‘

After that we'll just need to check if it works, thank you for all your testing, feedback and patience in the meantime. It is much appreciated!

Sounds good @SidOfc, thanks for a great plugin :)

@AckslD cheers, I try :)

I noticed while playing around that this seems to be caused by the menu,longest configuration. Even when I locally disabled the code which appends to completeopt it still broke. For this reason, I decided to overwrite completeopt in markdown buffers instead of just appending to it.

You should be able to update and verify that the current line is no longer removed now. Despite this patch fixing the issue I do wonder if this is also an acceptable fix for you, or do you really need to configure completeopt to something other than menuone,noinsert in markdown files?

I would really prefer to keep my setting for completeopt since this affects also completion of other things and I like to have the completion insert the match directly if there is only one for example.

I realise now that I misunderstood your earlier comment, I think a better option (although I don't know exactly how to do this) would be to not set completeopt at all but rather try to find out why the current way is incompatible with longest.

I looked into the code a bit and it seems the completefunc is not used in a normal way. Instead of returning the list of matches there is instead a call to Grep here. Why is that? Does Grep manually replace the text instead of passing the results to vim's handling of completion matches? Just trying to understand the behaviour here.

Actually if I just temporarily disable that if statement by applying the following patch it all works again as expected, even with completeopt=menu,longest:

diff --git a/autoload/mkdx.vim b/autoload/mkdx.vim
index 23eb60c..502bd2e 100644
--- a/autoload/mkdx.vim
+++ b/autoload/mkdx.vim
@@ -1246,7 +1246,7 @@ fun! s:util.ContextualComplete()
 
   if (line[start] != '#') | return [start, []] | endif
 
-  if (!s:_testing && s:_can_vimgrep_fmt)
+  if 0
     let hashes = {}
     let opts = extend(get(s:util.grepopts, s:util.grepcmd, {}), {'pattern': '^(#{1,6} .*|(\-|=)+)$|(name|id)="[^"]+"', 'sync': 1})
     let opts['each'] = function(s:util.HeadersAndAnchorsToHashCompletions, [hashes])

@AckslD the code you are disabling right now is code which can potentially asynchronously fetch headings (taking a look at the implementation of s:util.Grep will clarify further).

I did this to make sure that the editor does not freeze for too long. Especially in large markdown files walking over each line and performing a regex becomes a bit sluggish.

The file you are testing with is likely not large enough to notice any difference which is why it seems to be just "useless code".

As to why this exactly breaks the menu options I have no clue honestly, but I do prefer keeping this code and then force menuone,insert for markdown files instead of digging deeper myself (currently living a reasonably busy schedule).

Alternatively, you can opt-out of fragment link completion and find some other plugin which does it better. Perhaps nvim-cmp provides some markdown fragment link completion component?


For background context, this is the first Vim plugin I ever developed, it is by no means developed in a pragmatic manner which makes it difficult to comprehend the code years later. It also tries to stay compatible between Vim and Neovim, another thing which further complicates any development of this plugin.

These days I write Lua plugins for Neovim only as that's the version of Vim I use myself and I can't be arsed supporting two "similar" yet "different" APIs just because the two projects have/had some kind of "rivalry".

Finally for some closing notes, I have no intention of porting "all of mkdx" to a Lua Neovim plugin, but I did recently gain some motivation for reviving the good parts of mkdx as a new Lua markdown plugin which would obviously try not to become a "monster" codebase (this is also the first time I'm publicly exposing this idea lol) :)

@SidOfc I completely understand and no problem! :)

I think I now roughly understand the code I miight try to figure out what's actually going on. In the meantime opting out from fragment link completion is indeed an option.

I tried to play around a bit and see what's going on and if I set completeopt=menu,preview and then type # in insert mode it actually crashes nvim ๐Ÿ˜ฌ

The output says:

-- User defined completion (^U^N^P) -- Searching...munmap_chunk(): invalid pointer                                                                                                   
                                                                                  [1]    3184780 IOT instruction (core dumped)  nvim README.md

I guess this is some issue in (nightly) nvim. Even if there is something wrong with how completeopt is used it shouldn't crash so I'll probably open an issue in core for this.


Re background. I think that sounds like a nice plan :) I also have some old vimscript plugins and am now only writing plugins in lua which is extremely satisfying. As you say, extracting the core concepts from mkdx might indeed be nice and maybe leave completion etc for nvim-cmp or similar.

Kudos for that mindset @AckslD, I think mkdx.nvim as a Neovim-only Lua plugin will become a reality at some point though no plans yet here :)

One final question remains, what should be done with this issue? Technically mkdx should not remove lines anymore when autocompleting.

Also, I think it's my wonky "programming" from the past which may cause some invalid_pointer error, I'm fairly certain the way I'm doing async stuff in mkdx is just outright garbage and quite unsafe. I definitely stuck with "happy path" programming most of the time back then.

One final question remains, what should be done with this issue? Technically mkdx should not remove lines anymore when autocompleting.

What do you think about having the option to opt-out of the async part for now at least?

On another note, at least for my part, I'd actually prefer to have the option to only set the completefunc and not bind the keys (eg #) in insert mode. This is not actually related to the issue at hand by I prefer to trigger completion manually and don't actually use auto-completion otherwise. I do use cmp but with autocomplete = false :)

@AckslD I've added two new settings (which are currently undocumented) which should give you the control you are looking for. The relevant changes are visible in this commit (I had a reference to this issue included, but it got trimmed somehow).

Setting pumheight to 0 will prevent pumheight from being modified in markdown files. Setting completeopt to 0 has the same effect (e.g. keeps whatever you set).

To ensure some kind of backwards compat, I've set them to the default value they had before they became conditional.

Alternatively, pumheight can be any positive integer (default 15) to determine height for popupmenu and completeopt can be a string value which you would like to use in markdown files (default noinsert,menuone, set 0 to use your own completeopt).

I'm going to write some docs so people know this behavior can be tweaked. Will leave the issue open for you to first confirm whether this fix is good.

Finally, thank you so much for sitting this one through with me so far!

@SidOfc Looks good. Btw, I think the main issue was that complete_add was called on the first call to completefunc which is not the intended way. I opened #160 which fixes this. In #160 I don't have the issue anymore, even using :set completeopt=menu,longest and the minimal example above does not crash nvim anymore.

@AckslD cheers for the PR and all your efforts, will check it out and merge if all looks good ๐Ÿ‘