microsoft / vscode

Visual Studio Code
https://code.visualstudio.com
MIT License
163.05k stars 28.8k forks source link

[theming] Access theme's colors programmatically #32813

Open fabiospampinato opened 7 years ago

fabiospampinato commented 7 years ago

I think we should add support for accessing theme's colors programmatically.

For instance let's take as an example the popular OneDark Pro theme, I'd like to access colors defined under tokenColors.

My use case: I'm making an extension that decorates some tokens, and I'd like them to have the same color that comments have in my theme, the problem is that the regex I use to find those tokens depends on some configurable value, so I cannot pre-compute it in advance and just put it in a .tmLanguage file.

It's already possible to somehow access colors defined under the colors key, via something like new vscode.ThemeColor ( 'activityBar.background' ), adding support for this sounds like a useful generalization to me.

What do you think?

aeschli commented 7 years ago

I'd rather avoid API that gives out the actual color, but hope that all can be done with color references. That way extensions don't need to worry about listening to theme changes. Yes, sounds like a useful idea but is not an easy addition as token colors are based on text mate theme rules and the text mate scope hierarchy. If there are more requests, we can certainly look at it. Until then I recommend you to use one of the regular workbench colors, or - added to 1.16 - define a new color in your extension.

eamodio commented 6 years ago

@aeschli while I still think an API for this would be valuable (or at the least an API that tells you if the current theme is light or dark), another alternative that might work in certain scenarios would be to expose the set of theme colors as css variables. This would allow the scenario here: https://github.com/Microsoft/vscode/issues/34411#issue-257879258

In theory I could use an SVG image, with colors pulled from the css variables, allowing me to get theme adaptable icons into the hovers.

Thoughts?

FYI, this is similar to this request https://github.com/Microsoft/vscode/issues/41785, but not just for the webview, for the editor itself.

aeschli commented 6 years ago

@eamodio Great idea. If using css variables in SVG images works then we should definitely do this. Let's track this in a separate issue!

DanTup commented 6 years ago

46940 was closed as a dupe of this, but I'm not sure it's the same request. I have remotely-hosted SVGs I want to include in my markdown tooltips. I need to decide between the black and white versions so need to know whether VS Code considers the current theme light or dark. I don't need access to colours and I cannot modify the SVG.

eamodio commented 6 years ago

This issue has come up again. In GitLens, I would like to expose 2 colors (hot and cold versions) for the heatmap that GitLens generates, but I am unable to use theme colors, because I need access to the real color values to generate the full palette of colors required.

vviikk commented 6 years ago

A lot of extensions would benefit from this. Bracket colorizer, jumpy, gitlens and a zillion others. Would be good to implement this.

aeschli commented 6 years ago

@piggyslasher Please explain how/where these extension could profit from this.

There is already a story that extensions can declare a new color (by id) and use it in decorations. Users can redefine these colors in themes.

fabiospampinato commented 6 years ago

My use case: I'm making an extension that decorates some tokens, and I'd like them to have the same color that comments have in my theme, the problem is that the regex I use to find those tokens depends on some configurable value, so I cannot pre-compute it in advance and just put it in a .tmLanguage file

@aeschli that's my use case. Sure, I'm already exporting these colors via the settings and users can override them already. But I can't just pick the colors for them automatically depending on what theme they are currently using. Also if someone uses one of those extensions for rotating themes I can't just expect them to update their settings every time.

vviikk commented 6 years ago

@aeschli A use case in my scenario is I use the extension called "Bracket pair colorizer" with adds colors to matching brackets, like so: image Now, in order for the extension to render the brackets to match my theme, I have to hard code the colors into my settings:

image

Without this, I would end up with something that looks like this out of the box: image

Notice the color of the brackets. They're from the extension's default preferences because the developer can't access the colors. I hope I'm on the right track.

aeschli commented 6 years ago

@piggyslasher Did you see that you can define new colors in the extension's package.json?

"contributes": {
  "colors": [{
      "id": "bracketPairColorizer.bracket1",
      "description": "Color for the outermost bracket",
      "defaults": {
          "dark": "#112233",
          "light": "#ddeeff",
          "highContrast": "foreground"
      }
  }]
}

