microsoft / vscode

Visual Studio Code
https://code.visualstudio.com
MIT License
164.67k stars 29.44k forks source link

Symbol occurrence highlighting overlaps occurrences of the selected text #100530

Open anthroid opened 4 years ago

anthroid commented 4 years ago

When selecting text or placing the cursor on a symbol with the C/C++ extension disabled, the other occurrences of the symbol are highlighted, or if there is a selection, the occurrences of the selection are highlighted. The selection is highlighted with the regular selection highlight color (editor.selectionBackground), and the occurrences are highlighted using the occurrences highlight color (editor.selectionHighlightBackground).

With the C/C++ extension enabled, if the global setting Editor: Occurrences Highlight is enabled, VS Code will always do both, with the symbolic occurrence highlighting rendered after the selection highlight, and all occurrences of both the selected text, and the symbol are highlighted in the same style, with no way to differentiate between them. This causes the selected text, which is more specific, and a direct result of a user action, and therefore arguably higher precedence, to be completely obscured by the symbol occurrence provided by the extension, which is arguably lower precedence.

Here, in an unsaved buffer, with the language set to C, the behavior is predictable and correct. When placing the cursor on an occurrence of a symbol with no selection, the other occurrences of the symbol are highlighted: c-unsaved-buffer-cursor-symbols

Here, in the same unsaved buffer, part of the symbol foo is selected. The behavior is still predictable and correct. The other occurrences of the selection are highlighted in the highlight color (editor.selectionHighlightBackground), and the selection itself is highlighted in the selection color (editor.selectionBackground): c-unsaved-buffer-partial-selection

Likewise, if the entire symbol foo is selected, the behavior is still as expected, with the selection itself highlighted with the selection color, and the other occurrences of the selection highlighted in the highlight color: c-unsaved-buffer-full-symbol-selection

If the file is saved, and the C/C++ extension becomes enabled for the file, the behavior becomes problematic. Here, as in the first example, the cursor is placed within a symbol, but no selection is made. The behavior is as expected, with the symbol and all other occurrences highlighted in the highlight color: c-saved-buffer-cursor-symbols

Here, when part of the symbol is selected in the saved file, the symbol occurrences overlap the selection occurrences. The first two characters of the symbol foo are selected, but due to the overlapping, both the selection is obscured, and additionally, there are now two different occurrences being highlighted throughout the file at the same time, in the same color and style (both the selection, fo, and the symbol, foo), making them impossible to differentiate: c-saved-buffer-partial-selection

Here, when the entire symbol is selected, the selection color is lost, so it is impossible to identify which text is selected: c-saved-buffer-full-symbol-selection

If both colors are made to be transparent, or a highlight border is added, it becomes obvious that both highlight actions are taking place, first the selection occurrences, then the symbolic occurrences (there is also a noticeable delay for the second highlight, so the selection highlighting can be seen briefly before being obscured). Setting these colors to be transparent would seem to resolve the problem, but the occurrence highlighting feature becomes almost useless when the highlight transparency is adjusted to the point where the original selection is visible through it. I also firmly believe that transparency is not a solution to this issue, because most color combinations either result in the underlying selection not being visible enough to distinguish through the occurrence highlight, or the occurrence highlighting not being visible against the background. The other core problem is that both the selection and the symbol are highlighted using the same editor.selectionHighlightBackground color, so even if there was no visible problem overlapping at the location of the selection itself, all of the instances of both the selection and the symbol occurrences are highlighted the same, which is confusing.

This can be seen here, where editor.selectionBackground is set to a bright, opaque color #ff0000, and editor.selectionHighlightBackground is set to a bright, but also transparent color #00ff0050, in order to keep the main highlight color visible underneath: c-saved-buffer-partial-selection-transparent This makes the selection highlighting visible, but unfortunately also causes whitespace decorations to be obscured, which is another problem with relying on transparency for symbol visibility. This is one of the best case scenarios (a bright, opaque selection color with a bright, transparent highlight color), but still suffers from all highlighted occurrences being styled the same (both foo and fo are highlighted green). Most normal combinations of transparent highlight colors are hardly visible, especially if using a sane editor.selectionBackground.

Setting both colors to transparent values allows whitespace decorations to be shown, but results in hardly usable or visible highlighting in either case. Here, both are set to transparent values, and the selection itself can hardly be seen, and still, all occurrences of both the selection and the symbol are the same color: c-saved-buffer-both-transparent

As far as I can tell, there is no way to disable the 'overlapping' occurrence highlighting feature without losing other important functionality, like single-clicking on a symbol and the other instances being automatically highlighted, or without completely disabling the C/C++ extension and losing Intellisense features.

It appears the most straightforward solution would be to do one, or ideally both of the following:

