From a0f7bdc0501657829720334f13cd3279d85bf43c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Nov 2025 05:45:39 +0000 Subject: [PATCH 1/2] Initial plan From 9b7177a03af4fe9cf0407d8affe7533554acf957 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Nov 2025 05:54:59 +0000 Subject: [PATCH 2/2] Add GnomeTerminalColorParser and OS Defaults preset Co-authored-by: laeubi <1331477+laeubi@users.noreply.github.com> --- .../control/impl/TerminalMessages.java | 1 + .../control/impl/TerminalMessages.properties | 1 + .../preferences/GnomeTerminalColorParser.java | 241 ++++++++++++++++++ .../preferences/TerminalColorPresets.java | 36 +++ .../GnomeTerminalColorParserTest.java | 198 ++++++++++++++ 5 files changed, 477 insertions(+) create mode 100644 terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/preferences/GnomeTerminalColorParser.java create mode 100644 terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/internal/preferences/GnomeTerminalColorParserTest.java diff --git a/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/control/impl/TerminalMessages.java b/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/control/impl/TerminalMessages.java index 56ecd05699a..fcac8e14f68 100644 --- a/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/control/impl/TerminalMessages.java +++ b/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/control/impl/TerminalMessages.java @@ -22,6 +22,7 @@ public class TerminalMessages extends NLS { public static String TerminalColorPresets_EclipseDark; public static String TerminalColorPresets_EclipseLight; + public static String TerminalColorPresets_OsDefaults; public static String TerminalColorPresets_TerminalDefaults; public static String TerminalColorsFieldEditor_Background; public static String TerminalColorsFieldEditor_Black; diff --git a/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/control/impl/TerminalMessages.properties b/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/control/impl/TerminalMessages.properties index 96b277c2e77..8b7c1ba5e05 100644 --- a/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/control/impl/TerminalMessages.properties +++ b/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/control/impl/TerminalMessages.properties @@ -23,6 +23,7 @@ TerminalColorPresets_EclipseLight=Eclipse Light TerminalColorPresets_TerminalDefaults=Terminal Defaults TerminalColorPresets_EclipseDark=Eclipse Dark +TerminalColorPresets_OsDefaults=OS Defaults TerminalColorsFieldEditor_Background=Background TerminalColorsFieldEditor_Black=Black TerminalColorsFieldEditor_Blue=Blue diff --git a/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/preferences/GnomeTerminalColorParser.java b/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/preferences/GnomeTerminalColorParser.java new file mode 100644 index 00000000000..fdd36a95f4b --- /dev/null +++ b/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/preferences/GnomeTerminalColorParser.java @@ -0,0 +1,241 @@ +/******************************************************************************* + * Copyright (c) 2025 Contributors to the Eclipse Foundation. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.terminal.internal.preferences; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.eclipse.swt.graphics.RGB; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +/** + * Parser for GNOME Terminal color preferences from gschema.xml files. + * + * @since 5.0 + */ +public class GnomeTerminalColorParser { + + private static final String GNOME_TERMINAL_SCHEMA_PATH = "/usr/share/glib-2.0/schemas/org.gnome.Terminal.gschema.xml"; + private static final Pattern RGB_PATTERN = Pattern.compile("rgb\\((\\d+),(\\d+),(\\d+)\\)"); + + /** + * Result of parsing GNOME Terminal color preferences. + */ + public static class GnomeTerminalColors { + private final List palette; + private final RGB foreground; + private final RGB background; + private final RGB selectionForeground; + private final RGB selectionBackground; + + public GnomeTerminalColors(List palette, RGB foreground, RGB background, RGB selectionForeground, + RGB selectionBackground) { + this.palette = palette; + this.foreground = foreground; + this.background = background; + this.selectionForeground = selectionForeground; + this.selectionBackground = selectionBackground; + } + + public List getPalette() { + return palette; + } + + public RGB getForeground() { + return foreground; + } + + public RGB getBackground() { + return background; + } + + public RGB getSelectionForeground() { + return selectionForeground; + } + + public RGB getSelectionBackground() { + return selectionBackground; + } + } + + /** + * Checks if the GNOME Terminal schema file exists. + * + * @return true if the schema file exists + */ + public static boolean isAvailable() { + Path schemaPath = Paths.get(GNOME_TERMINAL_SCHEMA_PATH); + return Files.exists(schemaPath) && Files.isReadable(schemaPath); + } + + /** + * Parses the GNOME Terminal color preferences. + * + * @return Optional containing the parsed colors, or empty if parsing fails + */ + public static Optional parse() { + return parse(GNOME_TERMINAL_SCHEMA_PATH); + } + + /** + * Parses the GNOME Terminal color preferences from the specified file. + * + * @param schemaPath path to the gschema.xml file + * @return Optional containing the parsed colors, or empty if parsing fails + */ + public static Optional parse(String schemaPath) { + File schemaFile = new File(schemaPath); + if (!schemaFile.exists() || !schemaFile.canRead()) { + return Optional.empty(); + } + + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + factory.setXIncludeAware(false); + factory.setExpandEntityReferences(false); + + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.parse(schemaFile); + document.getDocumentElement().normalize(); + + // Find all key elements + NodeList keyNodes = document.getElementsByTagName("key"); + + List palette = null; + RGB foreground = null; + RGB background = null; + RGB highlightForeground = null; + RGB highlightBackground = null; + Boolean highlightColorsSet = null; + + // First pass: collect all values + for (int i = 0; i < keyNodes.getLength(); i++) { + Element keyElement = (Element) keyNodes.item(i); + String keyName = keyElement.getAttribute("name"); + + NodeList defaultNodes = keyElement.getElementsByTagName("default"); + if (defaultNodes.getLength() == 0) { + continue; + } + + String defaultValue = defaultNodes.item(0).getTextContent().trim(); + + switch (keyName) { + case "palette": + palette = parsePalette(defaultValue); + break; + case "foreground-color": + foreground = parseColor(defaultValue); + break; + case "background-color": + background = parseColor(defaultValue); + break; + case "highlight-colors-set": + highlightColorsSet = Boolean.parseBoolean(defaultValue); + break; + case "highlight-foreground-color": + highlightForeground = parseColor(defaultValue); + break; + case "highlight-background-color": + highlightBackground = parseColor(defaultValue); + break; + } + } + + // Determine selection colors based on highlight-colors-set flag + RGB selectionForeground; + RGB selectionBackground; + if (Boolean.TRUE.equals(highlightColorsSet) && highlightForeground != null && highlightBackground != null) { + selectionForeground = highlightForeground; + selectionBackground = highlightBackground; + } else { + // Default to swapped foreground/background + selectionForeground = background != null ? background : new RGB(0, 0, 0); + selectionBackground = foreground != null ? foreground : new RGB(255, 255, 255); + } + + // Verify we have the minimum required data + if (palette != null && palette.size() >= 16 && foreground != null && background != null) { + return Optional.of(new GnomeTerminalColors(palette, foreground, background, selectionForeground, + selectionBackground)); + } + + } catch (Exception e) { + // Log error but don't throw - just return empty + System.err.println("Failed to parse GNOME Terminal schema: " + e.getMessage()); + } + + return Optional.empty(); + } + + /** + * Parses a palette string from the gschema file. + * Expected format: ['rgb(r,g,b)', 'rgb(r,g,b)', ...] + * + * @param paletteString the palette string + * @return list of RGB colors + */ + private static List parsePalette(String paletteString) { + List colors = new ArrayList<>(); + Matcher matcher = RGB_PATTERN.matcher(paletteString); + + while (matcher.find()) { + try { + int r = Integer.parseInt(matcher.group(1)); + int g = Integer.parseInt(matcher.group(2)); + int b = Integer.parseInt(matcher.group(3)); + colors.add(new RGB(r, g, b)); + } catch (NumberFormatException e) { + // Skip invalid color + } + } + + return colors; + } + + /** + * Parses a single color string from the gschema file. + * Expected format: 'rgb(r,g,b)' or "rgb(r,g,b)" + * + * @param colorString the color string + * @return RGB color or null if parsing fails + */ + private static RGB parseColor(String colorString) { + Matcher matcher = RGB_PATTERN.matcher(colorString); + if (matcher.find()) { + try { + int r = Integer.parseInt(matcher.group(1)); + int g = Integer.parseInt(matcher.group(2)); + int b = Integer.parseInt(matcher.group(3)); + return new RGB(r, g, b); + } catch (NumberFormatException e) { + return null; + } + } + return null; + } +} diff --git a/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/preferences/TerminalColorPresets.java b/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/preferences/TerminalColorPresets.java index 56474e3bce3..f020a8d5bad 100644 --- a/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/preferences/TerminalColorPresets.java +++ b/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/preferences/TerminalColorPresets.java @@ -126,6 +126,42 @@ public RGB getRGB(TerminalColor terminalColor) { presets.add(new Preset(TerminalMessages.TerminalColorPresets_EclipseDark) // .set(FOREGROUND, getDefaultPreset().getRGB(WHITE)) // .set(BACKGROUND, getDefaultPreset().getRGB(BLACK))); + + // Add OS Defaults preset if GNOME Terminal schema is available + if (GnomeTerminalColorParser.isAvailable()) { + GnomeTerminalColorParser.parse().ifPresent(gnomeColors -> { + Preset osPreset = new Preset(TerminalMessages.TerminalColorPresets_OsDefaults); + + // Set palette colors + java.util.List palette = gnomeColors.getPalette(); + if (palette.size() >= 16) { + osPreset.set(BLACK, palette.get(0)); + osPreset.set(RED, palette.get(1)); + osPreset.set(GREEN, palette.get(2)); + osPreset.set(YELLOW, palette.get(3)); + osPreset.set(BLUE, palette.get(4)); + osPreset.set(MAGENTA, palette.get(5)); + osPreset.set(CYAN, palette.get(6)); + osPreset.set(WHITE, palette.get(7)); + osPreset.set(BRIGHT_BLACK, palette.get(8)); + osPreset.set(BRIGHT_RED, palette.get(9)); + osPreset.set(BRIGHT_GREEN, palette.get(10)); + osPreset.set(BRIGHT_YELLOW, palette.get(11)); + osPreset.set(BRIGHT_BLUE, palette.get(12)); + osPreset.set(BRIGHT_MAGENTA, palette.get(13)); + osPreset.set(BRIGHT_CYAN, palette.get(14)); + osPreset.set(BRIGHT_WHITE, palette.get(15)); + } + + // Set general colors + osPreset.set(FOREGROUND, gnomeColors.getForeground()); + osPreset.set(BACKGROUND, gnomeColors.getBackground()); + osPreset.set(SELECTION_FOREGROUND, gnomeColors.getSelectionForeground()); + osPreset.set(SELECTION_BACKGROUND, gnomeColors.getSelectionBackground()); + + presets.add(osPreset); + }); + } } public Preset getDefaultPreset() { diff --git a/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/internal/preferences/GnomeTerminalColorParserTest.java b/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/internal/preferences/GnomeTerminalColorParserTest.java new file mode 100644 index 00000000000..d33acd7dc75 --- /dev/null +++ b/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/internal/preferences/GnomeTerminalColorParserTest.java @@ -0,0 +1,198 @@ +/******************************************************************************* + * Copyright (c) 2025 Contributors to the Eclipse Foundation. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.terminal.internal.preferences; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; +import java.util.Optional; + +import org.eclipse.swt.graphics.RGB; +import org.eclipse.terminal.internal.preferences.GnomeTerminalColorParser.GnomeTerminalColors; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class GnomeTerminalColorParserTest { + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + @Test + public void testParseValidSchema() throws IOException { + String schemaContent = """ + + + + + ['rgb(0,0,0)', 'rgb(205,0,0)', 'rgb(0,205,0)', 'rgb(205,205,0)', 'rgb(0,0,238)', 'rgb(205,0,205)', 'rgb(0,205,205)', 'rgb(229,229,229)', 'rgb(127,127,127)', 'rgb(255,0,0)', 'rgb(0,255,0)', 'rgb(255,255,0)', 'rgb(92,92,255)', 'rgb(255,0,255)', 'rgb(0,255,255)', 'rgb(255,255,255)'] + + + 'rgb(255,255,255)' + + + 'rgb(0,0,0)' + + + false + + + 'rgb(0,0,0)' + + + 'rgb(255,255,255)' + + + + """; + + File schemaFile = tempFolder.newFile("test-schema.xml"); + Files.writeString(schemaFile.toPath(), schemaContent); + + Optional result = GnomeTerminalColorParser.parse(schemaFile.getAbsolutePath()); + + assertTrue("Parser should successfully parse valid schema", result.isPresent()); + GnomeTerminalColors colors = result.get(); + + // Check palette + List palette = colors.getPalette(); + assertEquals("Palette should have 16 colors", 16, palette.size()); + assertEquals("First color should be black", new RGB(0, 0, 0), palette.get(0)); + assertEquals("Second color should be red", new RGB(205, 0, 0), palette.get(1)); + assertEquals("Last color should be white", new RGB(255, 255, 255), palette.get(15)); + + // Check foreground and background + assertEquals("Foreground should be white", new RGB(255, 255, 255), colors.getForeground()); + assertEquals("Background should be black", new RGB(0, 0, 0), colors.getBackground()); + + // When highlight-colors-set is false, selection colors should default to foreground/background + assertEquals("Selection foreground should default to background", new RGB(0, 0, 0), + colors.getSelectionForeground()); + assertEquals("Selection background should default to foreground", new RGB(255, 255, 255), + colors.getSelectionBackground()); + } + + @Test + public void testParseWithCustomHighlightColors() throws IOException { + String schemaContent = """ + + + + + ['rgb(0,0,0)', 'rgb(205,0,0)', 'rgb(0,205,0)', 'rgb(205,205,0)', 'rgb(0,0,238)', 'rgb(205,0,205)', 'rgb(0,205,205)', 'rgb(229,229,229)', 'rgb(127,127,127)', 'rgb(255,0,0)', 'rgb(0,255,0)', 'rgb(255,255,0)', 'rgb(92,92,255)', 'rgb(255,0,255)', 'rgb(0,255,255)', 'rgb(255,255,255)'] + + + 'rgb(255,255,255)' + + + 'rgb(0,0,0)' + + + true + + + 'rgb(100,100,100)' + + + 'rgb(200,200,200)' + + + + """; + + File schemaFile = tempFolder.newFile("test-schema-custom-highlight.xml"); + Files.writeString(schemaFile.toPath(), schemaContent); + + Optional result = GnomeTerminalColorParser.parse(schemaFile.getAbsolutePath()); + + assertTrue("Parser should successfully parse schema with custom highlight colors", result.isPresent()); + GnomeTerminalColors colors = result.get(); + + // When highlight-colors-set is true, custom colors should be used + assertEquals("Selection foreground should use custom color", new RGB(100, 100, 100), + colors.getSelectionForeground()); + assertEquals("Selection background should use custom color", new RGB(200, 200, 200), + colors.getSelectionBackground()); + } + + @Test + public void testParseNonExistentFile() { + Optional result = GnomeTerminalColorParser + .parse("/nonexistent/path/to/schema.xml"); + assertFalse("Parser should return empty for non-existent file", result.isPresent()); + } + + @Test + public void testParseInvalidXml() throws IOException { + String invalidContent = "This is not valid XML"; + + File schemaFile = tempFolder.newFile("invalid-schema.xml"); + Files.writeString(schemaFile.toPath(), invalidContent); + + Optional result = GnomeTerminalColorParser.parse(schemaFile.getAbsolutePath()); + + assertFalse("Parser should return empty for invalid XML", result.isPresent()); + } + + @Test + public void testParseIncompleteSchema() throws IOException { + String incompleteContent = """ + + + + + 'rgb(255,255,255)' + + + + """; + + File schemaFile = tempFolder.newFile("incomplete-schema.xml"); + Files.writeString(schemaFile.toPath(), incompleteContent); + + Optional result = GnomeTerminalColorParser.parse(schemaFile.getAbsolutePath()); + + assertFalse("Parser should return empty for incomplete schema (missing palette and background)", + result.isPresent()); + } + + @Test + public void testParsePaletteWithFewerColors() throws IOException { + String schemaContent = """ + + + + + ['rgb(0,0,0)', 'rgb(255,0,0)'] + + + 'rgb(255,255,255)' + + + 'rgb(0,0,0)' + + + + """; + + File schemaFile = tempFolder.newFile("short-palette-schema.xml"); + Files.writeString(schemaFile.toPath(), schemaContent); + + Optional result = GnomeTerminalColorParser.parse(schemaFile.getAbsolutePath()); + + assertFalse("Parser should return empty for palette with fewer than 16 colors", result.isPresent()); + } +}