Users can then customize the color in the workspace.colorCustomization setting and even themes can add a default color.

Color default values can be defined for light, dark and high contrast theme and can either be a reference to an existing color or a color hex value.

tamuratak commented 5 years ago

Hi, I am working on a PR for LaTeX-Workshop, a VSCode extension for LaTeX editing. In the PR, I make a latex preview in hover available rendering math equations in SVG format with MathJax and embedding the dataurl of generated SVG into a markdown text as an image source.

When rendering math equations, I have to tell MathJax which color to use for each rendering. Otherwise rendered equations are invisible on a certain theme. To work around the lack of color API in VSCode, I have to render equations in a WebView process, not in an extension process because we can know colors form css variables in the WebView process, as suggested by @eamodio. So when LaTeX-Workshop users close a WebView pane, my PR does not work. For LaTeX-Workshop users, opening a WebView pane as a pdf viewer is a usual thing. So the lack of color api seems to be a minor thing.

However, the same can be said for other documents having TeX-like equations. Docstring in python, for example. Let's think about API documents with math equations written in docstring. Python libraries should have tons of such API documents. If we want to render equations in them with MathJax, and to display them with API documents in hover, the same problem happens. And, for python programmers while coding, opening a WebView pane is an unusual thing.

Please notice that, for this purpose, extensions don't have to listen to theme changes. Equations are dynamically rendered each time. MathJax is enough fast to do that.

Regards,

On a light theme.

oct-13-2018 10-46-17

On a dark theme.

oct-13-2018 10-48-16

Gruntfuggly commented 5 years ago

@aeschli Unfortunately that doesn't allow the colours to be used outside of the vscode API. 😞

amueller commented 5 years ago

