Skip to content

Commit

Permalink
feat: add support for loading .tmTheme and VSCode JSON themes
Browse files Browse the repository at this point in the history
  • Loading branch information
sebthom committed Feb 13, 2024
1 parent 86ff746 commit ee7dfaa
Show file tree
Hide file tree
Showing 15 changed files with 627 additions and 58 deletions.
6 changes: 3 additions & 3 deletions org.eclipse.tm4e.core/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ Import-Package: org.w3c.css.sac;resolution:=optional,
Bundle-RequiredExecutionEnvironment: JavaSE-17
Export-Package: org.eclipse.tm4e.core,
org.eclipse.tm4e.core.grammar,
org.eclipse.tm4e.core.internal.grammar;x-friends:="org.eclipse.tm4e.core.tests",
org.eclipse.tm4e.core.internal.grammar;x-friends:="org.eclipse.tm4e.core.tests,org.eclipse.tm4e.ui",
org.eclipse.tm4e.core.internal.grammar.tokenattrs;x-friends:="org.eclipse.tm4e.core.tests",
org.eclipse.tm4e.core.internal.matcher;x-friends:="org.eclipse.tm4e.core.tests",
org.eclipse.tm4e.core.internal.oniguruma;x-friends:="org.eclipse.tm4e.languageconfiguration",
org.eclipse.tm4e.core.internal.theme;x-friends:="org.eclipse.tm4e.core.tests",
org.eclipse.tm4e.core.internal.theme.raw;x-friends:="org.eclipse.tm4e.core.tests",
org.eclipse.tm4e.core.internal.theme;x-friends:="org.eclipse.tm4e.core.tests,org.eclipse.tm4e.ui",
org.eclipse.tm4e.core.internal.theme.raw;x-friends:="org.eclipse.tm4e.core.tests,org.eclipse.tm4e.ui",
org.eclipse.tm4e.core.internal.utils;x-friends:="org.eclipse.tm4e.core.tests,org.eclipse.tm4e.registry,org.eclipse.tm4e.languageconfiguration,org.eclipse.tm4e.markdown,org.eclipse.tm4e.ui,org.eclipse.tm4e.ui.tests",
org.eclipse.tm4e.core.model,
org.eclipse.tm4e.core.registry,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
* - Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
* - Sebastian Thomschke - added isBold/isItalic/isStrikethrough/isUnderline
*/
package org.eclipse.tm4e.core.internal.theme;

Expand Down Expand Up @@ -38,16 +39,16 @@ public static String fontStyleToString(final int fontStyle) {
}

