Skip to content

Commit afe66c7

Browse files
committed
feat: add option to display TextMate token info in hover
1 parent 413a0dd commit afe66c7

14 files changed

Lines changed: 217 additions & 30 deletions

File tree

org.eclipse.tm4e.core/src/main/java/org/eclipse/tm4e/core/model/TMModel.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.lang.System.Logger;
2121
import java.time.Duration;
2222
import java.util.ArrayList;
23+
import java.util.Collections;
2324
import java.util.List;
2425
import java.util.Objects;
2526
import java.util.concurrent.BlockingQueue;
@@ -255,7 +256,7 @@ private void revalidateTokens() {
255256
// check if complete line was tokenized
256257
if (r.stoppedEarly) {
257258
// treat the rest of the line as one default token
258-
r.tokens.add(new TMToken(r.actualStopOffset, ""));
259+
r.tokens.add(new TMToken(r.actualStopOffset, "", Collections.emptyList()));
259260
// Use the line's starting state as end state in case of incomplete tokenization
260261
r.endState = currLineTokens.startState;
261262
}

org.eclipse.tm4e.core/src/main/java/org/eclipse/tm4e/core/model/TMToken.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717
package org.eclipse.tm4e.core.model;
1818

19+
import java.util.List;
20+
1921
import org.eclipse.jdt.annotation.Nullable;
2022

2123
/**
@@ -28,10 +30,12 @@ public final class TMToken {
2830
public final int startIndex;
2931
public final String type;
3032
// public readonly language: string
33+
public final List<String> scopes;
3134

32-
public TMToken(final int startIndex, final String type) {
35+
public TMToken(final int startIndex, final String type, final List<String> scopes) {
3336
this.startIndex = startIndex;
3437
this.type = type;
38+
this.scopes = scopes;
3539
}
3640

3741
@Override

org.eclipse.tm4e.core/src/main/java/org/eclipse/tm4e/core/model/TMTokenizationSupport.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.eclipse.jdt.annotation.Nullable;
3030
import org.eclipse.tm4e.core.grammar.IGrammar;
3131
import org.eclipse.tm4e.core.grammar.IStateStack;
32+
import org.eclipse.tm4e.core.grammar.IToken;
3233
import org.eclipse.tm4e.core.internal.grammar.StateStack;
3334
import org.eclipse.tm4e.core.internal.utils.MoreCollections;
3435
import org.eclipse.tm4e.core.internal.utils.StringUtils;
@@ -83,13 +84,12 @@ public TokenizationResult tokenize(final String line,
8384
// Create the result early and fill in the tokens later
8485
final var tmTokens = new ArrayList<TMToken>(tokens.length < 10 ? tokens.length : 10);
8586
String lastTokenType = null;
86-
for (final var token : tokens) {
87+
for (final IToken token : tokens) {
8788
final String tokenType = decodeTextMateTokenCached.apply(decodeMap, token.getScopes());
8889

8990
// do not push a new token if the type is exactly the same (also helps with ligatures)
9091
if (!tokenType.equals(lastTokenType)) {
91-
final int tokenStartIndex = token.getStartIndex();
92-
tmTokens.add(new TMToken(tokenStartIndex + offsetDelta, tokenType));
92+
tmTokens.add(new TMToken(token.getStartIndex() + offsetDelta, tokenType, token.getScopes()));
9393
lastTokenType = tokenType;
9494
}
9595
}

org.eclipse.tm4e.ui/plugin.xml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
path="./themes/Eclipse-light.css" />
3838
<theme id="org.eclipse.tm4e.ui.themes.WtpXmlClassic"
3939
name="%Theme.WtpXmlClassic.name"
40-
path="./themes/WTP-XML-Classic.css" />
40+
path="./themes/WTP-XML-Classic.css" />
4141
<!-- "Dark" themes -->
4242
<theme id="org.eclipse.tm4e.ui.themes.Dark"
4343
name="%Theme.Dark.name"
@@ -138,4 +138,10 @@
138138
<super type="org.eclipse.tm4e.ui.textmarker"/>
139139
<persistent value="true"/>
140140
</extension>
141+
142+
<extension point="org.eclipse.ui.genericeditor.hoverProviders">
143+
<hoverProvider
144+
class="org.eclipse.tm4e.ui.internal.hover.TMTokenTextHover"
145+
contentType="org.eclipse.core.runtime.text" />
146+
</extension>
141147
</plugin>

org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/TMUIPlugin.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ public class TMUIPlugin extends AbstractUIPlugin {
4343
@Nullable
4444
private static volatile TMUIPlugin plugin;
4545

46+
public static boolean getPreference(final String key, final boolean defaultValue) {
47+
return Platform.getPreferencesService().getBoolean(PLUGIN_ID, key, defaultValue, null /* = search in all available scopes */);
48+
}
49+
4650
public static @Nullable String getPreference(final String key, final @Nullable String defaultValue) {
4751
return Platform.getPreferencesService().getString(PLUGIN_ID, key, defaultValue, null /* = search in all available scopes */);
4852
}

