lervag / wiki.vim

A wiki plugin for Vim
MIT License
657 stars 70 forks source link

Feature request: mark a header that has a backlink coming from another page #313

Closed ttdduu closed 1 year ago

ttdduu commented 1 year ago

Hi! Feature request: make headers that have been backlinked in another page italic (maybe for wiki-ft instead of wiki.vim).

lervag commented 1 year ago

Hi :)

ttdduu commented 1 year ago

Apologize, I clicked enter and sent the issue!

Anyway, I have a feature request: If I have page_1 and page_2, and page_2 has a #section1#section1.1 to which I want to link from page_1, I think it would be nice to see some kind of symbol in #section1.1, so I can see that it has been linked elsewhere (and I can see the backlink with wiki-graph-find-backlinks). For example, having #section1.1 in italics.

That's it! And thank you for all you do. I've been using your plugins basically every day for a long time now.

lervag commented 1 year ago

That's an interesting proposal. I believe it is possible to do this by use of some recent/advanced features e.g. in Neovim. Not sure if it is possible in Vim - perhaps it is. I will need to think about it.

But, to be clear, the proposal or idea is generally to make it clear that a specific header has an incoming link, right?

Perhaps it would be OK to put this indication as a sign in the sign column? Or as virtual text?

lervag commented 1 year ago

Also, I hope you don't mind that I took the liberty of reformatting your updated feature request - it makes it easier for me to fully grasp the intent.

That's it! And thank you for all you do. I've been using your plugins basically every day for a long time now.

<3

ttdduu commented 1 year ago

But, to be clear, the proposal or idea is generally to make it clear that a specific header has an incoming link, right?

Exactly! Because i may be missing the importance of this header while i parse through the page it is in, as i suggest below.

Perhaps it would be OK to put this indication as a sign in the sign column? Or as virtual text?

I hadn't thought about these options, but either one would look much better than italicizing the header. As an additional feature, maybe this sign could be the actual number of backlinks the section has, since that alone would give a sense of "how important" it is in the wiki. I could then opt to go look for it with wiki-graph-find-backlinks.

Also, I hope you don't mind that I took the liberty of reformatting your updated feature request - it makes it easier for me to fully grasp the intent.

Of course! Sorry i didn't pay much attention to formatting. I hadn't opened an issue in a long time; i think it shows by the mess i made at the beginning :)

lervag commented 1 year ago

I've pushed some updates that make it easy to add this feature. Right now, you can enable it on your own like this:

augroup MyWikiAutocmds
  autocmd!
  autocmd User WikiBufferInitialized call ShowIncoming()
augroup END