final var style = new StringBuilder();
if ((fontStyle & Italic) == Italic) {
if (isItalic(fontStyle)) {
style.append("italic ");
}
if ((fontStyle & Bold) == Bold) {
if (isBold(fontStyle)) {
style.append("bold ");
}
if ((fontStyle & Underline) == Underline) {
if (isUnderline(fontStyle)) {
style.append("underline ");
}
if ((fontStyle & Strikethrough) == Strikethrough) {
if (isStrikethrough(fontStyle)) {
style.append("strikethrough ");
}
if (style.isEmpty()) {
Expand All @@ -57,6 +58,22 @@ public static String fontStyleToString(final int fontStyle) {
return style.toString();
}

public static boolean isBold(final int fontStyle) {
return (fontStyle & Bold) == Bold;
}

public static boolean isItalic(final int fontStyle) {
return (fontStyle & Italic) == Italic;
}

public static boolean isUnderline(final int fontStyle) {
return (fontStyle & Underline) == Underline;
}

public static boolean isStrikethrough(final int fontStyle) {
return (fontStyle & Strikethrough) == Strikethrough;
}

private FontStyle() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
* github.com/microsoft/vscode-textmate/blob/main/src/theme.ts</a>
*/
public class StyleAttributes {
private static final StyleAttributes NO_STYLE = new StyleAttributes(-1, 0, 0);
public static final StyleAttributes NO_STYLE = new StyleAttributes(-1, 0, 0);

/** @see FontStyle */
public final int fontStyle;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,14 @@
public final class Theme {

public static Theme createFromRawTheme(@Nullable final IRawTheme source, @Nullable final List<String> colorMap) {
return createFromParsedTheme(parseTheme(source), colorMap);
final var theme = createFromParsedTheme(parseTheme(source), colorMap);

// custom tm4e code, not from upstream
if (source != null) {
theme.editorColors = source.getEditorColors();
}

return theme;
}

public static Theme createFromParsedTheme(final List<ParsedThemeRule> source, @Nullable final List<String> colorMap) {
Expand All @@ -52,6 +59,7 @@ public static Theme createFromParsedTheme(final List<ParsedThemeRule> source, @N
private final ColorMap _colorMap;
private final StyleAttributes _defaults;
private final ThemeTrieElement _root;
private Map<String, String> editorColors = Collections.emptyMap(); // custom tm4e code, not from upstream

public Theme(final ColorMap colorMap, final StyleAttributes defaults, final ThemeTrieElement root) {
this._colorMap = colorMap;
Expand All @@ -67,6 +75,10 @@ public StyleAttributes getDefaults() {
return this._defaults;
}

public Map<String, String> getEditorColors() { // custom tm4e code, not from upstream
return editorColors;
}

public @Nullable StyleAttributes match(@Nullable final ScopeStack scopePath) {
if (scopePath == null) {
return this._defaults;
Expand Down Expand Up @@ -153,11 +165,11 @@ public static List<ParsedThemeRule> parseTheme(@Nullable final IRawTheme source)
}

int fontStyle = FontStyle.NotSet;
final var settingsFontStyle = entrySetting.getFontStyle();
if (settingsFontStyle instanceof final String style) {
final String settingsFontStyle = entrySetting.getFontStyle();
if (settingsFontStyle != null) {
fontStyle = FontStyle.None;

final var segments = StringUtils.splitToArray(style, ' ');
final var segments = StringUtils.splitToArray(settingsFontStyle, ' ');
for (final var segment : segments) {
switch (segment) {
case "italic":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
package org.eclipse.tm4e.core.internal.theme.raw;

import java.util.Collection;
import java.util.Map;

import org.eclipse.jdt.annotation.Nullable;

Expand All @@ -27,4 +28,5 @@ public interface IRawTheme {
@Nullable
Collection<IRawThemeSetting> getSettings();

Map<String, String> getEditorColors(); // custom tm4e code, not from upstream
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
package org.eclipse.tm4e.core.internal.theme.raw;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;

import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tm4e.core.internal.parser.PropertySettable;
Expand All @@ -32,7 +35,30 @@ public final class RawTheme extends PropertySettable.HashMap<@Nullable Object>
@Override
@SuppressWarnings("unchecked")
public @Nullable Collection<IRawThemeSetting> getSettings() {
return (Collection<IRawThemeSetting>) super.get("settings");
// vscode themes only
if (get("tokenColors") instanceof final Collection settings)
return settings;

return (Collection<IRawThemeSetting>) get("settings");
}

// custom tm4e code, not from upstream
@Override
@SuppressWarnings("unchecked")
public Map<String, String> getEditorColors() {
// vscode themes only
if (get("colors") instanceof final Map colors)
return colors;

final var settings = getSettings();
return settings == null
? Collections.emptyMap()
: settings.stream()
.filter(s -> s.getScope() == null)
.map(s -> ((Map<String, Map<String, String>>) s).get("settings"))
.filter(Objects::nonNull)
.findFirst()
.orElse(Collections.emptyMap());
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package org.eclipse.tm4e.core.internal.utils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
Expand Down Expand Up @@ -75,6 +76,10 @@ public static <T> T getLastElement(final List<T> list) {
return list.get(list.size() - 1);
}

public static <T> Collection<T> nullToEmpty(@Nullable final Collection<T> coll) {
return coll == null ? Collections.emptyList() : coll;
}

public static <T> List<T> nullToEmpty(@Nullable final List<T> list) {
return list == null ? Collections.emptyList() : list;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,26 @@
*
* Contributors:
* - Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
* - Sebastian Thomschke (Vegard IT) - add hashCode/equals methods
* - Sebastian Thomschke (Vegard IT) - add methods hashCode/equals, fromHex(String)
*/
package org.eclipse.tm4e.core.theme;

import org.eclipse.jdt.annotation.Nullable;

public class RGB {

public static @Nullable RGB fromHex(final @Nullable String hex) {
if (hex == null || hex.isBlank())
return null;

final var offset = hex.startsWith("#") ? 1 : 0;
final int r = Integer.parseInt(hex.substring(offset + 0, offset + 2), 16);
final int g = Integer.parseInt(hex.substring(offset + 2, offset + 4), 16);
final int b = Integer.parseInt(hex.substring(offset + 4, offset + 6), 16);

return new RGB(r, g, b);
}

public final int red;
public final int green;
public final int blue;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/**
* Copyright (c) 2024 Vegard IT GmbH and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Sebastian Thomschke (Vegard IT) - initial implementation
*/
package org.eclipse.tm4e.core.internal.theme;

import static org.eclipse.tm4e.core.internal.utils.NullSafetyHelper.castNonNull;
import static org.junit.jupiter.api.Assertions.assertEquals;

import java.nio.file.Path;

import org.eclipse.tm4e.core.internal.grammar.ScopeStack;
import org.eclipse.tm4e.core.internal.theme.raw.RawThemeReader;
import org.eclipse.tm4e.core.registry.IThemeSource;
import org.eclipse.tm4e.core.registry.IThemeSource.ContentType;
import org.junit.jupiter.api.Test;

class ThemeTypeTest {

@Test
void testTMPlistTheme() throws Exception {

final var rawTheme = RawThemeReader.readTheme(
IThemeSource.fromFile(Path.of("../org.eclipse.tm4e.core.tests/src/main/resources/test-cases/themes/QuietLight.tmTheme")));

assertEquals("Quiet Light", rawTheme.getName());
final var theme = Theme.createFromRawTheme(rawTheme, null);
final var colors = theme.getColorMap();

final var editorColors = rawTheme.getEditorColors();
assertEquals("#F5F5F5", editorColors.get("background"));
assertEquals("#000000", editorColors.get("caret"));
assertEquals("#333333", editorColors.get("foreground"));
assertEquals("#AAAAAA", editorColors.get("invisibles"));
assertEquals("#E4F6D4", editorColors.get("lineHighlight"));
assertEquals("#C9D0D9", editorColors.get("selection"));

var attrs = castNonNull(theme.match(ScopeStack.from()));
assertEquals("#333333", colors.get(attrs.foregroundId));
assertEquals("#F5F5F5", colors.get(attrs.backgroundId));

attrs = castNonNull(theme.match(ScopeStack.from("comment")));
assertEquals("#AAAAAA", colors.get(attrs.foregroundId));
assertEquals(FontStyle.Italic, attrs.fontStyle & FontStyle.Italic);

attrs = castNonNull(theme.match(ScopeStack.from("punctuation.definition.comment")));
assertEquals("#AAAAAA", colors.get(attrs.foregroundId));
assertEquals(FontStyle.Italic, attrs.fontStyle & FontStyle.Italic);

attrs = castNonNull(theme.match(ScopeStack.from("keyword")));
assertEquals("#4B83CD", colors.get(attrs.foregroundId));
attrs = castNonNull(theme.match(ScopeStack.from("keyword.operator")));
assertEquals("#777777", colors.get(attrs.foregroundId));
}

@Test
void testTMJsonTheme() throws Exception {
final var rawTheme = RawThemeReader.readTheme(
IThemeSource.fromFile(Path.of("../org.eclipse.tm4e.core.tests/src/main/resources/test-cases/themes/dark_vs.json")));

assertEquals("Dark Visual Studio", rawTheme.getName());
final var theme = Theme.createFromRawTheme(rawTheme, null);
final var colors = theme.getColorMap();

final var editorColors = rawTheme.getEditorColors();
assertEquals("#D4D4D4", editorColors.get("foreground"));
assertEquals("#1E1E1E", editorColors.get("background"));

var attrs = castNonNull(theme.match(ScopeStack.from()));
assertEquals("#D4D4D4", colors.get(attrs.foregroundId));
assertEquals("#1E1E1E", colors.get(attrs.backgroundId));

attrs = castNonNull(theme.match(ScopeStack.from("comment")));
assertEquals("#608B4E", colors.get(attrs.foregroundId));
assertEquals(FontStyle.Italic, attrs.fontStyle & FontStyle.Italic);

attrs = castNonNull(theme.match(ScopeStack.from("keyword")));
assertEquals("#569CD6", colors.get(attrs.foregroundId));
attrs = castNonNull(theme.match(ScopeStack.from("keyword.operator")));
assertEquals("#D4D4D4", colors.get(attrs.foregroundId));
attrs = castNonNull(theme.match(ScopeStack.from("keyword.operator.expression")));
assertEquals("#569CD6", colors.get(attrs.foregroundId));
}

@Test
void testVSCodeJsonTheme() throws Exception {
final var rawTheme = RawThemeReader.readTheme(
IThemeSource.fromString(ContentType.JSON, """
{
"name": "My theme",
"tokenColors": [
{
"settings": {
"foreground": "#ABCDEF",
"background": "#012345"
}
},
{
"name": "Comment",
"scope": "comment",
"settings": {
"fontStyle": "italic",
"foreground": "#FF0000"
}
},
{
"name": "Keyword",
"scope": "keyword",
"settings": {
"foreground": "#00FF00"
}
}
],
"colors": {
"editor.foreground": "#FFFFFF",
"editor.background": "#000000",
"editor.selectionForeground": "#EEEEEE",
"editor.selectionBackground": "#333333",
"editor.lineHighlightBackground": "#999999"
},
"semanticHighlighting": true
}
"""));

assertEquals("My theme", rawTheme.getName());
final var theme = Theme.createFromRawTheme(rawTheme, null);
final var colors = theme.getColorMap();

final var editorColors = rawTheme.getEditorColors();
assertEquals("#FFFFFF", editorColors.get("editor.foreground"));
assertEquals("#000000", editorColors.get("editor.background"));

var attrs = castNonNull(theme.match(ScopeStack.from()));
assertEquals("#ABCDEF", colors.get(attrs.foregroundId));
assertEquals("#012345", colors.get(attrs.backgroundId));

attrs = castNonNull(theme.match(ScopeStack.from("comment")));
assertEquals("#FF0000", colors.get(attrs.foregroundId));
assertEquals(FontStyle.Italic, attrs.fontStyle & FontStyle.Italic);

attrs = castNonNull(theme.match(ScopeStack.from("keyword.something")));
assertEquals("#00FF00", colors.get(attrs.foregroundId));
}
}
Loading

0 comments on commit ee7dfaa

Please sign in to comment.