microsoft / monaco-editor

A browser based code editor

Home Page:https://microsoft.github.io/monaco-editor/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Changing theme in one editor should change all editors

fzafiroski opened this issue · comments

monaco-editor: 0.8.0
Browser: Chrome
OS: Centos 7

When I have two editors in two different html DOM elements, if I dynamically change theme on one editor using updateOptions() function, theme properly changes on the editor, but the other editor takes some changes, for example font colors change but background does not.
The other editor should stay unchanged.

It is currently not possible to have two editors have different themes.

The problem is that I can't dynamically change theme at all, because if I change to one editor, the other one gets messed up.

Is it not possible to change it on both editors?

e.g.
var editor1 = monaco.editor.create(...);
var editor2 = monaco.editor.create(...);
...
editor1.updateOptions({ theme: ... });
editor2.updateOptions({ theme: ... });

It is possible and that's the only way to do it in the current state, but that could add some unnecessary code.
What I wanted to say is that changing theme on one editor should either affect all the editors in the same way as the editor we changed theme on, or not affect them at all. This way, it just makes some changes to other editors which makes the text in them unreadable.
So I think this is more of a bug than a feature-request.

Why is not possible to have multiple editors of different themes?

-1 to Changing theme in one editor should change all editors

This is counter intuitive as the create editor function leads one to believe that themes should work on a per editor basis. There are many use cases (I have one right now which is broken) that require having multiple editors on a page styled differently. This also worked fine a few versions ago so not sure why it was changed... I'm interested in why the monaco-token-styles is injected globally as I would be open to working on a PR to fix this if necessary.

commented

@alexandrudima is this problem solved? I have the same problem in "monaco-editor": "^0.10.1",.

Any updates on the issue?

It is very sad that such cool editor does not support multiple themes in different editors..
Are there any plan to work on this ticket?
Looking for a good news!

No, there are no plans to support this. The root cause is that tokens are stored with resolved colors in the text buffer and the same text buffer can be shown in two distinct editors.

Oh just noticed this update here, so it means that this is an architectural design... I guess it is because you ported from VS Code which is an app base editor(Like you change the theme in one it affects all the windows).
I'm sad that I'll have to move back to Ace Editor. I will be watching the repo until this feature comes.

Thanks for the updates!

Could colors of tokens be represented as CSS classes, so applying different colors can be achieved by applying different CSS rules?