@aeschli can you provide a reference for this? I feel like it might solve the @vviikk issue (which I'm also having) but I'm not sure I understand your suggestion.

Basically you're suggesting to use these? https://code.visualstudio.com/api/references/theme-color

aeschli commented 5 years ago

Yes, ideally you use one of the existing colors, or define a new color id (see https://github.com/Microsoft/vscode/issues/32813#issuecomment-414227205) to allow users to theme your color as well. I know that there are still many gaps with this story, Colors in hover, computed colors...

CoenraadS commented 5 years ago

If I understand correctly.

User defines a css color as editor.foreground

I need to do a lookup to workbench.colorCustomizations to retrieve the css value

However if I do vscode.workspace.getConfiguration("workbench.colorCustomizations", undefined);

I get only the user defined settings. Is there I way I can get the flattened settings including all the theme colors. (e.g. if user did not specify a custom editor.foreground)

aeschli commented 5 years ago

No, you can not look up a color, that's the API that's missing. But you can reference a color by using new ThemeColor('editor.foreground') in certain APIs (status bar, decorators)

CoenraadS commented 5 years ago

Aah that's the missing link. Awesome πŸ‘ I think it works now

DanTup commented 5 years ago

I found a way to abuse the WebView to find out whether the theme is Dark or Light. It's a huge hack, but I need to know whether to use light/dark icons and there's no API, so...

It returns a promise with an enum of the theme:

detectTheme(context.subscriptions).then((theme) => {
    if (theme === Theme.Dark) {
        console.log("Using Dark theme!");
    } else if (theme === Theme.Light) {
        console.log("Using Light theme!");
    } else if (theme === Theme.HighContrast) {
        console.log("Using High Contrast theme!");
    } else {
        console.log("Failed to parse theme");
    }
});

It'll automatically resolve with Unknown after a second, in case something has gone wrong. theme_detector.ts looks like this:

import * as vs from "vscode";

export enum Theme {
    Unknown,
    Dark,
    Light,
    HighContrast,
}

export function detectTheme(disposables?: vs.Disposable[]): Promise<Theme> {
    return new Promise((resolve) => {
        let panel = createPanel();
        const messageHandler = (bodyCssClass?: string) => {
            if (panel) {
                panel.dispose();
                panel = undefined;
            }
            resolve(bodyCssClass ? parseClass(bodyCssClass) : Theme.Unknown);
        };

        // After a second, just resolve as unknown.
        setTimeout(() => messageHandler(), 1000);

        panel.webview.onDidReceiveMessage(
            messageHandler,
            undefined,
            disposables,
        );
        panel.webview.html = themeDetectorScript;
    });
}

const themeDetectorScript = `<html><body><script>
    (function() {
        const vscode = acquireVsCodeApi();
        vscode.postMessage(document.body.className);
    })();
</script></body></html>`;

function createPanel() {
    return vs.window.createWebviewPanel(
        "theme-detector",
        "",
        {
            preserveFocus: true,
            viewColumn: vs.ViewColumn.Beside,
        },
        {
            enableScripts: true,
            localResourceRoots: [],
        },
    );
}

function parseClass(bodyCssClass: string): Theme {
    if (bodyCssClass && bodyCssClass.indexOf("vscode-dark") !== -1) {
        return Theme.Dark;
    } else if (bodyCssClass && bodyCssClass.indexOf("vscode-light") !== -1) {
        return Theme.Light;
    } else if (bodyCssClass && bodyCssClass.indexOf("vscode-high-contrast") !== -1) {
        return Theme.HighContrast;
    } else {
        return Theme.Unknown;
    }
}

I don't take any responsibility if you decide to use this, it could be fragile, but since I wrote it I thought I'd share it. I only need the theme, but you should be able to tweak the support extracting colours.

Note: It doesn't do any detection of the theme changing, so you may not want to hold onto the result for too long. Though there is also a visible flicker of the window and it could take up to a second to fail, so you might also not want to call it too much.

bobbrow commented 5 years ago

Can we initialize a ThemeColor based on a "scope" value? For example, if our extension wants to add text decorations based on the theme's setting for the "comment" scope, is it possible?

For the C++ extension, we essentially want to color the code based on our own language service's information instead of TextMate grammars which are not capable of classifying user-defined types correctly.

aeschli commented 5 years ago

Can we initialize a ThemeColor based on a "scope" value?

@bobbrow That's currently not possible

rchiodo commented 5 years ago

I'm plus oneing @bobbrow's comment. We need the same thing for the Python Extension. We host the monaco editor in our 'Python Interactive' window and we have no way to get the colors for different scopes. Currently we have a hack of trying to find the current theme's json on disk.

memeplex commented 5 years ago

The PDF previewer in LaTeX Workshop could benefit from knowing if the current theme is dark or light in order to decide whether to invert or not the background and foreground colors. In general, I believe the light/dark state is an important piece of information to expose.

PEZ commented 5 years ago

I +1000 this one. My use case is that in Clojure there are three types of comments:

  1. Line based, rest of line after a ;.
  2. The (comment) function, returning nil whatever you pass it.
  3. The ignore reader token, #_ resulting in that the reader skips the whole form following it.

These are often used differently, with ; comment being the way to just comment something, (comment) to keep test code in the file, and #_ to disable chunks of code.

To support this use I'm rendering the (comment foo) and #_ stuff as dimmed, with intact syntax highlighting. But some users want those to be rendered the same as line based comments. Which I can't do, since I can't find the theme color value of comments.

Note that I can't solve this with tmLanguage grammar, because dimming can't be done that way.

DanTup commented 5 years ago

Here's another annoyance with not knowing if the theme is light or dark... Terminal ansi colours...

I had some code that used Grey and White in stack traces so that users own code frames was more visible (white) than framework code (grey). I tested it with both light and dark themes and found that the White ansi color codes produce a dark color in the themes (great!).

Well, until a user with a third party theme found that I was showing white text on a light background 😞

I figured I could raise this as a bug against their theme, however I failed to find any guidance on how themes should handle these, though I did found that in the VS Code code, ansiWhite is inverted for light/dark theme but ansiBlack is not which leaves me more confused.

anaisbetts commented 5 years ago

Came here via @DanTup - I'm trying to write a VS Extension to sync your color theme to Windows Terminal, but this bug blocks it. This information is available internally (via "Generate Color Theme from Current Settings"), but I have no sane access to it

paulyoung commented 5 years ago

@anaisbetts I’ve done this before. It’s not exactly sane but it worked for me at the time.

const getTokenColorsForTheme = (themeName: string): TokenColors => {
  const tokenColors = new Map();
  let currentThemePath;
  for (const extension of extensions.all) {
    const themes = extension.packageJSON.contributes && extension.packageJSON.contributes.themes;
    const currentTheme = themes && themes.find((theme) => theme.id === themeName);
    if (currentTheme) {
      currentThemePath = path.join(extension.extensionPath, currentTheme.path);
      break;
    }
  }
  const themePaths = [];
  if (currentThemePath) { themePaths.push(currentThemePath); }
  while (themePaths.length > 0) {
    const themePath = themePaths.pop();
    const theme = require(themePath);
    if (theme) {
      if (theme.include) {
        themePaths.push(path.join(path.dirname(themePath), theme.include));
      }
      if (theme.tokenColors) {
        theme.tokenColors.forEach((rule) => {
          if (typeof rule.scope === "string" && !tokenColors.has(rule.scope)) {
            tokenColors.set(rule.scope, rule.settings);
          } else if (rule.scope instanceof Array) {
            rule.scope.forEach((scope) => {
              if (!tokenColors.has(rule.scope)) {
                tokenColors.set(scope, rule.settings);
              }
            });
          }
        });
      }
    }
  }
  return tokenColors;
}

where

type TokenColors = Map<string, TokenColorSettings>

interface TokenColorSettings {
  background?: string
  fontStyle?: string
  foreground?: string
}
paulyoung commented 5 years ago

Usage:

const themeName: string | undefined = workspace.getConfiguration("workbench").get("colorTheme");
const tokenColors = getTokenColorsForTheme(themeName);
kufii commented 5 years ago

@paulyoung Thanks for the solution! pretty hacky but it worked for me

aeschli commented 4 years ago

This is a proposal for accessing the current workbench colors


    /**
     * Represents a color theme kind.
     */
    export enum ColorThemeKind {
        Light = 1,
        Dark = 2,
        HighContrast = 3
    }

    /**
     * Represents a color theme.
     */
    export interface ColorTheme {

        /**
         * The kind of this color theme: light, dark or high contrast.
         */
        readonly kind: ColorThemeKind;

        /**
         * The id of the color theme. This corresponds to the value used in the `workbench.colorTheme` setting.
         */
        readonly id: string;

        /**
         * Get the color value for a color identifier.
         *
         * @param colorIdentifier A color identifier as defined in https://code.visualstudio.com/docs/getstarted/theme-color-reference
         * or contributed by an extension.
         * @return The color as defined by the theme or in the color defaults. If the color identifier is
         * unknown, `undefined` is returned.
         */
        getColor(colorIdentifier: ThemeColor): Color | undefined;
    }

    export namespace window {
        /**
         * The currently active color theme as configured in the settings. The active
         * theme can be changed via the `workbench.colorTheme` setting.
         */
        export let activeColorTheme: ColorTheme;

        /**
         * An [event](#Event) which fires when the active theme changes.
         */
        export const onDidChangeActiveColorTheme: Event<ColorTheme>;
    }
rchiodo commented 4 years ago

I'm not sure why that API is needed? All of those colors are available in the root CSS for a webview?

What we really want is the token colors, not the CSS colors.

aeschli commented 4 years ago

Token colors are coming too. I've just added the first implementation for https://github.com/microsoft/vscode/issues/77133 and once that has settled I'll propose something like

function getTokenStyle(type: string, modifiers: string[]): TokenStyle;

interface TokenStyle {
   foreground: Color:
   bold: boolean:
   italics: boolean:
   underline: boolean;
} 
Colengms commented 4 years ago

Would onDidChangeActiveColorTheme() fire when colors are changed by settings that override theme colors? (i.e. editor.tokenColorCustomizations).

aeschli commented 4 years ago

@Colengms Yes

PEZ commented 4 years ago

@APerricone for Webviews you can use the vscode css vars in your css. Like I do here: https://github.com/BetterThanTomorrow/calva/tree/master/assets/styles

APerricone commented 4 years ago

@PEZ sorry, i saw the documentation 5 minutes after the comment, so I removed the comment... Thank you anyway.

usernamehw commented 4 years ago

Any ETA of this moving from proposed state to stable?

aeschli commented 4 years ago

The APIs to get the theme kind (light / dark / hc) is now made public API. This should help with selecting the corresponding images in hovers.

usernamehw commented 4 years ago

Only kind? Not the getting color value from id as in https://github.com/microsoft/vscode/issues/32813#issuecomment-558242506?

AllanOricil commented 4 years ago

Im creating an extension and I added an Icon to my webview panel but I currently can't change its color depending on the Color Theme. Couldn't you allow me to use an SVG with a fill color using a VSCode CSS property? or do the same as it happens to the "editor/title" button.

For the SVG suggestion I could do this (I tested and it does not work):

<path fill="var(--vscode-class)"></path>

For the editor menu I can switch the icons using the Manifest, like show below. But the icon for the Panel I could not. it would be great if the panel had two properties too somewhere or use at least the same icons from command.

Here you can see that the Panel Icon and Editor Icon have different colors image

While here they both have the same colors. image

"contributes": {
    "commands": [
      {
        "command": "YOUR_COMMAND",
        "title": "COMMAND TITLE",
        "icon": {
          "light": "/.images/icon2.png",
          "dark": "/.images/icon2.png"
        }
      }
    ],
    "menus": {
      "editor/title": [
        {
          "command": "YOUR_COMMAND",
          "alt": "markdown.showPreviewToSide",
          "group": "navigation"
        }
      ]
    }
}
aeschli commented 4 years ago

I'm not knowledgeable about webviews, but from reading https://github.com/microsoft/vscode/pull/54912 also web view icons support light and dark variants. If that's not good enough, can you file a new issue against web views?

AllanOricil commented 4 years ago

I'm not knowledgeable about webviews, but from reading #54912 also web view icons support light and dark variants. If that's not good enough, can you file a new issue against web views?

But the doc does not say how to do it.

aeschli commented 4 years ago

@mjbvz Can you help @AllanOricil ?

mjbvz commented 4 years ago

@AllanOricil WebviewPanel.iconPath takes a dark and light icon variant: https://github.com/microsoft/vscode/blob/fa55787dbc15086031e596b7da434644878a2add/src/vs/vscode.d.ts#L6873

AllanOricil commented 4 years ago

@AllanOricil WebviewPanel.iconPath takes a dark and light icon variant:

https://github.com/microsoft/vscode/blob/fa55787dbc15086031e596b7da434644878a2add/src/vs/vscode.d.ts#L6873

Thanks @aeschli and @mjbvz !!!! I was using this Doc https://code.visualstudio.com/api/references/vscode-api

connor4312 commented 3 years ago

For one particular use case -- I would love to use this in an extension I'm working to provide completions for webview colors, so that I could show the user's current color in the completion items.

eamodio commented 3 years ago

I still want it to make more of the GitLens blame annotations themeable -- since gutterIconPath isn't themeable today, so I need to generate svgs with specific colors. But if I could read a theme color, then I could use that value to re-generate the SVGs

AllanOricil commented 3 years ago

I found another use case. Imagine i have an extension that locally serves a page, so the user can work in a separate window. Now imagine I changed theme inside Vscode, how can I notify the local server the theme has been changed, so I can also apply the new theme on the page? I cant. There should be an event we can hook somewhere to get the new theme that was applied.

akukettunen commented 3 years ago

My personal use case would be setting my rooms Philips Hue lights coloring according to my current vsc themes colors when I open vscode :) That would be neat, altough achievable by hardcoding if the theme is not changed too often.

duncanbeevers commented 3 years ago

I want to generate svg diagrams representing inline regular expressions, and want to use the theme colors for elements of the generated illustration.

APerricone commented 3 years ago

@duncanbeevers In this case you can in the same way I did in https://github.com/APerricone/vscode-regexper-unofficial. Simply use the css variables https://code.visualstudio.com/api/extension-guides/webview#theming-webview-content