diff --git a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler2.ts b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler2.ts index 3586b00f799..98c23749c30 100644 --- a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler2.ts +++ b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler2.ts @@ -14,9 +14,8 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { editorOverviewRulerBorder, editorCursorForeground } from 'vs/editor/common/view/editorColorRegistry'; import { Color } from 'vs/base/common/color'; -import { ITheme, ThemeColor } from 'vs/platform/theme/common/themeService'; +import { ITheme } from 'vs/platform/theme/common/themeService'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; -import { ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModelWithDecorations'; class Settings { @@ -275,12 +274,7 @@ export class DecorationsOverviewRuler2 extends ViewPart { } public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { // invalidate color cache - const decorations = this._context.model.getAllOverviewRulerDecorations(); - for (let i = 0, len = decorations.length; i < len; i++) { - const decoration = decorations[i]; - const opts = decoration.source.options.overviewRuler; - opts._resolvedColor = null; - } + this._context.model.invalidateOverviewRulerColorCache(); return this._updateSettings(false); } @@ -303,10 +297,9 @@ export class DecorationsOverviewRuler2 extends ViewPart { const canvasHeight = this._settings.canvasHeight; const lineHeight = this._settings.lineHeight; const viewLayout = this._context.viewLayout; - const theme = this._context.theme; const outerHeight = this._context.viewLayout.getScrollHeight(); const heightRatio = canvasHeight / outerHeight; - const decorations = this._context.model.getAllOverviewRulerDecorations(); + const decorations = this._context.model.getAllOverviewRulerDecorations(this._context.theme); const minDecorationHeight = (Constants.MIN_DECORATION_HEIGHT * this._settings.pixelRatio) | 0; const halfMinDecorationHeight = (minDecorationHeight / 2) | 0; @@ -322,17 +315,12 @@ export class DecorationsOverviewRuler2 extends ViewPart { const paintQueue = new PaintQueue(canvasCtx, this._settings.x, this._settings.w); for (let i = 0, len = decorations.length; i < len; i++) { const decoration = decorations[i]; - const opts = decoration.source.options.overviewRuler; - const lane = opts.position; - if (lane === 0) { + if (decoration.lane === 0) { continue; } - const startLineNumber = decoration.range.startLineNumber; - const endLineNumber = decoration.range.endLineNumber; - const color = resolveColor(opts, theme); - let y1 = (viewLayout.getVerticalOffsetForLineNumber(startLineNumber) * heightRatio) | 0; - let y2 = ((viewLayout.getVerticalOffsetForLineNumber(endLineNumber) + lineHeight) * heightRatio) | 0; + let y1 = (viewLayout.getVerticalOffsetForLineNumber(decoration.startLineNumber) * heightRatio) | 0; + let y2 = ((viewLayout.getVerticalOffsetForLineNumber(decoration.endLineNumber) + lineHeight) * heightRatio) | 0; let height = y2 - y1; if (height < minDecorationHeight) { let yCenter = ((y1 + y2) / 2) | 0; @@ -345,7 +333,7 @@ export class DecorationsOverviewRuler2 extends ViewPart { y2 = yCenter + halfMinDecorationHeight; } - paintQueueAccept(paintQueue, color, y1, y2, lane); + paintQueueAccept(paintQueue, decoration.color, y1, y2, decoration.lane); } for (let i = 0, len = paintQueue.req.length; i < len; i++) { paintQueueFlush(paintQueue, paintQueue.req[i]); @@ -448,7 +436,9 @@ function paintQueueAccept(Q: PaintQueue, color: string, y1: number, y2: number, if (result !== null) { // there is already an ongoing request for this color & lane // => simply merge into it - result.y2 = y2; + if (y2 > result.y2) { + result.y2 = y2; + } return; } @@ -466,23 +456,3 @@ function paintQueueFlush(Q: PaintQueue, req: PaintRequest): void { Q.ctx.fillStyle = req.color; Q.ctx.fillRect(Q.x[req.lane], req.y1, Q.w[req.lane], req.y2 - req.y1); } - -function resolveColor(opts: ModelDecorationOverviewRulerOptions, theme: ITheme): string { - if (!opts._resolvedColor) { - const themeType = theme.type; - const color = (themeType === 'dark' ? opts.darkColor : themeType === 'light' ? opts.color : opts.hcColor); - opts._resolvedColor = resolveRulerColor(color, theme); - } - return opts._resolvedColor; -} - -function resolveRulerColor(color: string | ThemeColor, theme: ITheme): string { - if (typeof color === 'string') { - return color; - } - let c = color ? theme.getColor(color.id) : null; - if (!c) { - c = Color.transparent; - } - return c.toString(); -} diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 5cf9d2c728a..78bc73c82ee 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -10,10 +10,12 @@ import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { LineTokens } from 'vs/editor/common/core/lineTokens'; import { PrefixSumComputerWithCache } from 'vs/editor/common/viewModel/prefixSumComputer'; -import { ViewLineData, ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel'; +import { ViewLineData, ICoordinatesConverter, OverviewRulerDecoration } from 'vs/editor/common/viewModel/viewModel'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { WrappingIndent } from 'vs/editor/common/config/editorOptions'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { ThemeColor, ITheme } from 'vs/platform/theme/common/themeService'; +import { Color } from 'vs/base/common/color'; export class OutputPosition { _outputPositionBrand: void; @@ -57,6 +59,7 @@ export interface ISplitLine { getModelColumnOfViewPosition(outputLineIndex: number, outputColumn: number): number; getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number): Position; + getViewLineNumberOfModelPosition(deltaLineNumber: number, inputColumn: number): number; } export interface IViewModelLinesCollection { @@ -82,6 +85,8 @@ export interface IViewModelLinesCollection { getViewLineMaxColumn(viewLineNumber: number): number; getViewLineData(viewLineNumber: number): ViewLineData; getViewLinesData(viewStartLineNumber: number, viewEndLineNumber: number, needed: boolean[]): ViewLineData[]; + + getAllOverviewRulerDecorations(ownerId: number, filterOutValidation: boolean, theme: ITheme): OverviewRulerDecoration[]; } export class CoordinatesConverter implements ICoordinatesConverter { @@ -650,6 +655,42 @@ export class SplitLinesCollection implements IViewModelLinesCollection { // console.log('in -> out ' + inputLineNumber + ',' + inputColumn + ' ===> ' + r.lineNumber + ',' + r); return r; } + + private _getViewLineNumberForModelPosition(inputLineNumber: number, inputColumn: number): number { + let lineIndex = inputLineNumber - 1; + if (this.lines[lineIndex].isVisible()) { + // this model line is visible + const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getAccumulatedValue(lineIndex - 1)); + return this.lines[lineIndex].getViewLineNumberOfModelPosition(deltaLineNumber, inputColumn); + } + + // this model line is not visible + while (lineIndex > 0 && !this.lines[lineIndex].isVisible()) { + lineIndex--; + } + if (lineIndex === 0 && !this.lines[lineIndex].isVisible()) { + // Could not reach a real line + return 1; + } + const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getAccumulatedValue(lineIndex - 1)); + return this.lines[lineIndex].getViewLineNumberOfModelPosition(deltaLineNumber, this.model.getLineMaxColumn(lineIndex + 1)); + } + + public getAllOverviewRulerDecorations(ownerId: number, filterOutValidation: boolean, theme: ITheme): OverviewRulerDecoration[] { + const decorations = this.model.getOverviewRulerDecorations(ownerId, filterOutValidation); + const result = new OverviewRulerDecorations(); + for (let i = 0, len = decorations.length; i < len; i++) { + const decoration = decorations[i]; + const opts = decoration.options.overviewRuler; + const lane = opts.position; + const color = resolveColor(opts, theme); + const viewStartLineNumber = this._getViewLineNumberForModelPosition(decoration.range.startLineNumber, decoration.range.startColumn); + const viewEndLineNumber = this._getViewLineNumberForModelPosition(decoration.range.endLineNumber, decoration.range.endColumn); + + result.accept(color, viewStartLineNumber, viewEndLineNumber, lane); + } + return result.result; + } } class VisibleIdentitySplitLine implements ISplitLine { @@ -711,6 +752,10 @@ class VisibleIdentitySplitLine implements ISplitLine { public getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number): Position { return new Position(deltaLineNumber, inputColumn); } + + public getViewLineNumberOfModelPosition(deltaLineNumber: number, inputColumn: number): number { + return deltaLineNumber; + } } class InvisibleIdentitySplitLine implements ISplitLine { @@ -761,6 +806,10 @@ class InvisibleIdentitySplitLine implements ISplitLine { public getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number): Position { throw new Error('Not supported'); } + + public getViewLineNumberOfModelPosition(deltaLineNumber: number, inputColumn: number): number { + throw new Error('Not supported'); + } } export class SplitLine implements ISplitLine { @@ -914,6 +963,14 @@ export class SplitLine implements ISplitLine { // console.log('in -> out ' + deltaLineNumber + ',' + inputColumn + ' ===> ' + (deltaLineNumber+outputLineIndex) + ',' + outputColumn); return new Position(deltaLineNumber + outputLineIndex, outputColumn); } + + public getViewLineNumberOfModelPosition(deltaLineNumber: number, inputColumn: number): number { + if (!this._isVisible) { + throw new Error('Not supported'); + } + const r = this.positionMapper.getOutputPositionOfInputOffset(inputColumn - 1); + return (deltaLineNumber + r.outputLineIndex); + } } function createSplitLine(linePositionMapperFactory: ILineMapperFactory, text: string, tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent, isVisible: boolean): ISplitLine { @@ -1093,4 +1150,99 @@ export class IdentityLinesCollection implements IViewModelLinesCollection { return result; } + + public getAllOverviewRulerDecorations(ownerId: number, filterOutValidation: boolean, theme: ITheme): OverviewRulerDecoration[] { + const decorations = this.model.getOverviewRulerDecorations(ownerId, filterOutValidation); + const result = new OverviewRulerDecorations(); + for (let i = 0, len = decorations.length; i < len; i++) { + const decoration = decorations[i]; + const opts = decoration.options.overviewRuler; + const lane = opts.position; + const color = resolveColor(opts, theme); + const viewStartLineNumber = decoration.range.startLineNumber; + const viewEndLineNumber = decoration.range.endLineNumber; + + result.accept(color, viewStartLineNumber, viewEndLineNumber, lane); + } + result.flushAll(); + return result.result; + } +} + +class OverviewRulerDecorations { + + readonly req: OverviewRulerDecoration[] = []; + readonly result: OverviewRulerDecoration[] = []; + + constructor() { + } + + public accept(color: string, startLineNumber: number, endLineNumber: number, lane: number): void { + let result: OverviewRulerDecoration = null; + for (let i = 0, len = this.req.length; i < len; i++) { + const req = this.req[i]; + if (req.endLineNumber < startLineNumber) { + this._flush(req); + + if (i + 1 === len) { + // last element + this.req.pop(); + break; + } else { + this.req[i] = this.req.pop(); + len--; + i--; + continue; + } + } + + if (req.lane === lane && req.color === color) { + result = req; + break; + } + } + + if (result !== null) { + // there is already an ongoing request for this color & lane + // => simply merge into it + if (endLineNumber > result.endLineNumber) { + result.endLineNumber = endLineNumber; + } + return; + } + + result = new OverviewRulerDecoration(startLineNumber, endLineNumber, lane, color); + this.req.push(result); + } + + public flushAll(): void { + for (let i = 0, len = this.req.length; i < len; i++) { + this._flush(this.req[i]); + } + } + + private _flush(element: OverviewRulerDecoration): void { + this.result.push(element); + } +} + + +function resolveColor(opts: ModelDecorationOverviewRulerOptions, theme: ITheme): string { + if (!opts._resolvedColor) { + const themeType = theme.type; + const color = (themeType === 'dark' ? opts.darkColor : themeType === 'light' ? opts.color : opts.hcColor); + opts._resolvedColor = resolveRulerColor(color, theme); + } + return opts._resolvedColor; +} + +function resolveRulerColor(color: string | ThemeColor, theme: ITheme): string { + if (typeof color === 'string') { + return color; + } + let c = color ? theme.getColor(color.id) : null; + if (!c) { + c = Color.transparent; + } + return c.toString(); } diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index 2890bf9314f..ac489285266 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { INewScrollPosition, IModelDecoration, EndOfLinePreference, IViewState } from 'vs/editor/common/editorCommon'; +import { INewScrollPosition, IModelDecoration, EndOfLinePreference, IViewState, OverviewRulerLane } from 'vs/editor/common/editorCommon'; import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; import { Position, IPosition } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -14,6 +14,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { Scrollable, IScrollPosition } from 'vs/base/common/scrollable'; import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer'; +import { ITheme } from 'vs/platform/theme/common/themeService'; export interface IViewWhitespaceViewportData { readonly id: number; @@ -138,7 +139,8 @@ export interface IViewModel { getLineMaxColumn(lineNumber: number): number; getLineFirstNonWhitespaceColumn(lineNumber: number): number; getLineLastNonWhitespaceColumn(lineNumber: number): number; - getAllOverviewRulerDecorations(): ViewModelDecoration[]; + getAllOverviewRulerDecorations(theme: ITheme): OverviewRulerDecoration[]; + invalidateOverviewRulerColorCache(): void; getValueInRange(range: Range, eol: EndOfLinePreference): string; getModelLineMaxColumn(modelLineNumber: number): number; @@ -276,6 +278,17 @@ export class ViewModelDecoration { } } +export class OverviewRulerDecoration { + _overviewRulerDecorationBrand: void; + + constructor( + public readonly startLineNumber: number, + public endLineNumber: number, + public readonly lane: OverviewRulerLane, + public readonly color: string + ) { } +} + export class ViewEventsCollector { private _events: ViewEvent[]; diff --git a/src/vs/editor/common/viewModel/viewModelDecorations.ts b/src/vs/editor/common/viewModel/viewModelDecorations.ts index be7a324004a..e1bcdeda1a3 100644 --- a/src/vs/editor/common/viewModel/viewModelDecorations.ts +++ b/src/vs/editor/common/viewModel/viewModelDecorations.ts @@ -88,17 +88,6 @@ export class ViewModelDecorations implements IDisposable { return r; } - public getAllOverviewRulerDecorations(): ViewModelDecoration[] { - let modelDecorations = this.model.getOverviewRulerDecorations(this.editorId, this.configuration.editor.readOnly); - let result: ViewModelDecoration[] = [], resultLen = 0; - for (let i = 0, len = modelDecorations.length; i < len; i++) { - let modelDecoration = modelDecorations[i]; - let viewModelDecoration = this._getOrCreateViewModelDecoration(modelDecoration); - result[resultLen++] = viewModelDecoration; - } - return result; - } - public getDecorationsViewportData(viewRange: Range): IDecorationsViewportData { var cacheIsValid = true; cacheIsValid = cacheIsValid && (this._cachedModelDecorationsResolver !== null); diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 68cdaf1ccbc..8eb47ab129f 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -12,7 +12,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import { TokenizationRegistry, ColorId, LanguageId } from 'vs/editor/common/modes'; import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer'; import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations'; -import { MinimapLinesRenderingData, ViewLineRenderingData, ViewModelDecoration, IViewModel, ICoordinatesConverter, ViewEventsCollector } from 'vs/editor/common/viewModel/viewModel'; +import { MinimapLinesRenderingData, ViewLineRenderingData, ViewModelDecoration, IViewModel, ICoordinatesConverter, ViewEventsCollector, OverviewRulerDecoration } from 'vs/editor/common/viewModel/viewModel'; import { SplitLinesCollection, IViewModelLinesCollection, IdentityLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { MinimapTokensColorTracker } from 'vs/editor/common/view/minimapCharRenderer'; @@ -22,6 +22,8 @@ import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewMod import { ViewLayout } from 'vs/editor/common/viewLayout/viewLayout'; import { Color } from 'vs/base/common/color'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { ITheme } from 'vs/platform/theme/common/themeService'; +import { ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModelWithDecorations'; const USE_IDENTITY_LINES_COLLECTION = true; @@ -428,8 +430,17 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel ); } - public getAllOverviewRulerDecorations(): ViewModelDecoration[] { - return this.decorations.getAllOverviewRulerDecorations(); + public getAllOverviewRulerDecorations(theme: ITheme): OverviewRulerDecoration[] { + return this.lines.getAllOverviewRulerDecorations(this.editorId, this.configuration.editor.readOnly, theme); + } + + public invalidateOverviewRulerColorCache(): void { + const decorations = this.model.getOverviewRulerDecorations(); + for (let i = 0, len = decorations.length; i < len; i++) { + const decoration = decorations[i]; + const opts = decoration.options.overviewRuler; + opts._resolvedColor = null; + } } public getValueInRange(range: Range, eol: editorCommon.EndOfLinePreference): string {