It would also make sense to render whitespace and other related decorations after the highlighting as well, so they would be visible regardless of the selection and/or occurrence highlighting, and regardless of the transparency of the selected colors.

Steps to Reproduce:

Prepare the environment:

  1. Enable the setting Editor: Occurrences Highlight
  2. Set both editor.selectionBackground and editor.selectionHighlightBackground in settings.json -> workbench.colorCustomizations to two different, opaque colors. For example, #ff0000 and #0000ff.
  3. Ensure the C/C++ extension is installed and enabled. This example uses C/C++, but this problem likely occurs with any extension that provides highlight ranges.

Test the behavior:

  1. Create a new folder.
  2. Create a new .c file in the folder.
  3. Open the new folder in VS Code.
  4. Within the newly created .c file, add the code in the screenshots above, or any other valid C code with several occurrences of a given symbol, ideally one that shares likeness with another symbol or keyword in order to demonstrate the problem (testvar1 and temp2 for example, sharing the prefix te).
  5. Save the .c. file.
  6. Copy the entire text to the clipboard.
  7. Create a new empty buffer, set the language of the new buffer to C, and paste the copied text into the buffer. Do not save the buffer.
  8. Observe the behaviors described above. The unsaved buffer will behave predictably, presumably because the C/C++ extension is not active (so Intellisense is also not active, and the extension is not providing any highlight ranges). The saved buffer will have C/C++ Intellisense enabled, and symbolic occurrences highlighting will obscure the active selection highlighting.

Expected behavior Highlighted occurrences of selected text take precedence over symbolic highlighting occurrences, only occurrences of the selected text are highlighted if a selection is present, or both are visible and differentiated.

Does this issue occur when all extensions are disabled?: No. However, I initially filed this issue in the C/C++ extension repository and was instructed to file it here based on extensions not having any control over the highlight rendering, only the ranges to be highlighted as per https://microsoft.github.io/language-server-protocol/specification#textDocument_documentHighlight.

alexdima commented 4 years ago

I have taken a look at the sources. This is a problem somewhat specific to the C++ extension and to the way in which it provides document highlights. Document highlights can have an optional kind. This is useful for highlighting read access to a variable/argument/member vs. write access. Languages like TypeScript/JavaScript distinguish and return highlights with the kind set to Read or Write. Most languages do this.

I think in this case, the kind is missing from the highlights and that ends up using the color editor.selectionHighlightBackground, which, I tend to agree, is a rather dubious choice.

I did some blame on the sources, and it looks like Text highlights have always used the selectinoHighlight color, since the first VS Code public release https://github.com/microsoft/vscode/blame/79ee367cd6b7b1bf36d84faf9f80c90126ac2ea1/src/vs/editor/contrib/wordHighlighter/common/wordHighlighter.ts#L249

I think we can do two things:

  1. introduce another color for DocumentHighlightKind.Text which defaults to editor.selectionHighlightBackground if not defined.
  2. reach out to the C++ extension and ask why don't they use the kinds Read and Write.

@sean-mcmanus @Colengms Would it be possible for the C++ extension to use the kinds Read and Write in the document highlights it returns?

sean-mcmanus commented 4 years ago

@alexdima Our reference highlighting implementation is inherited from VS, which just obtains a range for the references and not a kind. From looking at the code, it doesn't appear trivial to add (we would need to file a feature request on VS at https://developercommunity.visualstudio.com/content/idea/post.html?space=62). We would prefer to be able to return a new DocumentHighlightKind in the LSP (https://microsoft.github.io/language-server-protocol/specification):

    /**
     * Reference to a symbol (read or write undetermined).
     */
    export const Symbol = 4;

Or whatever wording you think is appropriate. And it could default to use the Read color.

But just changing the default color would be fine with us too (or some way for our extension to opt-in to the new color).

alexdima commented 4 years ago

@sean-mcmanus Thank you, I was thinking it might be something like that -- difficult to determine read vs write usages. In this case, I think we should add on our side a custom color for DocumentHighlightKind.Text. Please let me know if you have any naming suggestions.

anthroid commented 4 years ago

