Skip to content

Conversation

@xzbdmw
Copy link

@xzbdmw xzbdmw commented Feb 1, 2025

This is a trick I learned from trouble, however this fits very good with quicker.nvim, because when you edit the buffer, the highlighter get called on every redraw just as a normal file.

iShot_2025-02-02_14.52.28.mp4

M.cache[buf] = M.cache[buf] or {}

if not M.cache[buf][lang] then
local ok, parser = pcall(vim.treesitter.get_parser, buf, lang)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of callingvim.treesitter.languagetree.new in trouble's code, we need to call get_parser, which will register redraw callback for current buffer (trouble manually calls attach every time item chanegs, so it does not need to call get_parser).

end

if loaded then
-- If attach_parser is true, we should not apply lsp highlight or query ts highlights
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The highlighter needs to own the entire buffer, because its highlight state is ephemeral, any other non-ephemeral highlight group will mess them up (they shift with text).

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it any worse than the current highlighting behavior? It already doesn't look very good when editing the text.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean we should query buffer highlights when possible?

@xzbdmw xzbdmw force-pushed the treesitter branch 9 times, most recently from 79b273e to d5e79fb Compare February 2, 2025 14:19
@xzbdmw
Copy link
Author

xzbdmw commented Feb 2, 2025

There are some strange issue with EM_QUAD, I'm not sure the exact cause, grep for "cmp.mapping" result in something like this (I've only found this case the highlighter did not work well):
image
If you put this line into an empty lua file they look the same (except for lsp semantic highlight)
� ["<d-v>"] = cmp.mapping(function(fallback)

image

This <83> is something from EM_QUAD, adding a space after the EM_QUAD solves this problem. The edge case of choosing the correct range start to be included in parser turned out to be tricky, and I find this is the simplest way to avoid all sorts of problems, that is, includes an extra space before the real code, for example, "␣" stands for the extra space:

README.md ␣┃10┃text
          |        | -- we include the space to parse

This allows README.md ␣┃10┃"some string“ text to be highlighted correctly when " is at start of the qf text. However this affects all the existing screen tests (I can adjust the tests if you think this is the right way).

@xzbdmw xzbdmw force-pushed the treesitter branch 6 times, most recently from f45bd15 to 8158a40 Compare February 3, 2025 16:17
@xzbdmw xzbdmw force-pushed the treesitter branch 4 times, most recently from 029800d to aa190a7 Compare February 3, 2025 20:28
Copy link
Owner

@stevearc stevearc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the strengths of the current approach is that it handles large lists very well. For a long running asynchronous grep process, for example, as we append new items we only have to parse and highlight those new items. It looks like this method would have to reparse and rehighlight the entire quickfix list. Is there any way around that?

These issues you're having with the EM_QUAD are likely because it's a multibyte unicode character. You probably have an off-by-one error somewhere that's causing it to get split, creating an invalid utf-8 byte sequence. This should be solvable without adding an extra space.

Comment on lines 37 to 42
vim.api.nvim_create_autocmd("QuickFixCmdPost", {
group = vim.api.nvim_create_augroup("quicker.treesitter.hl", { clear = true }),
callback = function(ev)
M.cache[ev.buf] = nil
end,
})
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What problem is this solving? I don't rely on QuickFixCmdPost in the rest of this plugin because some methods of setting/updating the quickfix do not trigger it.

lang = lang or "markdown"
lang = lang == "markdown" and "markdown_inline" or lang

M.cache[buf] = M.cache[buf] or {}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need some way to clear this cache when the buffer is deleted, right?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think QuickFixCmdPost can be changed to BufWipeOut.

README.md Outdated
Comment on lines 201 to 204
-- Attach parser to qf buffer, highlight text in real time as you edit.
-- If this is true, other highlight options are ignored.
attach_parser = true,
-- Query treesitter highlight groups using string parser, only update after you save or refresh.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we ship this feature, I think that ideally it would replace the current treesitter methods of highlighting; both the "fast" and the version with loaded buffers. If it's noticeably worse in some situations, then we can consider some way to make the other mechanisms work with it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This do has a problem: when list contains more than 10k items, each keypress adds like 30ms input latency.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like it would be possible to add an option to do a single parse pass and then detach if the buffer has > N lines, right? And better than maintaining two different ways to do highlighting.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the old string_parser code and add a new config option highlight.max_lines

end

if loaded then
-- If attach_parser is true, we should not apply lsp highlight or query ts highlights
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it any worse than the current highlighting behavior? It already doesn't look very good when editing the text.

@xzbdmw
Copy link
Author

xzbdmw commented Feb 13, 2025

For a long running asynchronous grep process, for example, as we append new items we only have to parse and highlight those new items. It looks like this method would have to reparse and rehighlight the entire quickfix list. Is there any way around that?

We can stop parsing lines with the same content, somehow cache it, however parsing is not the bottleneck here, it is vim.filetype.match the slowest, luckily 0.11 will speeds it up from 0.5ms to 0.05ms each item.

You probably have an off-by-one error somewhere that's causing it to get split

Actually I tried all the combination of offset and it will only highlight local but not local if you insert at the very start if the line without an additional space.

@xzbdmw
Copy link
Author

xzbdmw commented Feb 13, 2025

I’m not sure how useful cache regions are, as parsing is generally very fast. With 0.11 introducing asynchronous parsing, testing in the nightly build shows that parsing 7,000 items takes 144ms, while the quickfixTextFunc itself requires 200ms.

Copy link
Owner

@stevearc stevearc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested it locally and made some changes to the EM_QUAD logic. I tested it against your "grep cmp.mapping" and it seems to work fine.

local ft = vim.filetype.match({ buf = item.bufnr })
if config.highlight.attach_parser and ft then
info.regions[ft] = info.regions[ft] or {}
info.empty_regions[ft] = info.empty_regions[ft] or {}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is empty_regions for?

Copy link
Author

@xzbdmw xzbdmw Feb 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

empty_regions is used to to clear the included regions and make sure a reparse with clean state will happen after Refresh, if you modify the saved file and call Refresh on qf the previous highlight are still there:

iShot_2025-02-16_09.58.15.mp4

Copy link
Author

@xzbdmw xzbdmw Feb 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edit: this doesn't work, we have to manually include an empty region, multiple call to vim.treesitter.get_parser with the same argument returns the same parser.

If we want to support max_lines option, then empty_region is not needed, because lines are computed using regions, it complicates the logic of when should we register the callback (vim.treesitter.get_parser) and when should not (vim.treesitter.languagetree.new), now I'm just creating a new parser each time attach is called.

@xzbdmw
Copy link
Author

xzbdmw commented Feb 16, 2025

iShot_2025-02-16_09.42.27.mp4

the original highlight is correct but it has problem reparsing (This only happens if I insert at line start)

@github-actions github-actions bot requested a review from stevearc February 16, 2025 02:06
@xzbdmw
Copy link
Author

xzbdmw commented Feb 16, 2025

      if loaded then
        -- If this is called together with  the parser, they indeed will overwrite each in some cases
        add_item_highlights_from_buf(qfbufnr, item, line, i)
      end

These highlights comes from semantic highlight, two problems, one is everything after // should be gray and second is the semantic highlight left behind after saving.
https://github.com/user-attachments/assets/97b6a831-2651-464c-826d-7225380d860c

@xzbdmw xzbdmw force-pushed the treesitter branch 3 times, most recently from 8e0336d to 56e0ce6 Compare February 16, 2025 04:04
@xzbdmw
Copy link
Author

xzbdmw commented Feb 16, 2025

I think there remains two one question

  1. should we include semantic highlight with parser? I think this is just the config option lsp left user to decide
  2. is the space after EM_QUAD unavoidable?

@xzbdmw xzbdmw force-pushed the treesitter branch 3 times, most recently from 0ca1a4d to 8e8da59 Compare February 16, 2025 04:16
@xzbdmw
Copy link
Author

xzbdmw commented Feb 16, 2025

Sigh, owo more problem, you must perform an edit, if not, directly delete a line loses all the highlights:

iShot_2025-02-16_15.27.37.mp4

we can do something like this as workaround:

  -- Run a full parse for all included regions. There are two reasons:
  -- 1. When we call `vim.treesitter.get_parser`, we have not set any
  --    injection ranges.
  -- 2. If this is not called, the highlighter will do incremental parsing,
  --    which means it only parses visible areas (the on_win and on_line callback),
  --    so if we modify the buffer, unvisited area's state get unsynced.
  pcall(parser.parse, parser, true)
  vim.api.nvim_buf_call(buf, function()
    vim.cmd([[norm! "_xu]])
  end)

though it is a bit too hacky.

@stevearc
Copy link
Owner

stevearc commented Mar 4, 2025

Sorry for the long wait; I don't have as much free time as I used to. Loading the context back into my head, these are the remaining questions:

  1. What to do with the space after the EM_QUAD?
  2. Highlights disappear after deleting a line
  3. Is there anything pending in this thread?

For 1 I would say that I strongly bias towards not adding a space after the EM_QUAD. If the only negative impact is that inserting at the very beginning of a line doesn't get highlighted, I think that's still better than adding a bonus space everywhere. I still think that this should be able to work completely fine without the space, but it's possible that there's a fix or adjustment that would have to be made upstream.

For 2, that is very weird. I definitely don't like the hack approach. I don't think I fully understand what is going wrong here. I know that the treesitter highlights attempt to only update the regions that are visible, but why does that cause not-currently-visible regions to get desynced? Is there a bug in the incremental logic, or is this because of how we're setting/using the regions?

@xzbdmw
Copy link
Author

xzbdmw commented Mar 4, 2025

Is there a bug in the incremental logic, or is this because of how we're setting/using the regions?

My guess is that if you don't perform an edit, the entire injection tree get invalidated when one entire line disappears, this must have something to do with the LanguageTree internals's assumption.

If the only negative impact is that inserting at the very beginning of a line doesn't get highlighted, I think that's still better than adding a bonus space everywhere

This is not ideal when you add a comment leader at the very beginning of the line though, user will expect entire line get dimmed, since you can't use gcc in qf buffer, mostly one will use I// instead.

Maybe we can use extmarks to conceal the extra space?

Is there anything pending #41 (comment)

This is addressed.

That said, I'm not in a hurry to merge this PR, and there are many treesitter changes in the comming 0.11 stable version, we can wait for it.

@xzbdmw
Copy link
Author

xzbdmw commented Mar 4, 2025

Just push a commit conceals the space, seems works well.

Edit: for conceal to take effect in insert mode we have to set concealcursor, and that confilct if the qf text is concealed, e.g. markdown files, This approach is still not ideal.

Another option is to remove EM entirely, replace it with a space, and not rely on EM to calculate necessary width information, not sure how much complexity will be added by this approach.

@stevearc
Copy link
Owner

stevearc commented Mar 4, 2025

Okay, in that case let's wait for 0.11 and see if it provides fixes for any of the issues

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

Successfully merging this pull request may close these issues.

2 participants