MeanderingProgrammer / render-markdown.nvim

Plugin to improve viewing Markdown files in Neovim
MIT License
1.86k stars 38 forks source link

feature: Support for Markdown text highlight using `==` e.g. `==I'm a highlighted text==` #212

Closed morgaan closed 1 month ago

morgaan commented 1 month ago

Is your feature request related to a problem? Please describe.

Sort of. Obsidian.nvim supports text highlighting using == pairs, as it seems to be the case as well for Obsidian, Quilt, iA Writer, HackMD, Typora to name a few. This converts toward <mark>I'm a highlighted text</mark>.

In order to benefit from this awesome plugin of yours, it's preferable to disable Obsidian.nvim ui module. Since your plugin does not yet support the highlighting syntax, this surface a regression if we turn off Obsidian.nvim ui module.

The highlight option in Markdown is super valuable for folks using a markdown wiki as their Second Brain and practicing Progressive Summarization

Describe the solution you'd like

The solution I would see is the support for the ==highlighted text== syntax. A configuration could be:

require('render-markdown').setup({
    highlight = {
        enabled = true,
        background = 'RenderMarkdownHightlightBg'
        foreground = 'RenderMarkdownHightlight'
    },
})

You may have a much better and straightforward approach.

Describe alternatives you've considered

Tried to follow this Stackoverflow thread answer without luck

Additional information

No response

MeanderingProgrammer commented 1 month ago

Duplicate of: https://github.com/MeanderingProgrammer/render-markdown.nvim/issues/173

I rely completely on tree-sitter to do the heavy lifting of getting relevant sections of the buffer to add decorations to. This has the upsides of being fast, well defined, and easy to use, but comes with the downside that if I can't pull the node directly I'm probably not going to add it to the plugin. Custom parsing is just not something I want to include in this plugin for my own sanity.

Since the == syntax is not part of github flavored markdown it's not included in the tree-sitter parser. If it gets added at some point I can add this feature.

I get it's a kind of regression and the feature feels like it would be simple to add, but that's unfortunately just not the case, at least currently.

I do include a custom_handlers implementation of this feature that you can try out in your config in the original issue, it's by no means perfect but might work well enough.

morgaan commented 1 month ago

Oh thank you so much, I knew you would have a much better and straightforward approach.

The custom_handlers you produced works well if the highlighted text is contained on the same line. As I do wrap text that exceeds 80 characters width (for readability), if I want to highlight across the wrapped lines then it's unfortunately not working.

I'm unfortunately not really familiar with the Lua language and the treesitter framework. Would this be an easy patch to the handler to enhance the capture of the highlight across multiple lines?

I believe that this handler would be a nice addition to your Custom handlers page, as it is not yet crowded with recipes as well as this is already the second time you are asked about this, and more over that as long as treesitter markdown grammar implementation does not support it, it is super valuable asset.

Thank you for the support so far

MeanderingProgrammer commented 1 month ago

It's not super straightforward. Once you have the text from local text = vim.treesitter.get_node_text(root, buf)

You would run the pattern match directly on the text rather than splitting to each line. Then you would need to do some offset calculation based on the range returned from calling find.

It gets tricky since you have to find offset from nearest line boundaries as well as the number of lines covered to figure out the start / end row / column.

MeanderingProgrammer commented 1 month ago

I played around with a multi-line version and came up with this:

require('render-markdown').setup({
  custom_handlers = {
    markdown = {
      extends = true,
      parse = function(root, buf)
        local marks = {}

        ---@param row { [1]: integer, [2]: integer }
        ---@param col { [1]: integer, [2]: integer }
        ---@param conceal? string
        ---@param hl_group? string
        local function append(row, col, conceal, hl_group)
          table.insert(marks, {
            conceal = row[1] == row[2],
            start_row = row[1],
            start_col = col[1],
            opts = { end_row = row[2], end_col = col[2], conceal = conceal, hl_group = hl_group },
          })
        end

        local text = vim.treesitter.get_node_text(root, buf)
        local top_row = root:range()

        ---@param index integer
        ---@return integer, integer
        local function row_col(index)
          local lines = vim.split(text:sub(1, index), '\n', { plain = true })
          return top_row + #lines - 1, #lines[#lines]
        end

        ---@type integer|nil
        local index = 1
        while index ~= nil do
          local start_index, end_index = text:find('(=)=[^=]+=(=)', index)
          if start_index ~= nil and end_index ~= nil then
            local start_row, start_col = row_col(start_index - 1)
            local end_row, end_col = row_col(end_index)
            -- Hide first 2 equal signs
            append({ start_row, start_row }, { start_col, start_col + 2 }, '', nil)
            -- Highlight contents
            append({ start_row, end_row }, { start_col, end_col }, nil, 'DiffDelete')
            -- Hide last 2 equal signs
            append({ end_row, end_row }, { end_col - 2, end_col }, '', nil)
            index = end_index + 1
          else
            index = nil
          end
        end

        return marks
      end,
    },
  },
})
morgaan commented 1 month ago

I played around with a multi-line version

This works like a charm :1st_place_medal:. Thank you so much! You saved my day here. I hope this will benefit more people down the line :pray:.

It may be worth to include it among your examples/recipes of custom handlers, this remains obviously your call :wink:

Thank you again so much!

MeanderingProgrammer commented 4 days ago

This is now handled natively by the plugin after: https://github.com/MeanderingProgrammer/render-markdown.nvim/commit/d6a82d70765aa238b7ea48d257a1d57a92501423

morgaan commented 3 days ago

Oh thank you so much for this addition :heart:

I just gave it a try, highlighting some french text with accents, and it behaved weirdly. Just as if accents were left out of the column tracking or something. It's getting worse as we move the cursor away for the line. I attached a video capture of this in the hope that it give you a better visual representation of what I mean.

https://github.com/user-attachments/assets/bb9a1733-05ac-4fc0-9e0c-f3183828477d

Here is the text used for the attached video capture if you want to give a try at fixing this:

Un jour Cosette se regarda par hasard dans son miroir et se dit : Tiens ! Il lui semblait presque qu’elle était jolie. Ceci la jeta dans un trouble singulier. Jusqu’à ce moment elle n’avait point songé à sa figure. Elle se voyait dans son miroir, mais elle ne s’y regardait pas. Et puis, on lui avait souvent dit qu’elle était laide ; Jean Valjean seul disait doucement : Mais non ! mais non ! Quoi qu’il en fût, Cosette s’était toujours crue laide, et avait grandi dans cette idée avec la résignation facile de l’enfance. Voici que tout d’un coup son miroir lui disait comme Jean Valjean : Mais non ! Elle ne dormit pas de la nuit. — Si j’étais jolie ? pensait-elle, comme cela serait drôle que je fusse jolie
MeanderingProgrammer commented 2 days ago

Ah, thanks, messed up the usage of displayed width vs. byte width. Should be fixed after: https://github.com/MeanderingProgrammer/render-markdown.nvim/commit/3a319cdbefebf0079a7012dab6b1bfc18ca5b97f.

morgaan commented 2 days ago

Just tried the fix. It works! Thank you so so much :trophy: