提交 c9890111 编写于 作者: M Martin Aeschlimann

Add befor/after decorations

上级 afbe654b
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
/**
* Return a hash value for an object.
*/
export function hash(obj: any, hashVal = 0) : number {
switch (typeof obj) {
case 'object':
if (obj === null) {
return numberHash(349, hashVal);
} else if (Array.isArray(obj)) {
return arrayHash(obj, hashVal);
}
return objectHash(obj, hashVal);
case 'string':
return stringHash(obj, hashVal);
case 'boolean':
return booleanHash(obj, hashVal);
case 'number':
return numberHash(obj, hashVal);
case 'undefined':
return numberHash(obj, 937);
default:
return numberHash(obj, 617);
}
}
function numberHash(val: number, initialHashVal: number) : number {
return (((initialHashVal << 5) - initialHashVal) + val) | 0; // hashVal * 31 + ch, keep as int32
}
function booleanHash(b: boolean, initialHashVal: number) : number {
return numberHash(b ? 433 : 863, initialHashVal);
}
function stringHash(s: string, hashVal: number) {
hashVal = numberHash(149417, hashVal);
for (let i = 0, length = s.length; i < length; i++) {
hashVal = numberHash(s.charCodeAt(i), hashVal);
}
return hashVal;
}
function arrayHash(arr: any[], initialHashVal: number) : number {
initialHashVal = numberHash(104579, initialHashVal);
return arr.reduce((hashVal, item) => hash(item, hashVal), initialHashVal);
}
function objectHash(obj: any, initialHashVal: number) : number {
initialHashVal = numberHash(181387, initialHashVal);
return Object.keys(obj).sort().reduce((hashVal, key) => {
hashVal = stringHash(key, hashVal);
return hash(obj[key], hashVal);
}, initialHashVal);
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import {hash} from 'vs/base/common/hash';
suite('Hash', () => {
test('string', () => {
assert.equal(hash('hello'), hash('hello'));
assert.notEqual(hash('hello'), hash('world'));
assert.notEqual(hash('hello'), hash('olleh'));
assert.notEqual(hash('hello'), hash('Hello'));
assert.notEqual(hash('hello'), hash('Hello '));
assert.notEqual(hash('h'), hash('H'));
assert.notEqual(hash('-'), hash('_'));
});
test('number', () => {
assert.equal(hash(1), hash(1.0));
assert.notEqual(hash(0), hash(1));
assert.notEqual(hash(1), hash(-1));
assert.notEqual(hash(0x12345678), hash(0x123456789));
});
test('boolean', () => {
assert.equal(hash(true), hash(true));
assert.notEqual(hash(true), hash(false));
});
test('array', () => {
assert.equal(hash([1, 2, 3]), hash([1, 2, 3]));
assert.equal(hash(['foo', 'bar']), hash(['foo', 'bar']));
assert.equal(hash([]), hash([]));
assert.notEqual(hash(['foo', 'bar']), hash(['bar', 'foo']));
assert.notEqual(hash(['foo', 'bar']), hash(['bar', 'foo', null]));
});
test('object', () => {
assert.equal(hash({}), hash({}));
assert.equal(hash({'foo': 'bar'}), hash({'foo': 'bar'}));
assert.equal(hash({'foo': 'bar', 'foo2': void 0}), hash({'foo2': void 0, 'foo': 'bar'}));
assert.notEqual(hash({'foo': 'bar'}), hash({'foo': 'bar2'}));
assert.notEqual(hash({}), hash([]));
});
});
\ No newline at end of file
......@@ -8,86 +8,196 @@ import * as objects from 'vs/base/common/objects';
import * as strings from 'vs/base/common/strings';
import URI from 'vs/base/common/uri';
import * as dom from 'vs/base/browser/dom';
import {IDecorationRenderOptions, IModelDecorationOptions, IModelDecorationOverviewRulerOptions, IThemeDecorationRenderOptions, OverviewRulerLane} from 'vs/editor/common/editorCommon';
import {IDecorationRenderOptions, IModelDecorationOptions, IModelDecorationOverviewRulerOptions, IThemeDecorationRenderOptions,
IContentDecorationRenderOptions, OverviewRulerLane, TrackedRangeStickiness, IDecorationInstanceRenderOptions} from 'vs/editor/common/editorCommon';
import {AbstractCodeEditorService} from 'vs/editor/common/services/abstractCodeEditorService';
import {IDisposable, toDisposable} from 'vs/base/common/lifecycle';
export class CodeEditorServiceImpl extends AbstractCodeEditorService {
private _styleSheet: HTMLStyleElement;
private _decorationRenderOptions: {[key:string]:DecorationRenderOptions};
private _decorationOptionProviders: {[key:string]:IModelDecorationOptionsProvider};
constructor() {
super();
this._styleSheet = dom.createStyleSheet();
this._decorationRenderOptions = Object.create(null);
this._decorationOptionProviders = Object.create(null);
}
public registerDecorationType(key:string, options: IDecorationRenderOptions): void {
if (this._decorationRenderOptions[key]) {
this._decorationRenderOptions[key].dispose();
delete this._decorationRenderOptions[key];
let provider = this._decorationOptionProviders[key];
if (provider) {
provider.dispose();
delete this._decorationOptionProviders[key];
}
provider = new DecorationTypeOptionsProvider(this._styleSheet, key, options);
this._decorationOptionProviders[key] = provider;
provider.refCount++;
}
public registerDecorationSubType(key:string, parentTypeKey: string, options: IDecorationInstanceRenderOptions): void {
let provider = this._decorationOptionProviders[key];
if (!provider) {
provider = new DecorationSubTypeOptionsProvider(this._styleSheet, key, parentTypeKey, options);
this._decorationOptionProviders[key] = provider;
provider.refCount++;
}
var decorationRenderOptions = new DecorationRenderOptions(this._styleSheet, key, options);
this._decorationRenderOptions[key] = decorationRenderOptions;
}
public removeDecorationType(key:string): void {
if (this._decorationRenderOptions[key]) {
this._decorationRenderOptions[key].dispose();
delete this._decorationRenderOptions[key];
let provider = this._decorationOptionProviders[key];
if (provider) {
provider.refCount--;
if (provider.refCount <= 0) {
delete this._decorationOptionProviders[key];
provider.dispose();
this.listCodeEditors().forEach((ed) => ed.removeDecorations(key));
}
}
}
this.listCodeEditors().forEach((ed) => ed.removeDecorations(key));
public resolveDecorationOptions(decorationTypeKey:string, writable: boolean): IModelDecorationOptions {
let provider = this._decorationOptionProviders[decorationTypeKey];
if (!provider) {
throw new Error('Unknown decoration type key: ' + decorationTypeKey);
}
return provider.getOptions(this, writable);
}
public resolveDecorationType(key:string): IModelDecorationOptions {
if (this._decorationRenderOptions[key]) {
return this._decorationRenderOptions[key];
}
interface IModelDecorationOptionsProvider extends IDisposable {
refCount: number;
getOptions(codeEditorService: AbstractCodeEditorService, writable: boolean): IModelDecorationOptions;
}
class DecorationSubTypeOptionsProvider implements IModelDecorationOptionsProvider {
public refCount: number;
private _disposable: IDisposable;
private _parentTypeKey: string;
private _inlineClassName: string;
private _stickiness: TrackedRangeStickiness;
constructor(styleSheet: HTMLStyleElement, key: string, parentTypeKey: string, options:IDecorationInstanceRenderOptions) {
this._parentTypeKey = parentTypeKey;
this.refCount = 0;
var themedOpts = getThemedRenderOptions(options);
let inlineClassName = DecorationRenderHelper.handle(
styleSheet,
key,
parentTypeKey,
ModelDecorationCSSRuleType.InlineClassName,
{
light: DecorationRenderHelper.getCSSTextForModelDecorationContentClassName(themedOpts.light.after),
dark: DecorationRenderHelper.getCSSTextForModelDecorationContentClassName(themedOpts.dark.after),
selector: '::after'
},
{
light: DecorationRenderHelper.getCSSTextForModelDecorationContentClassName(themedOpts.light.before),
dark: DecorationRenderHelper.getCSSTextForModelDecorationContentClassName(themedOpts.dark.before),
selector: '::before'
}
);
if (inlineClassName) {
this._stickiness = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges;
this._inlineClassName = inlineClassName;
this._disposable = toDisposable(() => {
dom.removeCSSRulesWithPrefix(CSSNameHelper.getDeletionPrefixFor(ThemeType.Light, key), styleSheet);
dom.removeCSSRulesWithPrefix(CSSNameHelper.getDeletionPrefixFor(ThemeType.Dark, key), styleSheet);
dom.removeCSSRulesWithPrefix(CSSNameHelper.getDeletionPrefixFor(ThemeType.HighContrastBlack, key), styleSheet);
});
}
throw new Error('Unknown decoration type key: ' + key);
}
public getOptions(codeEditorService: AbstractCodeEditorService, writable: boolean): IModelDecorationOptions {
let options = codeEditorService.resolveDecorationOptions(this._parentTypeKey, true);
if (this._inlineClassName) {
options.inlineClassName = this._inlineClassName;
}
if (this._stickiness) {
options.stickiness = this._stickiness;
}
return options;
}
public dispose(): void {
if (this._disposable) {
this._disposable.dispose();
delete this._disposable;
}
}
}
class DecorationRenderOptions implements IModelDecorationOptions {
class DecorationTypeOptionsProvider implements IModelDecorationOptionsProvider {
private _styleSheet: HTMLStyleElement;
private _key: string;
public _key: string;
public refCount: number;
public className: string;
public inlineClassName: string;
public glyphMarginClassName: string;
public isWholeLine:boolean;
public overviewRuler:IModelDecorationOverviewRulerOptions;
public stickiness: TrackedRangeStickiness;
constructor(styleSheet: HTMLStyleElement, key:string, options:IDecorationRenderOptions) {
var themedOpts = resolveDecorationRenderOptions(options);
var themedOpts = getThemedRenderOptions(options);
this._styleSheet = styleSheet;
this._key = key;
this.className = DecorationRenderOptions._handle(
this.className = DecorationRenderHelper.handle(
this._styleSheet,
this._key,
null,
ModelDecorationCSSRuleType.ClassName,
DecorationRenderOptions._getCSSTextForModelDecorationClassName(themedOpts.light),
DecorationRenderOptions._getCSSTextForModelDecorationClassName(themedOpts.dark)
{
light: DecorationRenderHelper.getCSSTextForModelDecorationClassName(themedOpts.light),
dark: DecorationRenderHelper.getCSSTextForModelDecorationClassName(themedOpts.dark)
}
);
this.inlineClassName = DecorationRenderOptions._handle(
this.inlineClassName = DecorationRenderHelper.handle(
this._styleSheet,
this._key,
null,
ModelDecorationCSSRuleType.InlineClassName,
DecorationRenderOptions._getCSSTextForModelDecorationInlineClassName(themedOpts.light),
DecorationRenderOptions._getCSSTextForModelDecorationInlineClassName(themedOpts.dark)
{
light: DecorationRenderHelper.getCSSTextForModelDecorationInlineClassName(themedOpts.light),
dark: DecorationRenderHelper.getCSSTextForModelDecorationInlineClassName(themedOpts.dark)
},
{
light: DecorationRenderHelper.getCSSTextForModelDecorationContentClassName(themedOpts.light.after),
dark: DecorationRenderHelper.getCSSTextForModelDecorationContentClassName(themedOpts.dark.after),
selector: '::after'
},
{
light: DecorationRenderHelper.getCSSTextForModelDecorationContentClassName(themedOpts.light.before),
dark: DecorationRenderHelper.getCSSTextForModelDecorationContentClassName(themedOpts.dark.before),
selector: '::before'
}
);
this.glyphMarginClassName = DecorationRenderOptions._handle(
if (themedOpts.light.before || themedOpts.light.after || themedOpts.dark.before || themedOpts.dark.after) {
this.stickiness = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges;
}
this.glyphMarginClassName = DecorationRenderHelper.handle(
this._styleSheet,
this._key,
null,
ModelDecorationCSSRuleType.GlyphMarginClassName,
DecorationRenderOptions._getCSSTextForModelDecorationGlyphMarginClassName(themedOpts.light),
DecorationRenderOptions._getCSSTextForModelDecorationGlyphMarginClassName(themedOpts.dark)
{
light: DecorationRenderHelper.getCSSTextForModelDecorationGlyphMarginClassName(themedOpts.light),
dark: DecorationRenderHelper.getCSSTextForModelDecorationGlyphMarginClassName(themedOpts.dark)
}
);
this.isWholeLine = Boolean(options.isWholeLine);
......@@ -104,12 +214,28 @@ class DecorationRenderOptions implements IModelDecorationOptions {
}
}
public getOptions(codeEditorService: AbstractCodeEditorService, writable: boolean): IModelDecorationOptions {
if (!writable) {
return this;
}
return {
inlineClassName: this.inlineClassName,
className: this.className,
glyphMarginClassName: this.glyphMarginClassName,
isWholeLine: this.isWholeLine,
overviewRuler: this.overviewRuler,
stickiness: this.stickiness
};
}
public dispose(): void {
dom.removeCSSRulesWithPrefix(CSSNameHelper.getDeletionPrefixFor(ThemeType.Light, this._key), this._styleSheet);
dom.removeCSSRulesWithPrefix(CSSNameHelper.getDeletionPrefixFor(ThemeType.Dark, this._key), this._styleSheet);
dom.removeCSSRulesWithPrefix(CSSNameHelper.getDeletionPrefixFor(ThemeType.HighContrastBlack, this._key), this._styleSheet);
}
}
class DecorationRenderHelper {
private static _CSS_MAP = {
color: 'color:{0} !important;',
backgroundColor: 'background-color:{0};',
......@@ -129,12 +255,17 @@ class DecorationRenderOptions implements IModelDecorationOptions {
letterSpacing: 'letter-spacing:{0};',
gutterIconPath: 'background:url(\'{0}\') center center no-repeat;',
content: 'content:{0};',
margin: 'margin:{0};',
width: 'width:{0};',
height: 'height:{0};'
};
/**
* Build the CSS for decorations styled via `className`.
*/
private static _getCSSTextForModelDecorationClassName(opts:IThemeDecorationRenderOptions): string {
public static getCSSTextForModelDecorationClassName(opts:IThemeDecorationRenderOptions): string {
let cssTextArr = [];
if (typeof opts.backgroundColor !== 'undefined') {
......@@ -183,7 +314,7 @@ class DecorationRenderOptions implements IModelDecorationOptions {
/**
* Build the CSS for decorations styled via `inlineClassName`.
*/
private static _getCSSTextForModelDecorationInlineClassName(opts:IThemeDecorationRenderOptions): string {
public static getCSSTextForModelDecorationInlineClassName(opts:IThemeDecorationRenderOptions): string {
let cssTextArr = [];
if (typeof opts.textDecoration !== 'undefined') {
......@@ -202,10 +333,74 @@ class DecorationRenderOptions implements IModelDecorationOptions {
return cssTextArr.join('');
}
/**
* Build the CSS for decorations styled before or after content.
*/
public static getCSSTextForModelDecorationContentClassName(opts:IContentDecorationRenderOptions): string {
let cssTextArr = [];
if (typeof opts !== 'undefined') {
if (
typeof opts.borderColor !== 'undefined'
|| typeof opts.borderRadius !== 'undefined'
|| typeof opts.borderSpacing !== 'undefined'
|| typeof opts.borderStyle !== 'undefined'
|| typeof opts.borderWidth !== 'undefined'
) {
cssTextArr.push(strings.format('box-sizing: border-box;'));
}
if (typeof opts.borderColor !== 'undefined') {
cssTextArr.push(strings.format(this._CSS_MAP.borderColor, opts.borderColor));
}
if (typeof opts.borderRadius !== 'undefined') {
cssTextArr.push(strings.format(this._CSS_MAP.borderRadius, opts.borderRadius));
}
if (typeof opts.borderSpacing !== 'undefined') {
cssTextArr.push(strings.format(this._CSS_MAP.borderSpacing, opts.borderSpacing));
}
if (typeof opts.borderStyle !== 'undefined') {
cssTextArr.push(strings.format(this._CSS_MAP.borderStyle, opts.borderStyle));
}
if (typeof opts.borderWidth !== 'undefined') {
cssTextArr.push(strings.format(this._CSS_MAP.borderWidth, opts.borderWidth));
}
if (typeof opts.content !== 'undefined') {
cssTextArr.push(strings.format(this._CSS_MAP.content, opts.content));
}
if (typeof opts.textDecoration !== 'undefined') {
cssTextArr.push(strings.format(this._CSS_MAP.textDecoration, opts.textDecoration));
}
if (typeof opts.color !== 'undefined') {
cssTextArr.push(strings.format(this._CSS_MAP.color, opts.color));
}
if (typeof opts.backgroundColor !== 'undefined') {
cssTextArr.push(strings.format(this._CSS_MAP.backgroundColor, opts.backgroundColor));
}
if (typeof opts.margin !== 'undefined') {
cssTextArr.push(strings.format(this._CSS_MAP.margin, opts.margin));
}
let setInlineBlock = false;
if (typeof opts.width !== 'undefined') {
cssTextArr.push(strings.format(this._CSS_MAP.width, opts.width));
setInlineBlock = true;
}
if (typeof opts.height !== 'undefined') {
cssTextArr.push(strings.format(this._CSS_MAP.height, opts.height));
setInlineBlock = true;
}
if (setInlineBlock) {
cssTextArr.push('display:inline-block;');
}
}
return cssTextArr.join('');
}
/**
* Build the CSS for decorations styled via `glpyhMarginClassName`.
*/
private static _getCSSTextForModelDecorationGlyphMarginClassName(opts:IThemeDecorationRenderOptions): string {
public static getCSSTextForModelDecorationGlyphMarginClassName(opts:IThemeDecorationRenderOptions): string {
let cssTextArr = [];
if (typeof opts.gutterIconPath !== 'undefined') {
......@@ -215,22 +410,32 @@ class DecorationRenderOptions implements IModelDecorationOptions {
return cssTextArr.join('');
}
private static _handle(styleSheet: HTMLStyleElement, key:string, ruleType:ModelDecorationCSSRuleType, lightCSS:string, darkCSS:string): string {
if (lightCSS.length > 0 || darkCSS.length > 0) {
if (lightCSS.length > 0) {
this._createCSSSelector(styleSheet, ThemeType.Light, key, ruleType, lightCSS);
public static handle(styleSheet: HTMLStyleElement, key:string, parentKey: string, ruleType:ModelDecorationCSSRuleType, ...ruleSets: {selector?: string, light:string, dark:string}[]): string {
function createCSSSelector(themeType:ThemeType, pseudoSelector: string, cssText:string) {
let selector = CSSNameHelper.getSelector(themeType, key, parentKey, ruleType, pseudoSelector);
dom.createCSSRule(selector, cssText, styleSheet);
}
let hasContent = false;
for (let ruleSet of ruleSets) {
if (ruleSet.light.length > 0) {
createCSSSelector(ThemeType.Light, ruleSet.selector, ruleSet.light);
hasContent = true;
}
if (darkCSS.length > 0) {
this._createCSSSelector(styleSheet, ThemeType.Dark, key, ruleType, darkCSS);
this._createCSSSelector(styleSheet, ThemeType.HighContrastBlack, key, ruleType, darkCSS);
if (ruleSet.dark.length > 0) {
createCSSSelector(ThemeType.Dark, ruleSet.selector, ruleSet.dark);
createCSSSelector(ThemeType.HighContrastBlack, ruleSet.selector, ruleSet.dark);
hasContent = true;
}
return CSSNameHelper.getClassName(key, ruleType);
}
return undefined;
}
private static _createCSSSelector(styleSheet: HTMLStyleElement, themeType:ThemeType, key:string, ruleType:ModelDecorationCSSRuleType, cssText:string): void {
dom.createCSSRule(CSSNameHelper.getSelector(themeType, key, ruleType), cssText, styleSheet);
if (hasContent) {
let className = CSSNameHelper.getClassName(key, ruleType);
if (parentKey) {
className = className + ' ' + CSSNameHelper.getClassName(parentKey, ruleType);
}
return className;
}
return void 0;
}
}
......@@ -244,6 +449,7 @@ enum ModelDecorationCSSRuleType {
InlineClassName = 1,
GlyphMarginClassName = 2
}
class CSSNameHelper {
private static _getSelectorPrefixOf(theme:ThemeType): string {
......@@ -260,8 +466,15 @@ class CSSNameHelper {
return 'ced-' + key + '-' + type;
}
public static getSelector(themeType:ThemeType, key:string, ruleType:ModelDecorationCSSRuleType): string {
return this._getSelectorPrefixOf(themeType) + ' .' + this.getClassName(key, ruleType);
public static getSelector(themeType:ThemeType, key:string, parentKey: string, ruleType:ModelDecorationCSSRuleType, pseudoSelector:string): string {
let selector = this._getSelectorPrefixOf(themeType) + ' .' + this.getClassName(key, ruleType);
if (parentKey) {
selector = selector + '.' + this.getClassName(parentKey, ruleType);
}
if (pseudoSelector) {
selector = selector + pseudoSelector;
}
return selector;
}
public static getDeletionPrefixFor(themeType:ThemeType, key:string): string {
......@@ -274,11 +487,11 @@ interface IResolvedDecorationRenderOptions {
light: IThemeDecorationRenderOptions;
dark: IThemeDecorationRenderOptions;
}
function resolveDecorationRenderOptions(opts:IDecorationRenderOptions): IResolvedDecorationRenderOptions {
var light = objects.deepClone(opts);
function getThemedRenderOptions<T>(opts:{light?:T, dark?:T}): {light?:T, dark?:T} {
var light = <T> objects.deepClone(opts);
objects.mixin(light, opts.light);
var dark = objects.deepClone(opts);
var dark = <T> objects.deepClone(opts);
objects.mixin(dark, opts.dark);
return {
......
......@@ -8,7 +8,6 @@ import {IAction, IActionProvider, isAction} from 'vs/base/common/actions';
import {onUnexpectedError} from 'vs/base/common/errors';
import {EventEmitter, IEventEmitter} from 'vs/base/common/eventEmitter';
import {IDisposable, dispose} from 'vs/base/common/lifecycle';
import * as objects from 'vs/base/common/objects';
import * as timer from 'vs/base/common/timer';
import {TPromise} from 'vs/base/common/winjs.base';
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
......@@ -30,6 +29,7 @@ import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService';
import {CharacterHardWrappingLineMapperFactory} from 'vs/editor/common/viewModel/characterHardWrappingLineMapper';
import {SplitLinesCollection} from 'vs/editor/common/viewModel/splitLinesCollection';
import {ViewModel} from 'vs/editor/common/viewModel/viewModelImpl';
import {hash} from 'vs/base/common/hash';
var EDITOR_ID = 0;
......@@ -103,7 +103,7 @@ export abstract class CommonCodeEditor extends EventEmitter implements IActionPr
protected _instantiationService: IInstantiationService;
protected _keybindingService: IKeybindingService;
private _decorationTypeKeysToIds: {[decorationTypeKey:string]:string[];};
private _decorationTypeKeysToIds: {[decorationTypeKey:string]:{ [subtype:string]:string[]}};
private _codeEditorService: ICodeEditorService;
private _editorIdContextKey: IKeybindingContextKey<string>;
......@@ -651,28 +651,68 @@ export abstract class CommonCodeEditor extends EventEmitter implements IActionPr
return this.model.deltaDecorations(oldDecorations, newDecorations, this.id);
}
public setDecorations(decorationTypeKey: string, ranges:editorCommon.IRangeWithMessage[]): void {
var opts = this._codeEditorService.resolveDecorationType(decorationTypeKey);
var oldDecorationIds = this._decorationTypeKeysToIds[decorationTypeKey] || [];
this._decorationTypeKeysToIds[decorationTypeKey] = this.deltaDecorations(oldDecorationIds, ranges.map((r) : editorCommon.IModelDeltaDecoration => {
let decOpts: editorCommon.IModelDecorationOptions;
if (r.hoverMessage) {
// TODO@Alex: avoid objects.clone
decOpts = objects.clone(opts);
decOpts.htmlMessage = r.hoverMessage;
} else {
decOpts = opts;
public setDecorations(decorationTypeKey: string, decorationOptions:editorCommon.IDecorationOptions[]): void {
let oldDecorationsIds = this._decorationTypeKeysToIds[decorationTypeKey];
if (!oldDecorationsIds) {
oldDecorationsIds = this._decorationTypeKeysToIds[decorationTypeKey] = {};
}
// group all decoration options by render options. Each group is identified by a subType id. The subtype id is empty if
// there are no custom render options.
let decorationOptionsBySubTypes :{[key:string]: editorCommon.IDecorationOptions[]} = {};
for (let decorationOption of decorationOptions) {
let subKey = decorationOption.renderOptions ? hash(decorationOption.renderOptions).toString(16) : '';
let subTypeDecorationOptions = decorationOptionsBySubTypes[subKey];
if (!subTypeDecorationOptions) {
decorationOptionsBySubTypes[subKey] = subTypeDecorationOptions = [];
}
return {
range: r.range,
options: decOpts
};
}));
subTypeDecorationOptions.push(decorationOption);
}
// for each sub type create decorations. For custom render options register a decoration type if necessary
for (let subType in decorationOptionsBySubTypes) {
let decorationOptions = decorationOptionsBySubTypes[subType];
let typeKey = decorationTypeKey;
let oldIds = oldDecorationsIds[subType] || [];
if (subType.length > 0 && oldIds.length === 0) {
// custom render options: decoration type did not exist before, register new one
typeKey = decorationTypeKey + '-' + subType;
this._codeEditorService.registerDecorationSubType(typeKey, decorationTypeKey, decorationOptions[0].renderOptions);
}
let modelDecorations: editorCommon.IModelDeltaDecoration[] = decorationOptions.map(r => {
let opts = this._codeEditorService.resolveDecorationOptions(typeKey, !!r.hoverMessage);
if (r.hoverMessage) {
opts.htmlMessage = r.hoverMessage;
}
return {
range: r.range,
options: opts
};
});
oldDecorationsIds[subType] = this.deltaDecorations(oldIds, modelDecorations);
}
// remove decorations that are no longer used, deregister decoration type if necessary
for (let subType in oldDecorationsIds) {
if (!decorationOptionsBySubTypes.hasOwnProperty(subType)) {
delete oldDecorationsIds[subType];
if (subType.length > 0) { // custom render options: unregister decoration type
this._codeEditorService.removeDecorationType(decorationTypeKey + '-' + subType);
}
}
}
}
public removeDecorations(decorationTypeKey: string): void {
if (this._decorationTypeKeysToIds.hasOwnProperty(decorationTypeKey)) {
this.deltaDecorations(this._decorationTypeKeysToIds[decorationTypeKey], []);
let decorationIdsBySubType = this._decorationTypeKeysToIds[decorationTypeKey];
if (decorationIdsBySubType) {
for (let subType in decorationIdsBySubType) {
this.deltaDecorations(decorationIdsBySubType[subType], []);
}
delete this._decorationTypeKeysToIds[decorationTypeKey];
}
}
......@@ -959,6 +999,20 @@ export abstract class CommonCodeEditor extends EventEmitter implements IActionPr
this.viewModel = null;
}
if (this._decorationTypeKeysToIds) {
for (let decorationType in this._decorationTypeKeysToIds) {
let decorationIdsBySubType = this._decorationTypeKeysToIds[decorationType];
if (decorationIdsBySubType) {
for (let subType in decorationIdsBySubType) {
if (subType.length > 0) {
this._codeEditorService.removeDecorationType(decorationType + '-' + subType);
}
}
}
}
}
var result = this.model;
this.model = null;
......
......@@ -3385,6 +3385,26 @@ export interface IThemeDecorationRenderOptions {
gutterIconPath?: string;
overviewRulerColor?: string;
before?: IContentDecorationRenderOptions;
after?: IContentDecorationRenderOptions;
}
export interface IContentDecorationRenderOptions {
borderStyle?: string;
borderWidth?: string;
borderRadius?: string;
borderSpacing?: string;
borderColor?: string;
content?: string;
textDecoration?: string;
color?: string;
backgroundColor?: string;
margin?: string;
width?: string;
height?: string;
}
/**
......@@ -3398,14 +3418,30 @@ export interface IDecorationRenderOptions extends IThemeDecorationRenderOptions
dark?: IThemeDecorationRenderOptions;
}
export interface IThemeDecorationInstanceRenderOptions {
before?: {
content?: string;
};
after?: {
content?: string;
};
}
export interface IDecorationInstanceRenderOptions extends IThemeDecorationInstanceRenderOptions {
light?: IThemeDecorationInstanceRenderOptions;
dark?: IThemeDecorationInstanceRenderOptions;
}
/**
* @internal
*/
export interface IRangeWithMessage {
export interface IDecorationOptions {
range: IRange;
hoverMessage?: IHTMLContentElement[];
renderOptions? : IDecorationInstanceRenderOptions;
}
export interface ICommonCodeEditor extends IEditor {
onDidChangeModel(listener: (e:IModelChangedEvent)=>void): IDisposable;
......@@ -3536,7 +3572,7 @@ export interface ICommonCodeEditor extends IEditor {
/**
* @internal
*/
setDecorations(decorationTypeKey: string, ranges:IRangeWithMessage[]): void;
setDecorations(decorationTypeKey: string, ranges: IDecorationOptions[]): void;
/**
* @internal
......
......@@ -5,7 +5,7 @@
'use strict';
import Event, {Emitter} from 'vs/base/common/event';
import {ICommonCodeEditor, IDecorationRenderOptions, IModelDecorationOptions} from 'vs/editor/common/editorCommon';
import {ICommonCodeEditor, IDecorationRenderOptions, IModelDecorationOptions, IDecorationInstanceRenderOptions} from 'vs/editor/common/editorCommon';
import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService';
export abstract class AbstractCodeEditorService implements ICodeEditorService {
......@@ -70,6 +70,7 @@ export abstract class AbstractCodeEditorService implements ICodeEditorService {
}
public abstract registerDecorationType(key:string, options: IDecorationRenderOptions): void;
public abstract registerDecorationSubType(key:string, parentKey:string, options: IDecorationInstanceRenderOptions): void;
public abstract removeDecorationType(key:string): void;
public abstract resolveDecorationType(key:string): IModelDecorationOptions;
public abstract resolveDecorationOptions(decorationTypeKey:string, writable: boolean): IModelDecorationOptions;
}
......@@ -6,7 +6,7 @@
import Event from 'vs/base/common/event';
import {ServiceIdentifier, createDecorator} from 'vs/platform/instantiation/common/instantiation';
import {ICommonCodeEditor, IDecorationRenderOptions, IModelDecorationOptions} from 'vs/editor/common/editorCommon';
import {ICommonCodeEditor, IDecorationRenderOptions, IModelDecorationOptions, IDecorationInstanceRenderOptions} from 'vs/editor/common/editorCommon';
export var ID_CODE_EDITOR_SERVICE = 'codeEditorService';
export var ICodeEditorService = createDecorator<ICodeEditorService>(ID_CODE_EDITOR_SERVICE);
......@@ -30,6 +30,7 @@ export interface ICodeEditorService {
getFocusedCodeEditor(): ICommonCodeEditor;
registerDecorationType(key:string, options: IDecorationRenderOptions): void;
registerDecorationSubType(key:string, parentTypeKey: string, options: IDecorationInstanceRenderOptions): void;
removeDecorationType(key:string): void;
resolveDecorationType(key:string): IModelDecorationOptions;
resolveDecorationOptions(typeKey:string, writable: boolean): IModelDecorationOptions;
}
......@@ -4,11 +4,12 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import {IDecorationRenderOptions, IModelDecorationOptions} from 'vs/editor/common/editorCommon';
import {IDecorationRenderOptions, IModelDecorationOptions, IDecorationInstanceRenderOptions} from 'vs/editor/common/editorCommon';
import {AbstractCodeEditorService} from 'vs/editor/common/services/abstractCodeEditorService';
export class MockCodeEditorService extends AbstractCodeEditorService {
public registerDecorationType(key:string, options: IDecorationRenderOptions): void { }
public registerDecorationSubType(key:string, parentKey:string, options: IDecorationInstanceRenderOptions): void {}
public removeDecorationType(key:string): void { }
public resolveDecorationType(key:string): IModelDecorationOptions { return null; }
public resolveDecorationOptions(decorationTypeKey:string, writable: boolean): IModelDecorationOptions { return null; }
}
......@@ -697,6 +697,31 @@ declare namespace vscode {
* The color of the decoration in the overview ruler. Use rgba() and define transparent colors to play well with other decorations.
*/
overviewRulerColor?: string;
/**
* Defines the rendering options of the attachment that is inserted before the decorated text
*/
before?: ThemableDecorationAttachmentRenderOptions;
/**
* Defines the rendering options of the attachment that is inserted after the decorated text
*/
after?: ThemableDecorationAttachmentRenderOptions;
}
export interface ThemableDecorationAttachmentRenderOptions {
borderStyle?: string;
borderWidth?: string;
borderRadius?: string;
borderSpacing?: string;
borderColor?: string;
content?: string;
textDecoration?: string;
color?: string;
backgroundColor?: string;
margin?: string;
width?: string;
height?: string;
}
/**
......@@ -739,6 +764,42 @@ declare namespace vscode {
* A message that should be rendered when hovering over the decoration.
*/
hoverMessage: MarkedString | MarkedString[];
/**
* Render options applied to the current decoration. For performance reasons, keep the
* number of decoration specific options small, and use decoration types whereever possible.
*/
renderOptions?: DecorationInstanceRenderOptions;
}
export interface ThemableDecorationInstanceAttachmentRenderOptions {
content?: string;
color?: string;
backgroundColor?: string;
}
export interface ThemableDecorationInstanceRenderOptions {
/**
* Defines the rendering options of the attachment that is inserted before the decorated text
*/
before?: ThemableDecorationInstanceAttachmentRenderOptions;
/**
* Defines the rendering options of the attachment that is inserted after the decorated text
*/
after?: ThemableDecorationInstanceAttachmentRenderOptions;
}
export interface DecorationInstanceRenderOptions extends ThemableDecorationInstanceRenderOptions {
/**
* Overwrite options for light themes.
*/
light?: ThemableDecorationInstanceRenderOptions;
/**
* Overwrite options for dark themes.
*/
dark?: ThemableDecorationInstanceRenderOptions
}
/**
......
......@@ -13,7 +13,7 @@ import {TPromise} from 'vs/base/common/winjs.base';
import {Remotable, IThreadService} from 'vs/platform/thread/common/thread';
import {ExtHostModelService, ExtHostDocumentData} from 'vs/workbench/api/node/extHostDocuments';
import {Selection, Range, Position, EditorOptions, EndOfLine} from './extHostTypes';
import {ISingleEditOperation, ISelection, IRange, IEditor, EditorType, ICommonCodeEditor, ICommonDiffEditor, IDecorationRenderOptions, IRangeWithMessage} from 'vs/editor/common/editorCommon';
import {ISingleEditOperation, ISelection, IRange, IEditor, EditorType, ICommonCodeEditor, ICommonDiffEditor, IDecorationRenderOptions, IDecorationOptions} from 'vs/editor/common/editorCommon';
import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService';
import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService';
import {Position as EditorPosition} from 'vs/platform/editor/common/editor';
......@@ -689,7 +689,7 @@ export class MainThreadEditors {
return TPromise.as(null);
}
_trySetDecorations(id: string, key: string, ranges: IRangeWithMessage[]): TPromise<any> {
_trySetDecorations(id: string, key: string, ranges: IDecorationOptions[]): TPromise<any> {
if (!this._textEditorsMap[id]) {
return TPromise.wrapError('TextEditor disposed');
}
......
......@@ -11,7 +11,7 @@ import {IDisposable} from 'vs/base/common/lifecycle';
import * as modes from 'vs/editor/common/modes';
import * as types from './extHostTypes';
import {Position as EditorPosition} from 'vs/platform/editor/common/editor';
import {IPosition, ISelection, IRange, IRangeWithMessage, ISingleEditOperation} from 'vs/editor/common/editorCommon';
import {IPosition, ISelection, IRange, IDecorationOptions, ISingleEditOperation} from 'vs/editor/common/editorCommon';
import {IHTMLContentElement} from 'vs/base/common/htmlContent';
import {ITypeBearing} from 'vs/workbench/parts/search/common/search';
import * as vscode from 'vscode';
......@@ -155,27 +155,28 @@ function fromMarkedStringOrMarkedStringArr(something: vscode.MarkedString | vsco
}
}
function isRangeWithMessage(something: any): something is vscode.DecorationOptions {
return (typeof something.range !== 'undefined');
function isDecorationOptions(something: any): something is vscode.DecorationOptions {
return (typeof something.range !== 'undefined') || (typeof something.after !== 'undefined') || (typeof something.before !== 'undefined');
}
function isRangeWithMessageArr(something: vscode.Range[]|vscode.DecorationOptions[]): something is vscode.DecorationOptions[] {
function isDecorationOptionsArr(something: vscode.Range[]|vscode.DecorationOptions[]): something is vscode.DecorationOptions[] {
if (something.length === 0) {
return true;
}
return isRangeWithMessage(something[0]) ? true : false;
return isDecorationOptions(something[0]) ? true : false;
}
export function fromRangeOrRangeWithMessage(ranges:vscode.Range[]|vscode.DecorationOptions[]): IRangeWithMessage[] {
if (isRangeWithMessageArr(ranges)) {
return ranges.map((r): IRangeWithMessage => {
export function fromRangeOrRangeWithMessage(ranges:vscode.Range[]|vscode.DecorationOptions[]): IDecorationOptions[] {
if (isDecorationOptionsArr(ranges)) {
return ranges.map((r): IDecorationOptions => {
return {
range: fromRange(r.range),
hoverMessage: fromMarkedStringOrMarkedStringArr(r.hoverMessage)
hoverMessage: fromMarkedStringOrMarkedStringArr(r.hoverMessage),
renderOptions: r.renderOptions
};
});
} else {
return ranges.map((r): IRangeWithMessage => {
return ranges.map((r): IDecorationOptions => {
return {
range: fromRange(r)
};
......
......@@ -241,7 +241,7 @@ export class MainThreadTextEditor {
}
}
public setDecorations(key: string, ranges:EditorCommon.IRangeWithMessage[]): void {
public setDecorations(key: string, ranges:EditorCommon.IDecorationOptions[]): void {
if (!this._codeEditor) {
console.warn('setDecorations on invisible editor');
return;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册