Any update on this? I implemented the theme of my custom language (along hovers, completions, formatter, and an ugly surprise realizing it won't work in the same DOM as a TypeScript editor.

The solution was consuming the other theme's colors using their names. Perhaps if it's not feasible to support different color mappings, a solution could be formalizing some names for different kind of tokens. monaco API already has this categorized, I think is just a matter of enforcing theme authors to use those names so at least, in this case, there's a minimal theme consumer contract.

My two cents.

Badly needing it.... :D

Here {{name}} has a custom theme with orange token.... But on having a JSON response in right side Editor the color formating of left side single line editor is being override with right-side one's.

image

Originally It looks like...

image

No, there are no plans to support this. The root cause is that tokens are stored with resolved colors in the text buffer and the same text buffer can be shown in two distinct editors.

But is this also a problem when every editor uses its own model exclusively?

Btw. I really admire your work on monaco - I think monaco really stepped up the game for editors and brought the future a little bit closer!
I just wished sometimes it would use more modern design techniques (like non-global / non-shared state).

sad :(

I see #1446, all editors on a page must share the same theme.

But I found different theme on one page in monaco editor playground

Does it support now? How should I do this in my project?

Thank you

The second editor is in an <iframe>.

@alexdima I have two editor in the same page. when I set readonly option to left editor, right also is readonly, but I want to right is writeable. How I can insert my React components into iframe to have different monaco instances which doesn't affect each other?

How I can insert my React components into iframe to have different monaco instances which doesn't affect each other?

This will be difficult as iframes are a very strict separation layer. Basically, you need to split your app into two apps that communicate with each other through window.postMessage. The main app is loaded in the browser window, the second app in the iframe.

@hediet In general, I only have one React app for my website. It's so trick, difficult to write another app and put it into iframe

I can't believe there are no practical, certified solution to solve it. :(

For anyone that has bumped into this, here's what I'm doing to "fix" this: simply use css to revert the colors like so

.monaco-editor-container { /* the up most container of the editor */
    filter: invert(1) hue-rotate(170deg) brightness(.8) grayscale(.3);
}

How it looks like:

image

from here: https://proto.school/blog/01 (click on "View Solution").

You can tweak the "theme" by changing the filter values.
For example, if you want a "reader" theme (less blue), use:

.monaco-editor-container {
    filter: saturate(20) sepia(0.8);
}

image

It's not perfect but it works for my use case.

I hope this will be taken into consideration by the team since it is really useful to have a visual distinction between two editors to signal users that they are somewhat different, not connected, or simply have distinct purposes.
It's not just a matter of simply changing between dark and light themes, and so, I believe it makes a lot of sense to be able to customize each editor with different themes if we need to.

For anyone that has bumped into this, here's what I'm doing to "fix" this: simply use css to revert the colors like so

.monaco-editor-container { /* the up most container of the editor */
    filter: invert(1) hue-rotate(170deg) brightness(.8) grayscale(.3);
}

How it looks like:

image

from here: https://proto.school/blog/01 (click on "View Solution").

You can tweak the "theme" by changing the filter values.
For example, if you want a "reader" theme (less blue), use:

.monaco-editor-container {
    filter: saturate(20) sepia(0.8);
}
image

It's not perfect but it works for my use case.

I hope this will be taken into consideration by the team since it is really useful to have a visual distinction between two editors to signal users that they are somewhat different, not connected, or simply have distinct purposes.
It's not just a matter of simply changing between dark and light themes, and so, I believe it makes a lot of sense to be able to customize each editor with different themes if we need to.

It is a nice fix.
Thanks for suggesting your solution here.

Just wanted to bump this. Not sure what the exact goals are for the Monaco project, but it does seem that creating an open source shareable component that can be used outside of the VSCode context is one of them. Not being able to style multiple editors on the page differently is a big blocker to adoption by many. It's understood that this may not be a priority at all for the VS Code use case but it would be reassuring to see the maintainers acknowledging and putting some cycles into other use cases for this component. In any case, thanks for putting this out there at all - it's a nice piece of work.

commented

unexpected, it's a very strange behavior for a standalone editor library.

Well there is a way to get your editors to have different themes as seen in:
https://microsoft.github.io/monaco-editor/playground.html#customizing-the-appearence-exposed-colors

It just includes an iframe -.-

still have this problem, i just wanna use a editor to be a terminal log box, and set a theme different from main editor

Hi, is there an update on this?

Well there is a way to get your editors to have different themes as seen in: https://microsoft.github.io/monaco-editor/playground.html#customizing-the-appearence-exposed-colors

It just includes an iframe -.-

not a good way,dirty hackQAQ

Keeping this thread alive after 5 years. This is still needed. I could even take a CSS guide on which properties to manually override.

in the case of a local I use the editor, you then need to plug-in with an editor, the theme of the two editor will be affected

commented

May the universe bless you, @zebateira
@Monaco-editor team: I am still struggling with the choice of globals that the team made. Why?

Cannot be used in Chromium v78

There is a more pressing issue that regards this problem.
If I have an editor instance, and I set the theme of this instance to vs-dark, then in a future moment I create another editor without any theme definition, the new editor theme will redefine the theme of the first editor created.
Then, If I touch the first created editor and edit something, the dark theme will come back to both editors.
Quite simple to reproduce.
1 - Create an editor Instance
2 - Set the Theme of this editor Instance
3 - Create another editor Instance

I consider this a BUG because the new editor being created should be the one inheriting the theme definition of the previous one created because it had no theme definition on itself ever.

Personally, I understand why this capability could be problematic. Sure, multiple themes in different editor instances would be nice but it's the equivalent of having two panes open in a single editor with each pane applying a different theme.... Sounds a little wild.

I suggest a middle ground approach, specifically for Monaco wherein opposed to allowing different themes be applied, how about allowing a small set of editor color customisations? In most cases editor background adjustment would suffice, wherein mild color differentiation is made available opposed to going down the route of supporting full fledged theming in different instances.

The current approach is iFrame and as xiaoqingb pointed out it's hacky.

EDIT

I didn't do my homework. One can already apply a different code background (if it helps anyone):

const editor = monaco.editor.create(...);
const styles = editor.getDomNode().style;

// Set the editor background color
styles.setProperty('--vscode-editor-background', '#000000');

// Set the editor line numbers/gutters
styles.setProperty('--vscode-editorGutter-background', '#000000');

Badly needing it.... :D

Here {{name}} has a custom theme with orange token.... But on having a JSON response in right side Editor the color formating of left side single line editor is being override with right-side one's.

image

Originally It looks like...

image

can you help me ? how you changed the font color and background color of the react-monoco-editor? please its urgent

The reason why changing the theme of one editor affects all editors is that Monaco creates a set of global CSS variables in the style tag of the head tag, which contains the styles of the class named "monaco-editor".

image

All Monaco editors have a div container with the class "monaco-editor". Therefore, every time you change the theme of one editor, it will create new styles for the "monaco-editor" class, which will affect all other editors.

To solve this issue, you can create a container with a specific class name for the Monaco editor, and the new ".monaco-editor" class styles should take effect within the specific scope. For example

image
image

I implemented a component named MonacoThemeScope, aiming to isolate the theme of each Monaco editor. The main idea is to create a ".monaco-editor" style within a specific container to override the global one.

            <MonacoThemeScope overrideTheme={customTheme}>
                <MonacoEditor
                theme={defaultTheme}
               ....
              />
            </MonacoThemeScope>
            <MonacoThemeScope overrideTheme={'hc-black'}>
              <MonacoEditor
                theme={defaultTheme}
               ....
              />
            </MonacoThemeScope>

the following is the source code of MonacoThemeScope:

import { IStandaloneThemeService } from 'monaco-editor/esm/vs/editor/standalone/common/standaloneTheme';
import { StandaloneServices } from 'monaco-editor/esm/vs/editor/standalone/browser/standaloneServices';
import { Registry } from 'monaco-editor/esm/vs/platform/registry/common/platform.js';
import {
  asCssVariableName,
  Extensions,
} from 'monaco-editor/esm/vs/platform/theme/common/colorRegistry';
import React, { PropsWithChildren } from 'react';

const getColorVariables = (theme: string) => {
  const colorRegistry = Registry.as(Extensions.ColorContribution);

  const allThemes = StandaloneServices.get(
    IStandaloneThemeService,
  )._knownThemes;
  const customTheme = allThemes.get(theme);
  return colorRegistry
    .getColors()
    .map(item => {
      const color = customTheme?.getColor(item.id, true);
      if (color) {
        return {
          key: asCssVariableName(item.id),
          value: color.toString(),
        };
      }
    })
    .filter(Boolean);
};

export type MonacoThemeScopeProps = PropsWithChildren<{
  overrideTheme: string;
}>;
export const MonacoThemeScope: React.FC<MonacoThemeScopeProps> = props => {
  const { overrideTheme } = props;
  const themeName = `${overrideTheme}`;

  const containerRef = React.createRef<HTMLDivElement>();
  React.useEffect(() => {
    const styleEleId = `MonacoThemeScope-theme-${themeName}`;
    let styleEle = document.getElementById(styleEleId);
    if (!styleEle) {
      styleEle = document.createElement('style');
      styleEle.id = styleEleId;
      document.head.appendChild(styleEle);
    }
    const mergedColors: Record<string, string> = {};
    [...getColorVariables('vs'), ...getColorVariables(overrideTheme)].forEach(
      ({ key, value }) => {
        mergedColors[key] = value;
      },
    );

    styleEle.innerHTML = `
    .MonacoThemeScope-theme-${themeName} .monaco-editor {
      ${Object.entries(mergedColors)
        .map(([key, value]) => `${key}: ${value}`)
        .join(';\n')};
    }
    
    `;
  }, []);
  return (
    <div className={`MonacoThemeScope-theme-${themeName}`} ref={containerRef}>
      {props?.children}
    </div>
  );
};

this is the final result:
image
image

@geekeren excited you may have potentially found a solution to this! What is defaultTheme, is that anything special? Also, while your component successfully creates the CSS variables and scopes it, I am getting this:

Screenshot 2024-01-05 at 2 55 54 PM

That is, the style highlighted span elements have a class like mtk20 which is not scoped to monaco-editor or the CSS variables, and it uses that for color. Any ideas what I'm missing or how to get those to change?

Thanks for all the help here!

@geekeren also, my theme is defined like this, do I need to define it differently to make this work?

// https://code.visualstudio.com/api/references/theme-color

const theme = ({
  background,
  string,
  highlight,
  cursor,
}: {
  string: string
  background: string
  highlight: string
  cursor: string
}) => ({
  base: 'vs-dark',
  inherit: true,
  rules: [
    { token: 'invalid', foreground: 'f44747' },
    { token: 'emphasis', fontStyle: 'italic' },
    { token: 'strong', fontStyle: 'bold' },

    { token: 'variable', foreground: '111111' },
    { token: 'variable.predefined', foreground: '111111' },
    { token: 'variable.parameter', foreground: '111111' },
    { token: 'constant', foreground: '111111', fontStyle: 'bold' },
    { token: 'comment', foreground: 'cccccc', fontStyle: 'italic' },
    { token: 'number', foreground: '10B981' },
    { token: 'number.hex', foreground: '10B981', fontStyle: 'bold' },
    { token: 'regexp', foreground: string },
    { token: 'annotation', foreground: 'cc6666' },
    { token: 'type', foreground: '111111', fontStyle: 'bold' },

    { token: 'delimiter', foreground: 'BBBBBB' },
    { token: 'delimiter.html', foreground: 'BBBBBB' },
    { token: 'delimiter.xml', foreground: 'BBBBBB' },

    { token: 'tag', foreground: '111111', fontStyle: 'bold' },
    { token: 'tag.id.pug', foreground: '111111', fontStyle: 'bold' },
    { token: 'tag.class.pug', foreground: '111111', fontStyle: 'bold' },
    { token: 'meta.scss', foreground: '555555' },
    { token: 'meta.tag', foreground: '555555' },
    { token: 'metatag', foreground: '555555' },
    { token: 'metatag.content.html', foreground: '555555' },
    { token: 'metatag.html', foreground: '111111', fontStyle: 'bold' },
    { token: 'metatag.xml', foreground: '111111', fontStyle: 'bold' },
    { token: 'metatag.php', fontStyle: 'bold' },

    { token: 'key', foreground: '9CDCFE' },
    {
      token: 'string',
      foreground: string,
      fontStyle: 'bold',
    },
    {
      token: 'string.key.json',
      foreground: '111111',
      fontStyle: 'bold',
    },
    {
      token: 'string.value.json',
      foreground: string,
      fontStyle: 'bold',
    },

    { token: 'attribute.name', foreground: '111111' },
    { token: 'attribute.value', foreground: string, fontStyle: 'bold' },
    {
      token: 'attribute.value.number.css',
      foreground: '10B981',
      fontStyle: 'bold',
    },
    {
      token: 'attribute.value.unit.css',
      foreground: '10B981',
      fontStyle: 'bold',
    },
    {
      token: 'attribute.value.hex.css',
      foreground: '10B981',
      fontStyle: 'bold',
    },

    { token: 'string.sql', foreground: string, fontStyle: 'bold' },

    { token: 'keyword', foreground: '555555' },
    { token: 'keyword.flow', foreground: 'C586C0', fontStyle: 'bold' },
    { token: 'keyword.json', foreground: 'CE9178', fontStyle: 'bold' },
    {
      token: 'keyword.flow.scss',
      foreground: '555555',
    },

    { token: 'operator.scss', foreground: 'BBBBBB' },
    { token: 'operator.sql', foreground: 'BBBBBB' },
    { token: 'operator.swift', foreground: 'BBBBBB' },
    { token: 'predefined.sql', foreground: 'FF00FF' },
    {
      background: x(background),
      token: '',
    },
  ],
  colors: {
    'editor.foreground': '#111111',
    'editor.background': background,
    'editor.selectionBackground': highlight,
    'editor.lineHighlightBackground': highlight,
    'editor.selectionHighlightBackground': highlight,
    'editor.wordHighlightBackground': highlight,
    'editor.wordHighlightStrongBackground': highlight,
    'editorCursor.foreground': cursor,
    'terminalCursor.foreground': cursor,
    'editorWhitespace.foreground': '#3B3A32',
    'editorIndentGuide.activeBackground': '#9D550FB0',
    'editor.selectionHighlightBorder': highlight,
    'editorBracketHighlight.foreground1': cursor,
    'editorBracketHighlight.foreground2': cursor,
    'editorBracketHighlight.foreground3': cursor,
  },
})

export default theme

function x(t: string) {
  return t.replace(/#/g, '')
}

@lancejpollard it's unfortunately that the solution is not well solve the grammar highlight. the class name "mk-xxx" is dynamic generated differently in different theme.

@lancejpollard the defaultTheme in my code is vs theme, or vs-dark theme, I just keep the two monaco editors' base theme is same. if they are different, the class name "mk-xxx" will be confilict

Dang, didn't know if mk-xxx was dynamic or fixed, anyhow, here is what I landed on that works for my limited theme choice:

.MonacoThemeScope-theme-mytheme-light-violet .mtk6 {
  color: #7c3aed;
}

.MonacoThemeScope-theme .monaco-editor .bracket-highlighting-0,
.MonacoThemeScope-theme .monaco-editor .bracket-highlighting-1,
.MonacoThemeScope-theme .monaco-editor .bracket-highlighting-2 {
  color: #777777;
}

.MonacoThemeScope-theme-mytheme-light-violet .cursors-layer .cursor {
  background-color: #5b21b6;
  border-color: #5b21b6;
}

.MonacoThemeScope-theme-mytheme-light-violet
  .monaco-editor
  .view-overlays
  .current-line,
.MonacoThemeScope-theme-mytheme-light-violet
  .monaco-editor
  .selected-text {
  background-color: #ede9fe;
}

.MonacoThemeScope-theme-mytheme-light-blue .mtk6 {
  color: #2563eb;
}

.MonacoThemeScope-theme-mytheme-light-blue
  .monaco-editor
  .cursors-layer
  .cursor {
  background-color: #1e40af;
  border-color: #1e40af;
}

.MonacoThemeScope-theme-mytheme-light-blue
  .monaco-editor
  .view-overlays
  .current-line,
.MonacoThemeScope-theme-mytheme-light-blue
  .monaco-editor
  .selected-text {
  background-color: #dbeafe;
}
/*  */

.monaco-editor .lines-content {
  margin-left: 16px;
  /* margin-right: 16px; */
}

.monaco-editor .decorationsOverviewRuler {
  display: none !important;
}
/*

In another thought, because Sandpack does not use Monaco (it uses CodeMirror it looks like, because CodeMirror works better on mobile it sounds like), I might switch to using CodeMirror so I only have one technology in the app, if I chose to use Sandpack one of these days for something else too.