Path: blob/main/src/vs/editor/browser/services/abstractCodeEditorService.ts
3294 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as dom from '../../../base/browser/dom.js';6import * as domStylesheets from '../../../base/browser/domStylesheets.js';7import * as cssJs from '../../../base/browser/cssValue.js';8import { Emitter, Event } from '../../../base/common/event.js';9import { IDisposable, DisposableStore, Disposable, toDisposable, DisposableMap } from '../../../base/common/lifecycle.js';10import { LinkedList } from '../../../base/common/linkedList.js';11import * as strings from '../../../base/common/strings.js';12import { URI } from '../../../base/common/uri.js';13import { ICodeEditor, IDiffEditor } from '../editorBrowser.js';14import { ICodeEditorOpenHandler, ICodeEditorService } from './codeEditorService.js';15import { IContentDecorationRenderOptions, IDecorationRenderOptions, IThemeDecorationRenderOptions, isThemeColor } from '../../common/editorCommon.js';16import { IModelDecorationOptions, IModelDecorationOverviewRulerOptions, InjectedTextOptions, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from '../../common/model.js';17import { IResourceEditorInput } from '../../../platform/editor/common/editor.js';18import { IColorTheme, IThemeService } from '../../../platform/theme/common/themeService.js';19import { ThemeColor } from '../../../base/common/themables.js';2021export abstract class AbstractCodeEditorService extends Disposable implements ICodeEditorService {2223declare readonly _serviceBrand: undefined;2425private readonly _onWillCreateCodeEditor = this._register(new Emitter<void>());26public readonly onWillCreateCodeEditor = this._onWillCreateCodeEditor.event;2728private readonly _onCodeEditorAdd: Emitter<ICodeEditor> = this._register(new Emitter<ICodeEditor>());29public readonly onCodeEditorAdd: Event<ICodeEditor> = this._onCodeEditorAdd.event;3031private readonly _onCodeEditorRemove: Emitter<ICodeEditor> = this._register(new Emitter<ICodeEditor>());32public readonly onCodeEditorRemove: Event<ICodeEditor> = this._onCodeEditorRemove.event;3334private readonly _onWillCreateDiffEditor = this._register(new Emitter<void>());35public readonly onWillCreateDiffEditor = this._onWillCreateDiffEditor.event;3637private readonly _onDiffEditorAdd: Emitter<IDiffEditor> = this._register(new Emitter<IDiffEditor>());38public readonly onDiffEditorAdd: Event<IDiffEditor> = this._onDiffEditorAdd.event;3940private readonly _onDiffEditorRemove: Emitter<IDiffEditor> = this._register(new Emitter<IDiffEditor>());41public readonly onDiffEditorRemove: Event<IDiffEditor> = this._onDiffEditorRemove.event;4243private readonly _onDidChangeTransientModelProperty: Emitter<ITextModel> = this._register(new Emitter<ITextModel>());44public readonly onDidChangeTransientModelProperty: Event<ITextModel> = this._onDidChangeTransientModelProperty.event;4546protected readonly _onDecorationTypeRegistered: Emitter<string> = this._register(new Emitter<string>());47public onDecorationTypeRegistered: Event<string> = this._onDecorationTypeRegistered.event;4849private readonly _codeEditors: { [editorId: string]: ICodeEditor };50private readonly _diffEditors: { [editorId: string]: IDiffEditor };51protected _globalStyleSheet: GlobalStyleSheet | null;52private readonly _decorationOptionProviders = new Map<string, IModelDecorationOptionsProvider>();53private readonly _editorStyleSheets = new Map<string, RefCountedStyleSheet>();54private readonly _codeEditorOpenHandlers = new LinkedList<ICodeEditorOpenHandler>();5556constructor(57@IThemeService private readonly _themeService: IThemeService,58) {59super();60this._codeEditors = Object.create(null);61this._diffEditors = Object.create(null);62this._globalStyleSheet = null;63}6465willCreateCodeEditor(): void {66this._onWillCreateCodeEditor.fire();67}6869addCodeEditor(editor: ICodeEditor): void {70this._codeEditors[editor.getId()] = editor;71this._onCodeEditorAdd.fire(editor);72}7374removeCodeEditor(editor: ICodeEditor): void {75if (delete this._codeEditors[editor.getId()]) {76this._onCodeEditorRemove.fire(editor);77}78}7980listCodeEditors(): ICodeEditor[] {81return Object.keys(this._codeEditors).map(id => this._codeEditors[id]);82}8384willCreateDiffEditor(): void {85this._onWillCreateDiffEditor.fire();86}8788addDiffEditor(editor: IDiffEditor): void {89this._diffEditors[editor.getId()] = editor;90this._onDiffEditorAdd.fire(editor);91}9293removeDiffEditor(editor: IDiffEditor): void {94if (delete this._diffEditors[editor.getId()]) {95this._onDiffEditorRemove.fire(editor);96}97}9899listDiffEditors(): IDiffEditor[] {100return Object.keys(this._diffEditors).map(id => this._diffEditors[id]);101}102103getFocusedCodeEditor(): ICodeEditor | null {104let editorWithWidgetFocus: ICodeEditor | null = null;105106const editors = this.listCodeEditors();107for (const editor of editors) {108109if (editor.hasTextFocus()) {110// bingo!111return editor;112}113114if (editor.hasWidgetFocus()) {115editorWithWidgetFocus = editor;116}117}118119return editorWithWidgetFocus;120}121122123private _getOrCreateGlobalStyleSheet(): GlobalStyleSheet {124if (!this._globalStyleSheet) {125this._globalStyleSheet = this._createGlobalStyleSheet();126}127return this._globalStyleSheet;128}129130protected _createGlobalStyleSheet(): GlobalStyleSheet {131return new GlobalStyleSheet(domStylesheets.createStyleSheet());132}133134private _getOrCreateStyleSheet(editor: ICodeEditor | undefined): GlobalStyleSheet | RefCountedStyleSheet {135if (!editor) {136return this._getOrCreateGlobalStyleSheet();137}138const domNode = editor.getContainerDomNode();139if (!dom.isInShadowDOM(domNode)) {140return this._getOrCreateGlobalStyleSheet();141}142const editorId = editor.getId();143if (!this._editorStyleSheets.has(editorId)) {144const refCountedStyleSheet = new RefCountedStyleSheet(this, editorId, domStylesheets.createStyleSheet(domNode));145this._editorStyleSheets.set(editorId, refCountedStyleSheet);146}147return this._editorStyleSheets.get(editorId)!;148}149150_removeEditorStyleSheets(editorId: string): void {151this._editorStyleSheets.delete(editorId);152}153154public registerDecorationType(description: string, key: string, options: IDecorationRenderOptions, parentTypeKey?: string, editor?: ICodeEditor): IDisposable {155let provider = this._decorationOptionProviders.get(key);156if (!provider) {157const styleSheet = this._getOrCreateStyleSheet(editor);158const providerArgs: ProviderArguments = {159styleSheet: styleSheet,160key: key,161parentTypeKey: parentTypeKey,162options: options || Object.create(null)163};164if (!parentTypeKey) {165provider = new DecorationTypeOptionsProvider(description, this._themeService, styleSheet, providerArgs);166} else {167provider = new DecorationSubTypeOptionsProvider(this._themeService, styleSheet, providerArgs);168}169this._decorationOptionProviders.set(key, provider);170this._onDecorationTypeRegistered.fire(key);171}172provider.refCount++;173return {174dispose: () => {175this.removeDecorationType(key);176}177};178}179180public listDecorationTypes(): string[] {181return Array.from(this._decorationOptionProviders.keys());182}183184public removeDecorationType(key: string): void {185const provider = this._decorationOptionProviders.get(key);186if (provider) {187provider.refCount--;188if (provider.refCount <= 0) {189this._decorationOptionProviders.delete(key);190provider.dispose();191this.listCodeEditors().forEach((ed) => ed.removeDecorationsByType(key));192}193}194}195196public resolveDecorationOptions(decorationTypeKey: string, writable: boolean): IModelDecorationOptions {197const provider = this._decorationOptionProviders.get(decorationTypeKey);198if (!provider) {199throw new Error('Unknown decoration type key: ' + decorationTypeKey);200}201return provider.getOptions(this, writable);202}203204public resolveDecorationCSSRules(decorationTypeKey: string) {205const provider = this._decorationOptionProviders.get(decorationTypeKey);206if (!provider) {207return null;208}209return provider.resolveDecorationCSSRules();210}211212private readonly _transientWatchers = this._register(new DisposableMap<string, ModelTransientSettingWatcher>());213private readonly _modelProperties = new Map<string, Map<string, any>>();214215public setModelProperty(resource: URI, key: string, value: any): void {216const key1 = resource.toString();217let dest: Map<string, any>;218if (this._modelProperties.has(key1)) {219dest = this._modelProperties.get(key1)!;220} else {221dest = new Map<string, any>();222this._modelProperties.set(key1, dest);223}224225dest.set(key, value);226}227228public getModelProperty(resource: URI, key: string): any {229const key1 = resource.toString();230if (this._modelProperties.has(key1)) {231const innerMap = this._modelProperties.get(key1)!;232return innerMap.get(key);233}234return undefined;235}236237public setTransientModelProperty(model: ITextModel, key: string, value: any): void {238const uri = model.uri.toString();239240let w = this._transientWatchers.get(uri);241if (!w) {242w = new ModelTransientSettingWatcher(uri, model, this);243this._transientWatchers.set(uri, w);244}245246const previousValue = w.get(key);247if (previousValue !== value) {248w.set(key, value);249this._onDidChangeTransientModelProperty.fire(model);250}251}252253public getTransientModelProperty(model: ITextModel, key: string): any {254const uri = model.uri.toString();255256const watcher = this._transientWatchers.get(uri);257if (!watcher) {258return undefined;259}260261return watcher.get(key);262}263264public getTransientModelProperties(model: ITextModel): [string, any][] | undefined {265const uri = model.uri.toString();266267const watcher = this._transientWatchers.get(uri);268if (!watcher) {269return undefined;270}271272return watcher.keys().map(key => [key, watcher.get(key)]);273}274275_removeWatcher(w: ModelTransientSettingWatcher): void {276this._transientWatchers.deleteAndDispose(w.uri);277}278279abstract getActiveCodeEditor(): ICodeEditor | null;280281async openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {282for (const handler of this._codeEditorOpenHandlers) {283const candidate = await handler(input, source, sideBySide);284if (candidate !== null) {285return candidate;286}287}288return null;289}290291registerCodeEditorOpenHandler(handler: ICodeEditorOpenHandler): IDisposable {292const rm = this._codeEditorOpenHandlers.unshift(handler);293return toDisposable(rm);294}295}296297export class ModelTransientSettingWatcher extends Disposable {298public readonly uri: string;299private readonly _values: { [key: string]: any };300301constructor(uri: string, model: ITextModel, owner: AbstractCodeEditorService) {302super();303304this.uri = uri;305this._values = {};306this._register(model.onWillDispose(() => owner._removeWatcher(this)));307}308309public set(key: string, value: any): void {310this._values[key] = value;311}312313public get(key: string): any {314return this._values[key];315}316317public keys(): string[] {318return Object.keys(this._values);319}320}321322class RefCountedStyleSheet {323324private readonly _parent: AbstractCodeEditorService;325private readonly _editorId: string;326private readonly _styleSheet: HTMLStyleElement;327private _refCount: number;328329public get sheet() {330return this._styleSheet.sheet as CSSStyleSheet;331}332333constructor(parent: AbstractCodeEditorService, editorId: string, styleSheet: HTMLStyleElement) {334this._parent = parent;335this._editorId = editorId;336this._styleSheet = styleSheet;337this._refCount = 0;338}339340public ref(): void {341this._refCount++;342}343344public unref(): void {345this._refCount--;346if (this._refCount === 0) {347this._styleSheet.remove();348this._parent._removeEditorStyleSheets(this._editorId);349}350}351352public insertRule(selector: string, rule: string): void {353domStylesheets.createCSSRule(selector, rule, this._styleSheet);354}355356public removeRulesContainingSelector(ruleName: string): void {357domStylesheets.removeCSSRulesContainingSelector(ruleName, this._styleSheet);358}359}360361export class GlobalStyleSheet {362private readonly _styleSheet: HTMLStyleElement;363364public get sheet() {365return this._styleSheet.sheet as CSSStyleSheet;366}367368constructor(styleSheet: HTMLStyleElement) {369this._styleSheet = styleSheet;370}371372public ref(): void {373}374375public unref(): void {376}377378public insertRule(selector: string, rule: string): void {379domStylesheets.createCSSRule(selector, rule, this._styleSheet);380}381382public removeRulesContainingSelector(ruleName: string): void {383domStylesheets.removeCSSRulesContainingSelector(ruleName, this._styleSheet);384}385}386387interface IModelDecorationOptionsProvider extends IDisposable {388refCount: number;389getOptions(codeEditorService: AbstractCodeEditorService, writable: boolean): IModelDecorationOptions;390resolveDecorationCSSRules(): CSSRuleList;391}392393class DecorationSubTypeOptionsProvider implements IModelDecorationOptionsProvider {394395private readonly _styleSheet: GlobalStyleSheet | RefCountedStyleSheet;396public refCount: number;397398private readonly _parentTypeKey: string;399private _beforeContentRules: DecorationCSSRules | null;400private _afterContentRules: DecorationCSSRules | null;401402constructor(themeService: IThemeService, styleSheet: GlobalStyleSheet | RefCountedStyleSheet, providerArgs: ProviderArguments) {403this._styleSheet = styleSheet;404this._styleSheet.ref();405this._parentTypeKey = providerArgs.parentTypeKey!;406this.refCount = 0;407408this._beforeContentRules = new DecorationCSSRules(ModelDecorationCSSRuleType.BeforeContentClassName, providerArgs, themeService);409this._afterContentRules = new DecorationCSSRules(ModelDecorationCSSRuleType.AfterContentClassName, providerArgs, themeService);410}411412public getOptions(codeEditorService: AbstractCodeEditorService, writable: boolean): IModelDecorationOptions {413const options = codeEditorService.resolveDecorationOptions(this._parentTypeKey, true);414if (this._beforeContentRules) {415options.beforeContentClassName = this._beforeContentRules.className;416}417if (this._afterContentRules) {418options.afterContentClassName = this._afterContentRules.className;419}420return options;421}422423public resolveDecorationCSSRules(): CSSRuleList {424return this._styleSheet.sheet.cssRules;425}426427public dispose(): void {428if (this._beforeContentRules) {429this._beforeContentRules.dispose();430this._beforeContentRules = null;431}432if (this._afterContentRules) {433this._afterContentRules.dispose();434this._afterContentRules = null;435}436this._styleSheet.unref();437}438}439440interface ProviderArguments {441styleSheet: GlobalStyleSheet | RefCountedStyleSheet;442key: string;443parentTypeKey?: string;444options: IDecorationRenderOptions;445}446447448class DecorationTypeOptionsProvider implements IModelDecorationOptionsProvider {449450private readonly _disposables = new DisposableStore();451private readonly _styleSheet: GlobalStyleSheet | RefCountedStyleSheet;452public refCount: number;453454public description: string;455public className: string | undefined;456public inlineClassName: string | undefined;457public inlineClassNameAffectsLetterSpacing: boolean | undefined;458public beforeContentClassName: string | undefined;459public afterContentClassName: string | undefined;460public glyphMarginClassName: string | undefined;461public isWholeLine: boolean;462public lineHeight: number | undefined;463public fontSize: string | undefined;464public fontFamily: string | undefined;465public fontWeight: string | undefined;466public fontStyle: string | undefined;467public overviewRuler: IModelDecorationOverviewRulerOptions | undefined;468public stickiness: TrackedRangeStickiness | undefined;469public beforeInjectedText: InjectedTextOptions | undefined;470public afterInjectedText: InjectedTextOptions | undefined;471472constructor(description: string, themeService: IThemeService, styleSheet: GlobalStyleSheet | RefCountedStyleSheet, providerArgs: ProviderArguments) {473this.description = description;474475this._styleSheet = styleSheet;476this._styleSheet.ref();477this.refCount = 0;478479const createCSSRules = (type: ModelDecorationCSSRuleType) => {480const rules = new DecorationCSSRules(type, providerArgs, themeService);481this._disposables.add(rules);482if (rules.hasContent) {483return rules.className;484}485return undefined;486};487const createInlineCSSRules = (type: ModelDecorationCSSRuleType) => {488const rules = new DecorationCSSRules(type, providerArgs, themeService);489this._disposables.add(rules);490if (rules.hasContent) {491return { className: rules.className, hasLetterSpacing: rules.hasLetterSpacing };492}493return null;494};495496this.className = createCSSRules(ModelDecorationCSSRuleType.ClassName);497const inlineData = createInlineCSSRules(ModelDecorationCSSRuleType.InlineClassName);498if (inlineData) {499this.inlineClassName = inlineData.className;500this.inlineClassNameAffectsLetterSpacing = inlineData.hasLetterSpacing;501}502this.beforeContentClassName = createCSSRules(ModelDecorationCSSRuleType.BeforeContentClassName);503this.afterContentClassName = createCSSRules(ModelDecorationCSSRuleType.AfterContentClassName);504505if (providerArgs.options.beforeInjectedText && providerArgs.options.beforeInjectedText.contentText) {506const beforeInlineData = createInlineCSSRules(ModelDecorationCSSRuleType.BeforeInjectedTextClassName);507this.beforeInjectedText = {508content: providerArgs.options.beforeInjectedText.contentText,509inlineClassName: beforeInlineData?.className,510inlineClassNameAffectsLetterSpacing: beforeInlineData?.hasLetterSpacing || providerArgs.options.beforeInjectedText.affectsLetterSpacing511};512}513514if (providerArgs.options.afterInjectedText && providerArgs.options.afterInjectedText.contentText) {515const afterInlineData = createInlineCSSRules(ModelDecorationCSSRuleType.AfterInjectedTextClassName);516this.afterInjectedText = {517content: providerArgs.options.afterInjectedText.contentText,518inlineClassName: afterInlineData?.className,519inlineClassNameAffectsLetterSpacing: afterInlineData?.hasLetterSpacing || providerArgs.options.afterInjectedText.affectsLetterSpacing520};521}522523this.glyphMarginClassName = createCSSRules(ModelDecorationCSSRuleType.GlyphMarginClassName);524525const options = providerArgs.options;526this.isWholeLine = Boolean(options.isWholeLine);527this.lineHeight = options.lineHeight;528this.fontFamily = options.fontFamily;529this.fontSize = options.fontSize;530this.fontWeight = options.fontWeight;531this.fontStyle = options.fontStyle;532this.stickiness = options.rangeBehavior;533534const lightOverviewRulerColor = options.light && options.light.overviewRulerColor || options.overviewRulerColor;535const darkOverviewRulerColor = options.dark && options.dark.overviewRulerColor || options.overviewRulerColor;536if (537typeof lightOverviewRulerColor !== 'undefined'538|| typeof darkOverviewRulerColor !== 'undefined'539) {540this.overviewRuler = {541color: lightOverviewRulerColor || darkOverviewRulerColor,542darkColor: darkOverviewRulerColor || lightOverviewRulerColor,543position: options.overviewRulerLane || OverviewRulerLane.Center544};545}546}547548public getOptions(codeEditorService: AbstractCodeEditorService, writable: boolean): IModelDecorationOptions {549if (!writable) {550return this;551}552553return {554description: this.description,555inlineClassName: this.inlineClassName,556beforeContentClassName: this.beforeContentClassName,557afterContentClassName: this.afterContentClassName,558className: this.className,559glyphMarginClassName: this.glyphMarginClassName,560isWholeLine: this.isWholeLine,561lineHeight: this.lineHeight,562fontFamily: this.fontFamily,563fontSize: this.fontSize,564fontWeight: this.fontWeight,565fontStyle: this.fontStyle,566overviewRuler: this.overviewRuler,567stickiness: this.stickiness,568before: this.beforeInjectedText,569after: this.afterInjectedText570};571}572573public resolveDecorationCSSRules(): CSSRuleList {574return this._styleSheet.sheet.rules;575}576577public dispose(): void {578this._disposables.dispose();579this._styleSheet.unref();580}581}582583584export const _CSS_MAP: { [prop: string]: string } = {585color: 'color:{0} !important;',586opacity: 'opacity:{0};',587backgroundColor: 'background-color:{0};',588589outline: 'outline:{0};',590outlineColor: 'outline-color:{0};',591outlineStyle: 'outline-style:{0};',592outlineWidth: 'outline-width:{0};',593594border: 'border:{0};',595borderColor: 'border-color:{0};',596borderRadius: 'border-radius:{0};',597borderSpacing: 'border-spacing:{0};',598borderStyle: 'border-style:{0};',599borderWidth: 'border-width:{0};',600601fontStyle: 'font-style:{0};',602fontWeight: 'font-weight:{0};',603fontSize: 'font-size:{0};',604fontFamily: 'font-family:{0};',605textDecoration: 'text-decoration:{0};',606cursor: 'cursor:{0};',607letterSpacing: 'letter-spacing:{0};',608609gutterIconPath: 'background:{0} center center no-repeat;',610gutterIconSize: 'background-size:{0};',611612contentText: 'content:\'{0}\';',613contentIconPath: 'content:{0};',614margin: 'margin:{0};',615padding: 'padding:{0};',616width: 'width:{0};',617height: 'height:{0};',618619verticalAlign: 'vertical-align:{0};',620};621622623class DecorationCSSRules {624625private _theme: IColorTheme;626private readonly _className: string;627private readonly _unThemedSelector: string;628private _hasContent: boolean;629private _hasLetterSpacing: boolean;630private readonly _ruleType: ModelDecorationCSSRuleType;631private _themeListener: IDisposable | null;632private readonly _providerArgs: ProviderArguments;633private _usesThemeColors: boolean;634635constructor(ruleType: ModelDecorationCSSRuleType, providerArgs: ProviderArguments, themeService: IThemeService) {636this._theme = themeService.getColorTheme();637this._ruleType = ruleType;638this._providerArgs = providerArgs;639this._usesThemeColors = false;640this._hasContent = false;641this._hasLetterSpacing = false;642643let className = CSSNameHelper.getClassName(this._providerArgs.key, ruleType);644if (this._providerArgs.parentTypeKey) {645className = className + ' ' + CSSNameHelper.getClassName(this._providerArgs.parentTypeKey, ruleType);646}647this._className = className;648649this._unThemedSelector = CSSNameHelper.getSelector(this._providerArgs.key, this._providerArgs.parentTypeKey, ruleType);650651this._buildCSS();652653if (this._usesThemeColors) {654this._themeListener = themeService.onDidColorThemeChange(theme => {655this._theme = themeService.getColorTheme();656this._removeCSS();657this._buildCSS();658});659} else {660this._themeListener = null;661}662}663664public dispose() {665if (this._hasContent) {666this._removeCSS();667this._hasContent = false;668}669if (this._themeListener) {670this._themeListener.dispose();671this._themeListener = null;672}673}674675public get hasContent(): boolean {676return this._hasContent;677}678679public get hasLetterSpacing(): boolean {680return this._hasLetterSpacing;681}682683public get className(): string {684return this._className;685}686687private _buildCSS(): void {688const options = this._providerArgs.options;689let unthemedCSS: string, lightCSS: string, darkCSS: string;690switch (this._ruleType) {691case ModelDecorationCSSRuleType.ClassName:692unthemedCSS = this.getCSSTextForModelDecorationClassName(options);693lightCSS = this.getCSSTextForModelDecorationClassName(options.light);694darkCSS = this.getCSSTextForModelDecorationClassName(options.dark);695break;696case ModelDecorationCSSRuleType.InlineClassName:697unthemedCSS = this.getCSSTextForModelDecorationInlineClassName(options);698lightCSS = this.getCSSTextForModelDecorationInlineClassName(options.light);699darkCSS = this.getCSSTextForModelDecorationInlineClassName(options.dark);700break;701case ModelDecorationCSSRuleType.GlyphMarginClassName:702unthemedCSS = this.getCSSTextForModelDecorationGlyphMarginClassName(options);703lightCSS = this.getCSSTextForModelDecorationGlyphMarginClassName(options.light);704darkCSS = this.getCSSTextForModelDecorationGlyphMarginClassName(options.dark);705break;706case ModelDecorationCSSRuleType.BeforeContentClassName:707unthemedCSS = this.getCSSTextForModelDecorationContentClassName(options.before);708lightCSS = this.getCSSTextForModelDecorationContentClassName(options.light && options.light.before);709darkCSS = this.getCSSTextForModelDecorationContentClassName(options.dark && options.dark.before);710break;711case ModelDecorationCSSRuleType.AfterContentClassName:712unthemedCSS = this.getCSSTextForModelDecorationContentClassName(options.after);713lightCSS = this.getCSSTextForModelDecorationContentClassName(options.light && options.light.after);714darkCSS = this.getCSSTextForModelDecorationContentClassName(options.dark && options.dark.after);715break;716case ModelDecorationCSSRuleType.BeforeInjectedTextClassName:717unthemedCSS = this.getCSSTextForModelDecorationContentClassName(options.beforeInjectedText);718lightCSS = this.getCSSTextForModelDecorationContentClassName(options.light && options.light.beforeInjectedText);719darkCSS = this.getCSSTextForModelDecorationContentClassName(options.dark && options.dark.beforeInjectedText);720break;721case ModelDecorationCSSRuleType.AfterInjectedTextClassName:722unthemedCSS = this.getCSSTextForModelDecorationContentClassName(options.afterInjectedText);723lightCSS = this.getCSSTextForModelDecorationContentClassName(options.light && options.light.afterInjectedText);724darkCSS = this.getCSSTextForModelDecorationContentClassName(options.dark && options.dark.afterInjectedText);725break;726default:727throw new Error('Unknown rule type: ' + this._ruleType);728}729const sheet = this._providerArgs.styleSheet;730731let hasContent = false;732if (unthemedCSS.length > 0) {733sheet.insertRule(this._unThemedSelector, unthemedCSS);734hasContent = true;735}736if (lightCSS.length > 0) {737sheet.insertRule(`.vs${this._unThemedSelector}, .hc-light${this._unThemedSelector}`, lightCSS);738hasContent = true;739}740if (darkCSS.length > 0) {741sheet.insertRule(`.vs-dark${this._unThemedSelector}, .hc-black${this._unThemedSelector}`, darkCSS);742hasContent = true;743}744this._hasContent = hasContent;745}746747private _removeCSS(): void {748this._providerArgs.styleSheet.removeRulesContainingSelector(this._unThemedSelector);749}750751/**752* Build the CSS for decorations styled via `className`.753*/754private getCSSTextForModelDecorationClassName(opts: IThemeDecorationRenderOptions | undefined): string {755if (!opts) {756return '';757}758const cssTextArr: string[] = [];759this.collectCSSText(opts, ['backgroundColor'], cssTextArr);760this.collectCSSText(opts, ['outline', 'outlineColor', 'outlineStyle', 'outlineWidth'], cssTextArr);761this.collectBorderSettingsCSSText(opts, cssTextArr);762return cssTextArr.join('');763}764765/**766* Build the CSS for decorations styled via `inlineClassName`.767*/768private getCSSTextForModelDecorationInlineClassName(opts: IThemeDecorationRenderOptions | undefined): string {769if (!opts) {770return '';771}772const cssTextArr: string[] = [];773this.collectCSSText(opts, ['fontStyle', 'fontWeight', 'fontFamily', 'fontSize', 'textDecoration', 'cursor', 'color', 'opacity', 'letterSpacing'], cssTextArr);774if (opts.letterSpacing) {775this._hasLetterSpacing = true;776}777return cssTextArr.join('');778}779780/**781* Build the CSS for decorations styled before or after content.782*/783private getCSSTextForModelDecorationContentClassName(opts: IContentDecorationRenderOptions | undefined): string {784if (!opts) {785return '';786}787const cssTextArr: string[] = [];788789if (typeof opts !== 'undefined') {790this.collectBorderSettingsCSSText(opts, cssTextArr);791if (typeof opts.contentIconPath !== 'undefined') {792cssTextArr.push(strings.format(_CSS_MAP.contentIconPath, cssJs.asCSSUrl(URI.revive(opts.contentIconPath))));793}794if (typeof opts.contentText === 'string') {795const truncated = opts.contentText.match(/^.*$/m)![0]; // only take first line796const escaped = truncated.replace(/['\\]/g, '\\$&');797798cssTextArr.push(strings.format(_CSS_MAP.contentText, escaped));799}800this.collectCSSText(opts, ['verticalAlign', 'fontStyle', 'fontWeight', 'fontSize', 'fontFamily', 'textDecoration', 'color', 'opacity', 'backgroundColor', 'margin', 'padding'], cssTextArr);801if (this.collectCSSText(opts, ['width', 'height'], cssTextArr)) {802cssTextArr.push('display:inline-block;');803}804}805806return cssTextArr.join('');807}808809/**810* Build the CSS for decorations styled via `glyphMarginClassName`.811*/812private getCSSTextForModelDecorationGlyphMarginClassName(opts: IThemeDecorationRenderOptions | undefined): string {813if (!opts) {814return '';815}816const cssTextArr: string[] = [];817818if (typeof opts.gutterIconPath !== 'undefined') {819cssTextArr.push(strings.format(_CSS_MAP.gutterIconPath, cssJs.asCSSUrl(URI.revive(opts.gutterIconPath))));820if (typeof opts.gutterIconSize !== 'undefined') {821cssTextArr.push(strings.format(_CSS_MAP.gutterIconSize, opts.gutterIconSize));822}823}824825return cssTextArr.join('');826}827828private collectBorderSettingsCSSText(opts: any, cssTextArr: string[]): boolean {829if (this.collectCSSText(opts, ['border', 'borderColor', 'borderRadius', 'borderSpacing', 'borderStyle', 'borderWidth'], cssTextArr)) {830cssTextArr.push(strings.format('box-sizing: border-box;'));831return true;832}833return false;834}835836private collectCSSText(opts: any, properties: string[], cssTextArr: string[]): boolean {837const lenBefore = cssTextArr.length;838for (const property of properties) {839const value = this.resolveValue(opts[property]);840if (typeof value === 'string') {841cssTextArr.push(strings.format(_CSS_MAP[property], value));842}843}844return cssTextArr.length !== lenBefore;845}846847private resolveValue(value: string | ThemeColor): string {848if (isThemeColor(value)) {849this._usesThemeColors = true;850const color = this._theme.getColor(value.id);851if (color) {852return color.toString();853}854return 'transparent';855}856return value;857}858}859860const enum ModelDecorationCSSRuleType {861ClassName = 0,862InlineClassName = 1,863GlyphMarginClassName = 2,864BeforeContentClassName = 3,865AfterContentClassName = 4,866BeforeInjectedTextClassName = 5,867AfterInjectedTextClassName = 6,868}869870class CSSNameHelper {871872public static getClassName(key: string, type: ModelDecorationCSSRuleType): string {873return 'ced-' + key + '-' + type;874}875876public static getSelector(key: string, parentKey: string | undefined, ruleType: ModelDecorationCSSRuleType): string {877let selector = '.monaco-editor .' + this.getClassName(key, ruleType);878if (parentKey) {879selector = selector + '.' + this.getClassName(parentKey, ruleType);880}881if (ruleType === ModelDecorationCSSRuleType.BeforeContentClassName) {882selector += '::before';883} else if (ruleType === ModelDecorationCSSRuleType.AfterContentClassName) {884selector += '::after';885}886return selector;887}888}889890891