MeanderingProgrammer / markdown.nvim

Plugin to improve viewing Markdown files in Neovim
MIT License
504 stars 20 forks source link

Figure out how to handle concealed text consistently #50

Closed MeanderingProgrammer closed 1 week ago

MeanderingProgrammer commented 1 week ago

There are various aspects of this plugin that assume default settings and default highlights for concealing.

When this is not the case we end up overwriting or duplicating text, neither of which is good.

Anything to do with alignment will need to be looked over and work with no concealing, full concealing, and character replacement concealing.

sergey-tikhonenko commented 1 week ago

For the concealed language issue I've made up the following custom handler

custom_handlers = {
  markdown = {
    render = function(namespace, root, buf)
      -- check for the concealed language node for code blocks
      local icons = require("render-markdown.icons")
      query = vim.treesitter.query.parse("markdown", "(info_string (language) @_lang)")
      for _, node in query:iter_captures(root, buf) do
        local value = vim.treesitter.get_node_text(node, buf)
        local icon, icon_highlight = icons.get(value)
        if icon == nil or icon_highlight == nil then
          return
        end

        local start_row, start_col = node:range()
        local captures = vim.treesitter.get_captures_at_pos(buf, start_row, start_col)
        local is_consealed = false
        for _, capture in ipairs(captures) do
          if capture.metadata.conceal then
            is_consealed = true
          end
        end

        local icon_text
        if is_consealed then
          icon_text = { icon .. " " .. value, { icon_highlight, "ColorColumn" } }
        else
          -- if the language isn't concealed, keep users preferences intach, only the icon will be added
          icon_text = { icon .. " ", { icon_highlight, "ColorColumn" } }
        end
        vim.api.nvim_buf_set_extmark(buf, namespace, start_row, start_col, {
          virt_text = { icon_text },
          virt_text_pos = "inline",
        })
      end
    end,
    extends = true,
  },
},

In the plugin configuration was added code_style = "normal", to switch off the default icon rendering.

Please note that we need Treesitter captures rather than extmarks to determine whether the given region is concealed or not.

I suppose this might give you some ideas on how to solve the problem more generally

MeanderingProgrammer commented 1 week ago

I've added a version of your solution as part of this commit: https://github.com/MeanderingProgrammer/markdown.nvim/commit/9b7fdea8058d48285585c5d82df16f0c829b2384.

The change I made was focussed on getting tables working well, so that I wasn't just overlaying text. This change fit nicely in there since I needed to make calculating concealed text more generic.

LMK if it works for you!

I still need to go through and think of any other places to apply this, such as handling users that conceal heading #.

sergey-tikhonenko commented 1 week ago

Yes this works, thanks.

If heading #'s are concealed by Treesitter captures then the same check might be used.

Concerning tables and links icons: for my configuration they don't work together. Configuration:

pipe_table = { cell = "raw" }

image

  1. The links icon mode assumes that cell="padded', so the right border is shifted.
  2. The style="full' doesn't work because the cell border doesn't match the calculated one for the "padded" style.

Here is code:

| Default | Left | Right | Center | Links  |
|---------|:-----|------:|:------:|--------|
| 12      | 12   |   $12 |   12   | [link1](https://example.org)  |
| **123**     | *123*  |  $123 |  `123`   | [link2](https://come.other.link)  |
| 1       | 1    |    $1 |   1    | [local](#subtables)  |

The are a few options how to remediate the situation:

  1. Disable links icon rendering for tables. a. This might be unconditional or b. by adding a switch for that or c. for cell = "raw" only
  2. Somehow render the concealed cell content over the cell if there is enough space to fit it. Otherwise fallback to the previous approach.

At the moment I have to completely disable this feature, despite the fact that it would be very useful for regular text.

link = { enabled = false }

I'm sticking with the assumption that enabling the plugin shall improve default rendering rather than requiring content to be reformatted.

image

MeanderingProgrammer commented 1 week ago

No matter the approach taken, if you want tables to look nice with this plugin you will need to format the underlying text to some extent as well. Like in your example you're essentially manually computing concealed text length so things line up when rendered.

Table rendering can be disabled as well with { pipe_table = { enabled = false } }, as an alternative to disabling links.

For your example table it'll work if you use { pipe_table = { cell = 'padded' } } with this raw text:

| Default | Left  | Right | Center | Links                            |
|---------|:------|------:|:------:|----------------------------------|
| 12      | 12    |   $12 |   12   | [link1](https://example.org)     |
| **123** | *123* |  $123 |  `123` | [link2](https://come.other.link) |
| 1       | 1     |    $1 |   1    | [local](#subtables)              |

I wouldn't quite characterize the problem as The links icon mode assumes that cell='padded', so the right border is shifted.. More, either tables need to be 'aware' of everything else, or everything else needs to be 'aware' of tables.

I do agree the raw table rendering calculation for full wasn't great. I updated it to account for concealed and inlined text here: https://github.com/MeanderingProgrammer/markdown.nvim/commit/8c71558a1cf959c198bb0540a16ae09e93cead62.

The example you provided now almost works with { pipe_table = { cell = 'raw' } }. The only thing is you do need to account for the inlined link icons so adding a couple spaces to the header / delimiter:

| Default | Left | Right | Center | Links    |
|---------|:-----|------:|:------:|----------|
| 12      | 12   |   $12 |   12   | [link1](https://example.org)  |
| **123**     | *123*  |  $123 |  `123`   | [link2](https://come.other.link)  |
| 1       | 1    |    $1 |   1    | [local](#subtables)  |

Not having links in tables is a little tricky, may add that later. The problem is that the markdown and markdown_inline parsers are essentially completely independent and we can't get nodes from one in another. So when we're rendering links figuring out if the current element is inside of a table is non trivial.

MeanderingProgrammer commented 1 week ago

Pushed a fix for user concealed headings here: https://github.com/MeanderingProgrammer/markdown.nvim/commit/5ce35662725b1024c6dddc8d0bc03befc5abc878.

Marking this as done as it seems to handle the cases I've tested out relatively well.

We have re-usable code to apply elsewhere if similar issues crop up or if there are some bugs with the implementation.