Hi, I just wanted to add an update here, and pose a couple questions. Since opening this issue, I have continually found it nearly impossible to work with this behavior (not being able to see the actual selection when the extension's occurrence highlight clobbers it, and additionally not being able to differentiate between selection occurrences and symbol occurrences because they are both highlighted), so I ended up completely disabling the C/C++ extension.

I primarily work with C, and I still wanted to use completions and other features the extension provides, so I installed the C/C++ Clang Command Adapter extension (mitaki28.vscode-clang), which does not appear to implement any range-highlighting functionality, so this just works predictably with the native occurrence and selection highlight behavior (as with no language extension), and the behavior is correct. Symbolic occurrences are highlighted with a single click, and selections are highlighted without an overlapping symbol highlight.

Plain text, no language selected, no language extension:

With a partial symbol selected, only the selection occurrences are highlighted (using editor.selectionHighlightBackground, and the primary selection uses editor.selectionBackground. There is no overlapping symbol occurrence highlighting:

Screen Shot 2020-08-30 at 12 48 37 AM

C language selected, no C/C++ extension enabled:

Behavior is still correct:

Screen Shot 2020-08-30 at 12 56 07 AM

C language, C/C++ Clang Adapter extension enabled:

Behavior is still correct (partial symbol highlighted, no conflicting highlight):

Screen Shot 2020-08-30 at 12 58 48 AM

Single-click full-symbol highlighting also still works as expected (cursor is on an instance of foo, no selection):

Screen Shot 2020-08-30 at 1 00 26 AM

So I wanted to ask:

  1. Is there any additional functionality gained by the C/C++ extension providing these highlight ranges, or is this just creating a conflict with the native occurrence highlighting? Could it simply be removed from the extension?

  2. If there is additional functionality provided by highlighting these ranges that extends beyond the scope of the "native" highlighting behavior, shouldn't the extension use its own setting instead of using the global editor.occurrencesHighlight and editor.selectionHighlight? By using these global settings, this means the user must disable all occurrence highlighting behavior completely, rather than being able to keep the native behavior enabled, and simply disabling the extension's highlighting behavior.

sean-mcmanus commented 4 years ago

@anthroid Our C++ reference highlighting is semantic instead of just textual -- it only highlights the actual confirmed references to the selected item and not references to other programmatic entities. See the screenshot in which the hidden "iii" and static class member are not highlighted: image and without our extension: image

In regards to issue 2, we implement https://microsoft.github.io/language-server-protocol/specification#textDocument_documentHighlight We could add a setting to disable our reference highlighting; however, I think you're the only user who has asked for it. I filed an issue at https://github.com/microsoft/vscode-cpptools/issues/6056 in case other users want to upvote that or make a PR for the TypeScript changes.

anthroid commented 4 years ago

@sean-mcmanus Thanks for the explanation and for looking into it. In the meantime, I'll look into making the TS changes to make the setting independent and see what I can come up with.

wolf99 commented 4 years ago

I'm seeing this problem too. It is intensely frustrating to work in C with this C/C++ extension enabled.

NejcBW commented 4 years ago

I can confirm the same issue with essentially all languages using semantic highlighting.

The way I see it the problem is the fact that selection highlight is rendered below the symbol highlight. Just do it the other way around and everything will be fine.

In the example below you can see the behaviour of VS Code on the left and JetBrains Rider on the right. In both cases "Ad" is selected in the first line, but in VS Code the selection is not visible because because it comes below the symbol highlighting.

Screenshot 2020-11-08 180933

wolf99 commented 3 years ago

Hi @alexdima, do you know if implementation on this could be expected from MS?

I imagine there is a threshold of some N +1's for issues to receive such attention. However I sadly would not expect this to reach N. Mainly because I would guess that the set of people that program in C has very little overlap with the set that know a lot about TS (I being an example of being in the former set but very much not the latter). Probably similarly small overlap with the the set that will report such an issue rather than just moving on to another plugin or editor.

(Please tell me I'm wrong on any point 😄 )

wolf99 commented 2 years ago

I have simply stopped using this extension because of this issue.

The extension authors say it is a VSCode problem, VSCode authors say it is an extension problem.

Either way it results in unusable editing. VSCode is a file editor and one can run C/C++ builds from the CLI easily enough; so really the extension brings no real added value for me.

suxscribe commented 2 weeks ago

I kinda solved the issue. The thing is that selection highlights are rendered in a separate DOM element .view-overlays above the text elements. And each highlight is basically a div with it's own z-index.

To switch occurrence highlight and selection highlight layers we need to set one above the other. This can be done with Custom CSS and JS extension.

/* selection background */
.monaco-editor .lines-content .cslr {
  z-index: 1; /* default: unset */
}

/* occurrence / word highlight background */
.monaco-editor .lines-content .cdr {
  z-index: 0; /* default: 4 */
}

You might wanna play with z-indexes of each layer in case they interfere with another selection layers.

Examples:

Default style ![Image](https://github.com/user-attachments/assets/f05a90bd-ead6-49e0-ad43-1b070c563901)
Customized style ![Image](https://github.com/user-attachments/assets/744f0245-46da-424b-be79-d7f85ea20f7c) ![Image](https://github.com/user-attachments/assets/632ea58a-0005-4d89-82f1-dd7fe2299954)