Can I find the colors and font style currently being used to render a particular PsiElement?
I found an old question that looks like a similar topic: https://intellij-support.jetbrains.com/hc/en-us/community/posts/206801595-Is-there-an-API-for-getting-the-color-and-font-style-of-a-character-in-an-editor-
...but it doesn't apply well to what I'm trying to do.
I have created a HighlightVisitor where I want to apply highlights to individual characters in particular PsiElements which are variations upon whatever styling is already being used for those elements, rather than applying the same one-size-fits-all styling in every case. If the text was italic, I want it to remain italic. If the text was green, I want to change it to a different shade of green, or if blue, a different shade of blue.
While each PsiElement itself doesn't seem to carry that information (not surprising), is there any way to query how the current theme would style each kind of element, so I can get retrieve and modify that styling, keeping unchanged what I want unchanged, and modifying only what I want to modify?
The closest I've found (which is not close enough at all) is a way to find the default foreground and background colors for the current theme as a whole.
The point of this is to break up font ligatures, if present, by forcing individual characters to be rendered independently, rather than as strings of characters along with neighboring characters. Font style changes (i.e bold and italic) accomplish this -- but changing styles is an unwanted side effect. Background color changes have no effect. Foreground color changes, however, even barely noticeable changes, do the job, and that's the trick I'd like to be able to use to selectively suppress font ligatures from being rendered in specific contexts.
I've already created a Visual Studio Code extension that does what I'm trying to do here, but the trick was easier to pull off in VSCode because simply applying the same no-op (color = "") styling one character at a time did the job. I didn't need to worry about how characters were otherwise styled, or need to force an alternating change in styling from one character to the next.
This task would certainly be easier if rendering or not rendering ligatures was an attribute that could be applied as a highlighting style, or as a context-based theme option, but as far as I can tell direct control over ligature rendering with IntelliJ is a global setting only.
I'm of course open to any other methods to accomplish my goal if anyone has other ideas about how to do this.
请先登录再写评论。
This is not a complete solution at all, because I'd still need to be able to find out whether the text I'm highlighting was originally styled italic and/or bold, but I think I can at least get away with ignoring colors entirely. This is definitely a hack (and maybe one I shouldn't trust?), but I can use fake font styles, imaginary style bits which aren't assigned to BOLD or ITALIC, and this causes the rendering break-up I'm looking for:
The problem is that this wipes out any italic or bold style that was originally applied to the text. For colors, null effectively says "whatever the color previous was, keep it", but there is no style equivalent for keep existing styles and only adding new styles.
I think I've found a way to solve this problem. I don't know if this code covers all possible cases, but it's working well for now:
private fun searchForLigatures(file: PsiFile, holder: HighlightInfoHolder) {
val text = file.text
val syntaxHighlighter = SyntaxHighlighterFactory.getSyntaxHighlighter(file.language, file.project, file.virtualFile)
val defaultForeground = EditorColorsManager.getInstance().globalScheme.defaultForeground
var index = 0
var match: MatchResult? = null
var phase = 0
while ({ match = globalMatchLigatures.find(text, index); match }() != null && index < text.length) {
val matchIndex = match!!.range.first
val matchText = match!!.groupValues[0]
val elem = file.findElementAt(matchIndex)
if (elem != null) {
val type = elem.node.elementType
val textAttrKeys = syntaxHighlighter.getTokenHighlights(type)
val textAttrs = if (textAttrKeys.isNotEmpty()) holder.colorsScheme.getAttributes(textAttrKeys[0]) else null
val color = textAttrs?.foregroundColor ?: defaultForeground
val colors = getMatchingColors(color)
val fontType = textAttrs?.fontType ?: 0
if (shouldSuppressLigature(type)) {
for (i in matchText.indices) {
holder.add(
HighlightInfo
.newHighlightInfo(ligatureHighlight)
.textAttributes(TextAttributes(colors[phase], null, null, EffectType.BOXED, fontType))
.range(elem, matchIndex + i, matchIndex + i + 1)
.create())
phase = phase xor 1
}
}
}
index = (matchIndex + matchText.length).coerceAtLeast(index + 1)
}
}
The PsiElement instance itself doesn't provide any info about how it has been highlighted, but SyntaxHighlighterFactory, SyntaxHighlighter, EditorColorsManager, and the HighlightInfoHolder, passed to my code because it's registered as a HighlightVisitor (which has a colorScheme) can all be used together to tell me how the elements I want to highlight would normally be highlighted before I modify them.
One thing I'm not quite sure about, however, is that SyntaxHighlighter.getTokenHighlights(type) returns an array of TextAttributeKey. I haven't yet seen more than one key come back, but if that did happen, I wouldn't be sure what sense to make out of it. (Would just one key have priority? Would the info from multiple keys need to be blended together somehow, with either the forward or reverse order of the array expressing priority?)
At any rate, it turns out to work better to use my original idea (alternating, very subtle changes of color) rather than fake font styles. Even though BOLD and ITALIC are essentially bit flags, I think somewhere behind the scenes the particular values of 0, 1, 2, and 3 that arise from those two bit flags are all that work, and trying to use fake bit flags for imaginary text styles just makes the text come out plain, regardless of the BOLD and ITALIC bit values.
The above solution turns out not to be complete, and misses getting the correct colors in certain cases. A partial improvement can be made by using the last item, rather than the first, returned by SyntaxHighlighter.getTokenHighlights(type), or better yet, merge the results from all keys, with the last having highest priority.
...but even this doesn't get enough context-specific TextAttribute information. For that issue, I'll start a new thread.