org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/TMUIMessages.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public final class TMUIMessages extends NLS {
3434
public static String TextMatePreferencePage_LanguageConfigurationRelatedLink;
3535
public static String TextMatePreferencePage_TaskTagsRelatedLink;
3636
public static String TextMatePreferencePage_ThemeRelatedLink;
37+
public static String TextMatePreferencePage_ShowTextMateTokenInfoHover;
3738

3839
// Grammar preferences page
3940
public static String GrammarPreferencePage_title;

org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/TMUIMessages.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ TextMatePreferencePage_GrammarRelatedLink=See <a>''{0}''</a> for associating edi
2121
TextMatePreferencePage_LanguageConfigurationRelatedLink=See <a>''{0}''</a> for associating editors with language configurations.
2222
TextMatePreferencePage_TaskTagsRelatedLink=See <a>''{0}''</a> for task tags configuration.
2323
TextMatePreferencePage_ThemeRelatedLink=See <a>''{0}''</a> for associating editors with themes.
24+
TextMatePreferencePage_ShowTextMateTokenInfoHover=Show TextMate token info in hovers.
2425

2526
GrammarPreferencePage_title=TextMate grammars
2627
GrammarPreferencePage_description=Register, configure or remove TextMate grammars:
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Vegard IT GmbH and others.
3+
* This program and the accompanying materials are made
4+
* available under the terms of the Eclipse Public License 2.0
5+
* which is available at https://www.eclipse.org/legal/epl-2.0/
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Sebastian Thomschke (Vegard IT) - initial implementation
11+
*******************************************************************************/
12+
package org.eclipse.tm4e.ui.internal.hover;
13+
14+
import org.eclipse.jdt.annotation.NonNullByDefault;
15+
import org.eclipse.jdt.annotation.Nullable;
16+
import org.eclipse.jface.text.AbstractReusableInformationControlCreator;
17+
import org.eclipse.jface.text.BadLocationException;
18+
import org.eclipse.jface.text.DefaultInformationControl;
19+
import org.eclipse.jface.text.IDocument;
20+
import org.eclipse.jface.text.IInformationControl;
21+
import org.eclipse.jface.text.IInformationControlCreator;
22+
import org.eclipse.jface.text.IRegion;
23+
import org.eclipse.jface.text.ITextHover;
24+
import org.eclipse.jface.text.ITextHoverExtension;
25+
import org.eclipse.jface.text.ITextViewer;
26+
import org.eclipse.jface.text.Region;
27+
import org.eclipse.swt.widgets.Shell;
28+
import org.eclipse.tm4e.core.model.TMToken;
29+
import org.eclipse.tm4e.ui.internal.model.TMDocumentModel;
30+
import org.eclipse.tm4e.ui.internal.model.TMModelManager;
31+
import org.eclipse.tm4e.ui.internal.preferences.PreferenceHelper;
32+
import org.eclipse.ui.editors.text.EditorsUI;
33+
34+
public class TMTokenTextHover implements ITextHover, ITextHoverExtension {
35+
36+
private static final class RegionWithTMToken extends Region {
37+
final TMToken token;
38+
final String tokenText;
39+
40+
RegionWithTMToken(final int offset, final int length, final String tokenText, final TMToken token) {
41+
super(offset, length);
42+
this.tokenText = tokenText;
43+
this.token = token;
44+
}
45+
}
46+
47+
@Override
48+
public IInformationControlCreator getHoverControlCreator() {
49+
// setup a hover control that interprets basic HTML input
50+
return new AbstractReusableInformationControlCreator() {
51+
@Override
52+
protected IInformationControl doCreateInformationControl(final @NonNullByDefault({}) Shell parent) {
53+
return new DefaultInformationControl(parent, EditorsUI.getTooltipAffordanceString());
54+
}
55+
};
56+
}
57+
58+
@Override
59+
public @Nullable String getHoverInfo(final @NonNullByDefault({}) ITextViewer textViewer,
60+
final @NonNullByDefault({}) IRegion hoverRegion) {
61+
if (hoverRegion instanceof final RegionWithTMToken regionWithToken) {
62+
final var text = regionWithToken.tokenText.replace(' ', '·').replace('\t', '→');
63+
return "<b>" + text + "</b> (" + text.length()
64+
+ " chars)<br>"
65+
+ "<br>"
66+
+ "<b>Token Type:</b> " + regionWithToken.token.type + "<br>"
67+
+ "<b>TextMate Scopes:</b> <li>" + String.join("<li>", regionWithToken.token.scopes);
68+
}
69+
return null;
70+
}
71+
72+
@Override
73+
public @Nullable IRegion getHoverRegion(final @NonNullByDefault({}) ITextViewer textViewer, final int offset) {
74+
if (!PreferenceHelper.isTMTokenHoverEnabled())
75+
return null;
76+
77+
final @Nullable IDocument doc = textViewer.getDocument();
78+
if (doc == null)
79+
return null;
80+
81+
final TMDocumentModel model = TMModelManager.INSTANCE.getConnectedModel(doc);
82+
if (model == null)
83+
return null;
84+
85+
try {
86+
// retrieve parsed TM tokens of the hovered line
87+
final int lineIndex = doc.getLineOfOffset(offset);
88+
final var tokens = model.getLineTokens(lineIndex);
89+
if (tokens == null)
90+
return null;
91+
92+
// find the TM token at the hover position
93+
final int lineStartOffset = doc.getLineOffset(lineIndex);
94+
TMToken hoveredToken = null;
95+
TMToken nextToken = null;
96+
for (final TMToken token : tokens) {
97+
if (token.startIndex <= offset - lineStartOffset) {
98+
hoveredToken = token;
99+
} else {
100+
nextToken = token;
101+
break;
102+
}
103+
}
104+
if (hoveredToken == null)
105+
return null;
106+
107+
final int regionOffset = lineStartOffset + hoveredToken.startIndex;
108+
final int regionLength = nextToken == null
109+
? doc.getLineLength(lineIndex) - hoveredToken.startIndex
110+
: nextToken.startIndex - hoveredToken.startIndex;
111+
return new RegionWithTMToken(regionOffset, regionLength, doc.get(regionOffset, regionLength), hoveredToken);
112+
} catch (final BadLocationException e) {
113+
return null;
114+
}
115+
}
116+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@NonNullByDefault
2+
package org.eclipse.tm4e.ui.internal.hover;
3+
4+
import org.eclipse.jdt.annotation.NonNullByDefault;

org.eclipse.tm4e.ui/src/main/java/org/eclipse/tm4e/ui/internal/model/TMModelManager.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.util.Map;
1717
import java.util.concurrent.ConcurrentHashMap;
1818

19+
import org.eclipse.jdt.annotation.Nullable;
1920
import org.eclipse.jface.text.IDocument;
2021
import org.eclipse.tm4e.ui.model.ITMModelManager;
2122

@@ -44,6 +45,10 @@ public void disconnect(final IDocument document) {
4445
}
4546
}
4647

48+
public @Nullable TMDocumentModel getConnectedModel(final IDocument document) {
49+
return models.get(document);
50+
}
51+
4752
@Override
4853
public boolean isConnected(final IDocument document) {
4954
return models.containsKey(document);

0 commit comments

Comments
 (0)