How do I use SyntaxHighlighter.getTokenHighlights(type) to return context-specific text attributes? Follow
In the code below, syntax highlighting displays, for example, event and editor in two different colors.
I can't, however, get any of that specific color information using an instance of SyntaxHighlighter, acquired like this:
val syntaxHighlighter = SyntaxHighlighterFactory.getSyntaxHighlighter(file.language, file.project, file.virtualFile)
The PsiElements associated with event and editor both have the type IDENTIFIER, so that type alone isn't enough to reveal the different coloring used for each indentifier — the specific text attributes must be context-dependent.
The code I have so far looks like this:
private fun getHighlightStyling(elem: PsiElement, syntaxHighlighter: SyntaxHighlighter,
colorsScheme: TextAttributesScheme, defaultForeground: Color): HighlightStyling {
val type = elem.elementType ?: elem.node.elementType
val textAttrKeys = syntaxHighlighter.getTokenHighlights(type)
val textAttrs = getTextAttributes(colorsScheme, textAttrKeys)
val color = if (settings.state!!.debug) DEBUG_RED else textAttrs?.foregroundColor ?: defaultForeground
val colors = getMatchingColors(color)
val background = if (settings.state!!.debug) defaultForeground else null
val fontType = textAttrs?.fontType ?: 0
return HighlightStyling(type, colors, background, fontType)
}
Since the only public method of SyntaxHighlighter that seems to be available is getTokenHighlights(), and that method only takes a single argument of type IElementType, I'm wondering if there aren't context-specific values for IElementType, apart from the what I'm pulling out of PsiElement above, that would function as the proper keys to look up the specific TextAttributes values that I'm seeing displayed. (In the code above textAttrsKeys is an empty array when type is IDENTIFIER.)
I've tried to figure out how to connect PsiElement and IElementType with the kinds of style specifications you can make in an XML .icls color scheme, but I've failed so far to follow through all the twists and turns of the Community Edition source code to figure out how that works.
Please sign in to leave a comment.
The general approach is to use an annotator for this. For newer IntelliJ versions you will want to use the newSilentAnnotation API with HighlightSeverity.INFORMATION. For example:
You can use TextAttributes.ERASE_MARKER via enforced text attributes to remove the previous styling if necessary (e.g. to remove bold styling from a keyword) in an annotation before the one that sets the text attributes for the semantic type.
For the colour settings page, you can use
getAdditionalHighlightingTagToDescriptorMap
to define tags to the semantic types and their associated text attributes. That allows you to have something like "event.<property>editor</property>" in the demo text.I'm not sure how this information helps with my problem. I'm trying to find out how a particular PsiElement would otherwise be styled before I apply my own style, because my choice of style is intended to be a variation upon whatever styling would otherwise have been applied.
In fact, TextAttributes.ERASE_MARKER is the exact opposite of what I want to do.
Here's what I think the issue might be. When I check what type of element a PsiElement is like this:
I get a very generic type like IDENTIFIER. In the file DefaultLanguageHighlighterColors.java, for instance, however, there are much more specific types defined, such as FUNCTION_DECLARATION, which is a specific variety of identifier, with a possibly specific color in the current theme.
So what I think I really need to somehow discover is that a particular PsiElement has been more specifically classified as FUNCTION_DECLARATION, for example, or the matching TextMate scope entity.name.function. If, for testing purposes, I look up:
colorsScheme.getAttributes(DefaultLanguageHighlighterColors.FUNCTION_DECLARATION)
...I get the kind of color and style info I'm hoping to get, but what I can't do yet is find out by querying a PsiElement that FUNCTION_DECLARATION is the key being used for the current highlighting of an element.
The semantic-based colour information is not set by the syntax highlighter, it is set by annotators, HighlightVisitors (as is the case for the above -- see the details below), or other mechanisms. You would most likely need to get the editor where the file is open, locate the place in the editor where the PsiElement is located, and query the text attributes of that text range. Although I'm not sure what you are trying to do. -- If it is to correctly highlight the code in a custom editor window (e.g. from a run action), you would need to ensure that the annotators and highlighters get run.
The Java plugin looks like it is using a HighlightVisitor to set the semantic styling:
For DefaultLanguageHighlighterColors.FUNCTION_DECLARATION, Java extends that to a JavaHighlightingColors.METHOD_DECLARATION_ATTRIBUTES property that is used to create a HighlightInfoType in JavaHighlightInfoTypes.METHOD_DECLARATION
The HighlightNamesUtil.getMethodNameHighlightType function returns that highlight info object if the isDeclaration parameter is true (or JavaHighlightInfoTypes.CONSTRUCTOR_DECLARATION if the method is also a constructor). That function is called by HighlightNamesUtil.highlightMethodName, which is used by HighlightVisitorImpl. That class is registered in the plugin XML as a highlightVisitor extension, which ends up calling the HighlightVisitor.analyze method. NOTE: Other classes are using that class for other purposes (e.g. to check for errors).
For annotators, the erase marker code I showed is used by annotators such as GroovyKeywordAnnotator to remove styling from keywords that are not actually keywords, and GroovyAnnotator has the logic to build the annotations for the different semantic types. If you search for newSilentAnnotator, you will see several languages (regex, json, xml, etc.) that are using that to apply the semantic based approach to add the semantic styling. I'm using that approach in a plugin of mine to add styling to the language the plugin supports.
I'm trying to make an IntelliJ version of an extension I created for Visual Studio Code: https://marketplace.visualstudio.com/items?itemName=kshetline.ligatures-limited
The IntelliJ API doesn't provide direct, precise control over where font ligatures are rendered, and where they aren't. There's no ligature flag that's part of an individual TextAttribute. You can only turn ligatures on and off globally at the level of a color scheme.
So I need to use a trick for suppressing ligatures, which is to vary the style of every other character in a ligature, with a highlight applied to each character one at a time. Notice how the characters '<', '=', and '>' are blended into a single glyph in the first image, and rendered separately in the next, as well as the change in the rendering for "***":
If these characters are plain, italic, or bold, I want them to stay plain, italic, or bold. The only other way to trick IntelliJ into rendering each character separately is to vary the foreground color in an alternating pattern. But I don't want that change of color itself to be clearly noticeable. I only want the side effect the subtle color change creates of breaking the ligature up into separate characters. So what I do is create a pair of colors which are only a very slight modification of the original color:
In order for this trick to work properly, however, I need to know what color each character was going to be rendered with in the first place, before I apply my changes. I can only get the color right part of the time, so far, however, because I haven't found a consistent way to query the API about colors that are currently being used.
(It's not this tricky to do in VSCode — simply applying a no-op style one character at a time breaks ligatures apart, without having to worry about picking a particular color or font style to use, and without having to alternate styles.)
I'll see if any of what you suggest above leads to the color info that I'm looking for.
Just to add some detail, the plugin I'm trying to create is functioning primarily as a HighlightVisitor, but it's a HighlightVisitor that needs to know how other HighlightVisitors have highlighted the code — if that's possible. And if I can't get the TextAttribute info itself associated with a particular PsiElement, I want to know how that PsiElement has been more specifically semantically categorized than the very high-level, low-detail info returned by PsiElement.elementType, so I can get the most specific TextAttributesKey possible, and look that key up in the current color scheme.
This is not for a custom editor. This plugin is meant to provide control over ligatures in standard code editor panes.
You said "You would most likely need to get the editor where the file is open, locate the place in the editor where the PsiElement is located, and query the text attributes of that text range."
I do have the PsiFile, I do have the Editor, and I do know where the PsiElements that I wish to re-style are located. So, how exactly, given all of that available info, do I "query the text attributes of that text range"?
I tried to look at Editor.markupModel, the only thing in the API of the Editor that looks close to what I might want. First, I got an exception for trying to access markupModel while not on the proper dispatch thread.
I then used ApplicationManager.getApplication().invokeLater() to take a look, and found only about 50 RangeHighlighter instances (far fewer instances than differently-colored tokens on the screen), all of them with a null foreground color.
After much experimentation, I finally found a solution.
It wasn't Editor.markupModel that I wanted to use, it was EditorImpl.filteredDocumentMarkupModel that I needed — that's where all of the formatting info that I need to examine is being kept. Using filteredDocumentMarkupModel requires making sure, of course, that the editor instance you're provided with is an instance of EditorImpl. That always seems to be the case so far for my needs.
Just like markupModel, filteredDocumentMarkupModel can only be used on IntelliJ's application dispatch thread (otherwise an exception is thrown). This adds a bit of a complication to implementing a HighlightVisitor, because HighlightVisitors aren't called on such a dispatch thread.
The result is that I've had to redesign my HighlightVisitor to do only some of the highlighting work I'm trying to do during the time period when my analyze() and visit() methods are called. I also end up ignoring the HighlightInfoHolder that's passed to the HighlightVisitor. Instead I accumulate a list of info for the highlights that I want to make, then use ApplicationManager.getApplication().invokeLater() to create the highlights in a somewhat different manner than a HighlightVisitor would normally do, by adding those highlights via MarkupModel.addRangeHighlighter() on the proper dispatch thread instead.
So I have code that looks like this that runs after figuring out everything except the color of the highlights I want to create:
Notice that I have to keep track of old highlights that I've created, and clean them up first before adding new highlights — something that does not need to be done when using a HighlightInfoHolder.
The rest of the related code looks like this (please keep in mind some of this logic is very specific to my particular needs, but I hope it's a good illustration of getting at the formatting info that I'm accessing):
It's a lot more work than I thought would be required, but it gets the job done. As far as I can tell there's no built-in, straight-forward method to get to the formatting info I want to see — I have to do the work myself of sifting through the full list of syntax highlighting for the entire document to find the formatting that matches a particular row and column of the document.