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

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

Closed
ttdduu opened this issue Jul 2, 2023 · 11 comments
Closed

Comments

@ttdduu
Copy link

ttdduu commented Jul 2, 2023

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

@ttdduu ttdduu closed this as completed Jul 2, 2023
@lervag
Copy link
Owner

lervag commented Jul 2, 2023

Hi :)

@ttdduu
Copy link
Author

ttdduu commented Jul 2, 2023

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.

@ttdduu ttdduu reopened this Jul 2, 2023
@lervag
Copy link
Owner

lervag commented Jul 2, 2023

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
Copy link
Owner

lervag commented Jul 2, 2023

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
Copy link
Author

ttdduu commented Jul 3, 2023

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 :)

@ttdduu ttdduu changed the title Hi! Feature request: mark a header that has a backlink coming from another page Jul 3, 2023
@lervag
Copy link
Owner

lervag commented Jul 3, 2023

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
Copy link
Author

ttdduu commented Jul 3, 2023

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
Copy link
Owner

lervag commented Jul 3, 2023

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
Copy link
Owner

lervag commented Jul 4, 2023

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 added a commit that referenced this issue Jul 4, 2023
This is currently WIP, but the function does seem to work at least as
a proof of concept.

refer: #313
@lervag
Copy link
Owner

lervag commented Jul 4, 2023

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 added a commit that referenced this issue Jul 25, 2023
@lervag
Copy link
Owner

lervag commented Jul 25, 2023

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

  • WikiLinkIncomingToggle (mapped to <leader>wli by default)
    • Will toggle the display of incoming links. Incoming links are shown with both signs and virtual text for each heading, but also for the entire page (i.e. for unanchored links). The latter is displayed at the top of the page.
  • WikiLinkIncomingHover (mapped to <leader>wlI by default)
    • This will show a menu of files for each incoming link and allow to go to that link by selecting it.

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.

@lervag lervag closed this as completed Jul 25, 2023
lervag added a commit that referenced this issue Jul 25, 2023
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