function! ShowIncoming() abort
  call sign_define("wiki-incoming", {
        \ 'text': '',
        \ 'texthl': 'DiagnosticSignInfo',
        \})
  let l:id = nvim_create_namespace("wiki.vim")

  let l:links_enriched = filter(
        \ wiki#graph#get_backlinks_enriched(),
        \ 'v:val.target_lnum > 0'
        \)

  for [l:lnum, l:links] in items(wiki#u#group_by(l:links_enriched, 'target_lnum'))
    call sign_place(0, "wiki", "wiki-incoming", "", #{ lnum: l:lnum })

    let l:text = len(l:links) > 1
          \ ? printf(" incoming (%d links)", len(l:links))
          \ : printf(" incoming (from %s)", l:links[0].filename_from)

    call nvim_buf_set_extmark(0, l:id, l:lnum - 1, 0, {
          \ 'virt_text': [[l:text, "DiagnosticVirtualTextHint"]]
          \})
  endfor
endfunction

I need to figure out how to implement this in a nice, robust manner with the right amount of user flexibility. It's not fully clear to me yet.

Notice, though, that wiki#graph#get_backlinks_enriched uses a caching mechanism that is not smart. To get a list of incoming links, we really need to parse all pages in the wiki. This particular step is also cached, but for a large wiki (like mine), the feature will lead to noticable lags.

ttdduu commented 1 year ago

Great! I pasted this code in my vimrc. I'm afraid the current comment may be unclear or messy. If it is, i could try a different way of illustrating the behaviour i have seen.

Notice, though, that wiki#graph#get_backlinks_enriched uses a caching mechanism that is not smart.

Expected behaviour:

Have the links cache updated such that, every time vimrc is loaded, links that have been removed are not shown in the virtualtext, and links that have been added are shown.

Observed behaviour:

The virtualtext on a header on page1 that has been linked to from page2 and page3 only shows the link from one of the multiple pages. Let's say it is page2. The virtualtext reads incoming (from page2). I think that not showing page3 isn't a big deal, since i would still see it has been linked to from somewhere.

However, the cache is indeed not updated, as shown in any of these scenarios:

A. Deleting the link [[page1#section1]] in page2 and restarting my vim session. After that, the virtualtext in page1 reads the same: incoming (from page2). I would have expected incoming (from page3), since would still be showing only one of the multiple filepaths.

B. Doing same as in A, plus removing the code i pasted on my vimrc. Still see incoming (from page2).

C. Creating a new link [[page1#section1]] in a page4 since the first install of wiki.vim: i still get incoming (from page2).

D. The same as in C, plus reinstalling wiki.vim.

In addition, calling ShowIncoming() inside the vim session on page1 appends the same virtual text to the existing one.

I hope i was clear enough!

lervag commented 1 year ago

Expected behaviour:

Have the links cache updated such that, every time vimrc is loaded, links that have been removed are not shown in the virtualtext, and links that have been added are shown.

Observed behaviour:

The virtualtext on a header on page1 that has been linked to from page2 and page3 only shows the link from one of the multiple pages. Let's say it is page2. The virtualtext reads incoming (from page2). I think that not showing page3 isn't a big deal, since i would still see it has been linked to from somewhere.

This is precisely what I meant about the cache not being smart. And I agree, we need a way to handle this that makes sense.

Currently, you can refresh the cache manually by doing :WikiClearCache links-in.

The reason I can't be very smart about this is that the cache for incoming links takes quite long to refresh. So we really don't want to refresh it often.

A. Deleting the link [[page1#section1]] in page2 and restarting my vim session. After that, the virtualtext in page1 reads the same: incoming (from page2). I would have expected incoming (from page3), since would still be showing only one of the multiple filepaths.

B. Doing same as in A, plus removing the code i pasted on my vimrc. Still see incoming (from page2).

C. Creating a new link [[page1#section1]] in a page4 since the first install of wiki.vim: i still get incoming (from page2).

D. The same as in C, plus reinstalling wiki.vim.

Yes, this is currently expected behaviour for the reasons given.

In addition, calling ShowIncoming() inside the vim session on page1 appends the same virtual text to the existing one.

Yes, the current function is a proof of concept and does not properly handle things. This is work in progress! :)


There are two main tasks here, IMHO:

  1. Try and figure out a better way of handling the incoming link cache.
  2. Implement a built-in solution for the incoming link-marks.

Considering that the solution I've provided already works, I think the best thing to do first is to look into 1. If I can find a good way to do the cache, then perhaps the resulting feature will be fast enough to be "always active". Also, it could be useful with a statusline indicator that shows the number of incoming links total (including links that are not "anchored").

lervag commented 1 year ago

I've now updated the caching mechanism: There's now a fast cache update process that more or less solves my mentioned problem. However, it is still quite slow to update the cache for large wikis. E.g., in my own wiki the cache files are about 8 MB each and just the process of reading and writing these files takes 0.2 seconds. And even the fast cache method needs to write the cache. I've therefore added a threshold for the fast cache update as well, which is currently at 30 seconds.

If you update and use the same code as before, you should notice that the signs and virtual texts are updated - but only if you wait 30 seconds.

Here's an updated function that 1) disables the fast cache delay (with the nudge option) and 2) clears the existing signs and virtual texts:

function! ShowIncoming() abort
  call sign_define("wiki-incoming", {
        \ 'text': '',
        \ 'texthl': 'DiagnosticSignInfo',
        \})
  call sign_unplace("wiki.vim")

  let l:id = nvim_create_namespace("wiki.vim")
  call nvim_buf_clear_namespace(0, l:id, 0, -1)

  let l:toc = wiki#u#associate_by(wiki#toc#gather_entries(), 'anchor')

  let l:graph = wiki#graph#builder#get()
  let l:links_enriched = l:graph.get_links_to(expand('%:p'), #{nudge: v:true})

  for l:link in l:links_enriched
    let l:section = get(l:toc, remove(l:link, 'anchor'), {})
    let l:link.target_lnum = get(l:section, 'lnum', 0)
  endfor

  call filter(l:links_enriched, 'v:val.target_lnum > 0')

  for [l:lnum, l:links] in items(wiki#u#group_by(l:links_enriched, 'target_lnum'))
    call sign_place(0, "wiki.vim", "wiki-incoming", "", #{ lnum: l:lnum })

    let l:text = len(l:links) > 1
          \ ? printf(" incoming (%d links)", len(l:links))
          \ : printf(" incoming (from %s)", l:links[0].filename_from)

    call nvim_buf_set_extmark(0, l:id, l:lnum - 1, 0, {
          \ 'virt_text': [[l:text, "DiagnosticVirtualTextHint"]]
          \})
  endfor
endfunction

Let me know what you think so far.

lervag commented 1 year ago

I've pushed the above function as an API function: wiki#buffer#refresh_incoming_links. Thus, with the following you would get something semi useful without much personal config:

augroup MyWikiAutocmds
  autocmd!
  autocmd User WikiBufferInitialized call wiki#buffer#refresh_incoming_links()
augroup END

There is still quite a lot of work to make this more robust and fine tuned and to make it auto update as needed.

I believe, though, that this should not be added as a "standard" feature except behind a command or a mapping. That is, I believe we want to add a command/mapping that refreshes the "incoming links view" with proper documentation. I also want to document the function API and to show how to make it automatic by use of autocommands.

lervag commented 1 year ago

I've made more update snow. I've added two commands and mappings:

I think this ended up being quite good. You can still activate the display similar to before, but now you must use this (because I renamed the function):

augroup MyWikiAutocmds
  autocmd!
  autocmd User WikiBufferInitialized call wiki#link#incoming_display()
augroup END

It remains to add some documentation.