Skip to content

Commit

Permalink
Minimap: Render find match decorations, fixes #75216
Browse files Browse the repository at this point in the history
  • Loading branch information
Rachel Macfarlane committed Jun 21, 2019
1 parent 6c22b0b commit f7930a5
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 18 deletions.
105 changes: 100 additions & 5 deletions src/vs/editor/browser/viewParts/minimap/minimap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/v
import { getOrCreateMinimapCharRenderer } from 'vs/editor/common/view/runtimeMinimapCharRenderer';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { ViewLineData } from 'vs/editor/common/viewModel/viewModel';
import { ViewLineData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel';
import { scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { ModelDecorationMinimapOptions } from 'vs/editor/common/model/textModel';

function getMinimapLineHeight(renderMinimap: RenderMinimap): number {
if (renderMinimap === RenderMinimap.Large) {
Expand Down Expand Up @@ -335,10 +336,7 @@ class RenderData {
* Check if the current RenderData matches accurately the new desired layout and no painting is needed.
*/
public linesEquals(layout: MinimapLayout): boolean {
if (this.renderedLayout.startLineNumber !== layout.startLineNumber) {
return false;
}
if (this.renderedLayout.endLineNumber !== layout.endLineNumber) {
if (!this.scrollEquals(layout)) {
return false;
}

Expand All @@ -354,6 +352,14 @@ class RenderData {
return true;
}

/**
* Check if the current RenderData matches the new layout's scroll position
*/
public scrollEquals(layout: MinimapLayout): boolean {
return this.renderedLayout.startLineNumber === layout.startLineNumber
&& this.renderedLayout.endLineNumber === layout.endLineNumber;
}

_get(): { imageData: ImageData; rendLineNumberStart: number; lines: MinimapLine[]; } {
const tmp = this._renderedLines._get();
return {
Expand Down Expand Up @@ -435,6 +441,7 @@ export class Minimap extends ViewPart {
private readonly _domNode: FastDomNode<HTMLElement>;
private readonly _shadow: FastDomNode<HTMLElement>;
private readonly _canvas: FastDomNode<HTMLCanvasElement>;
private readonly _decorationsCanvas: FastDomNode<HTMLCanvasElement>;
private readonly _slider: FastDomNode<HTMLElement>;
private readonly _sliderHorizontal: FastDomNode<HTMLElement>;
private readonly _tokensColorTracker: MinimapTokensColorTracker;
Expand All @@ -444,6 +451,7 @@ export class Minimap extends ViewPart {

private _options: MinimapOptions;
private _lastRenderData: RenderData | null;
private _decorationsChanged: boolean = false;
private _buffers: MinimapBuffers | null;

constructor(context: ViewContext) {
Expand All @@ -469,6 +477,12 @@ export class Minimap extends ViewPart {
this._canvas.setLeft(0);
this._domNode.appendChild(this._canvas);

this._decorationsCanvas = createFastDomNode(document.createElement('canvas'));
this._decorationsCanvas.setPosition('absolute');
this._decorationsCanvas.setClassName('minimap-decorations-layer');
this._decorationsCanvas.setLeft(0);
this._domNode.appendChild(this._decorationsCanvas);

this._slider = createFastDomNode(document.createElement('div'));
this._slider.setPosition('absolute');
this._slider.setClassName('minimap-slider');
Expand Down Expand Up @@ -569,10 +583,17 @@ export class Minimap extends ViewPart {
this._domNode.setWidth(this._options.minimapWidth);
this._domNode.setHeight(this._options.minimapHeight);
this._shadow.setHeight(this._options.minimapHeight);

this._canvas.setWidth(this._options.canvasOuterWidth);
this._canvas.setHeight(this._options.canvasOuterHeight);
this._canvas.domNode.width = this._options.canvasInnerWidth;
this._canvas.domNode.height = this._options.canvasInnerHeight;

this._decorationsCanvas.setWidth(this._options.canvasOuterWidth);
this._decorationsCanvas.setHeight(this._options.canvasOuterHeight);
this._decorationsCanvas.domNode.width = this._options.canvasInnerWidth;
this._decorationsCanvas.domNode.height = this._options.canvasInnerHeight;

this._slider.setWidth(this._options.minimapWidth);
}

Expand Down Expand Up @@ -647,6 +668,11 @@ export class Minimap extends ViewPart {
return true;
}

public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
this._decorationsChanged = true;
return true;
}

// --- end event handlers

public prepareRender(ctx: RenderingContext): void {
Expand Down Expand Up @@ -689,9 +715,78 @@ export class Minimap extends ViewPart {
this._sliderHorizontal.setTop(0);
this._sliderHorizontal.setHeight(layout.sliderHeight);

this.renderDecorations(layout);
this._lastRenderData = this.renderLines(layout);
}

private renderDecorations(layout: MinimapLayout) {
const scrollHasChanged = this._lastRenderData && !this._lastRenderData.scrollEquals(layout);
if (scrollHasChanged || this._decorationsChanged) {
this._decorationsChanged = false;
const decorations = this._context.model.getDecorationsInViewport(new Range(layout.startLineNumber, 1, layout.endLineNumber, this._context.model.getLineMaxColumn(layout.endLineNumber)));

const { renderMinimap, canvasInnerWidth, canvasInnerHeight } = this._options;
const lineHeight = getMinimapLineHeight(renderMinimap);
const characterWidth = getMinimapCharWidth(renderMinimap);
const tabSize = this._context.model.getOptions().tabSize;
const canvasContext = this._decorationsCanvas.domNode.getContext('2d')!;

canvasContext.clearRect(0, 0, canvasInnerWidth, canvasInnerHeight);

// If the minimap is rendered using blocks, text takes up half the line height
const lineHeightRatio = renderMinimap === RenderMinimap.LargeBlocks || renderMinimap === RenderMinimap.SmallBlocks ? 0.5 : 1;
const height = lineHeight * lineHeightRatio;

for (let i = 0; i < decorations.length; i++) {

This comment has been minimized.

Copy link
@rebornix

rebornix Jun 21, 2019

Member

I'm wondering if we can group decorations by startLineNumber first and when we run renderDecoration, cache the { charIndex: leftWidth }. Otherwise if we have more than one decoration within one line, we are actually checking char codes multiple times.

if (decorations[i].options.minimap) {
this.renderDecoration(canvasContext, decorations[i], layout, height, lineHeight, tabSize, characterWidth);
}
}
}
}

private renderDecoration(canvasContext: CanvasRenderingContext2D,
decoration: ViewModelDecoration,
layout: MinimapLayout,
height: number,
lineHeight: number,
tabSize: number,
charWidth: number) {
const { startLineNumber, startColumn, endColumn } = decoration.range;

const startIndex = startColumn - 1;
const endIndex = endColumn - 1;

const y = (startLineNumber - layout.startLineNumber) * lineHeight;

// Get the offset of the decoration in the line. Have to read line data to see how much space each character takes.
let x = 0;
let endPosition = 0;
const lineData = this._context.model.getLineContent(startLineNumber);

This comment has been minimized.

Copy link
@rebornix

rebornix Jun 21, 2019

Member

should we get content from the range instead of the whole line?

for (let i = 0; i < endIndex; i++) {
const charCode = lineData.charCodeAt(i);
const dx = charCode === CharCode.Tab
? tabSize * charWidth
: strings.isFullWidthCharacter(charCode)
? 2 * charWidth
: charWidth;

if (i < startIndex) {
x += dx;
}

endPosition += dx;
}

const width = endPosition - x;

const minimapOptions = <ModelDecorationMinimapOptions>decoration.options.minimap;
const decorationColor = minimapOptions.getColor(this._context.theme);

canvasContext.fillStyle = decorationColor;
canvasContext.fillRect(x, y, width, height);
}

private renderLines(layout: MinimapLayout): RenderData {
const renderMinimap = this._options.renderMinimap;
const startLineNumber = layout.startLineNumber;
Expand Down
32 changes: 28 additions & 4 deletions src/vs/editor/common/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,45 @@ export enum OverviewRulerLane {
}

/**
* Options for rendering a model decoration in the overview ruler.
* Position in the minimap to render the decoration.
*/
export interface IModelDecorationOverviewRulerOptions {
export enum MinimapPosition {
Inline = 1
}

export interface IDecorationOptions {
/**
* CSS color to render in the overview ruler.
* CSS color to render.
* e.g.: rgba(100, 100, 100, 0.5) or a color from the color registry
*/
color: string | ThemeColor | undefined;
/**
* CSS color to render in the overview ruler.
* CSS color to render.
* e.g.: rgba(100, 100, 100, 0.5) or a color from the color registry
*/
darkColor?: string | ThemeColor;
}

/**
* Options for rendering a model decoration in the overview ruler.
*/
export interface IModelDecorationOverviewRulerOptions extends IDecorationOptions {
/**
* The position in the overview ruler.
*/
position: OverviewRulerLane;
}

/**
* Options for rendering a model decoration in the overview ruler.
*/
export interface IModelDecorationMinimapOptions extends IDecorationOptions {
/**
* The position in the overview ruler.
*/
position: MinimapPosition;
}

/**
* Options for a model decoration.
*/
Expand Down Expand Up @@ -89,6 +109,10 @@ export interface IModelDecorationOptions {
* If set, render this decoration in the overview ruler.
*/
overviewRuler?: IModelDecorationOverviewRulerOptions | null;
/**
* If set, render this decoration in the minimap.
*/
minimap?: IModelDecorationMinimapOptions | null;
/**
* If set, the decoration will be rendered in the glyph margin with this CSS class name.
*/
Expand Down
26 changes: 22 additions & 4 deletions src/vs/editor/common/model/textModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2823,16 +2823,14 @@ function cleanClassName(className: string): string {
return className.replace(/[^a-z0-9\-_]/gi, ' ');
}

export class ModelDecorationOverviewRulerOptions implements model.IModelDecorationOverviewRulerOptions {
class DecorationOptions implements model.IDecorationOptions {
readonly color: string | ThemeColor;
readonly darkColor: string | ThemeColor;
readonly position: model.OverviewRulerLane;
private _resolvedColor: string | null;

constructor(options: model.IModelDecorationOverviewRulerOptions) {
constructor(options: model.IDecorationOptions) {
this.color = options.color || strings.empty;
this.darkColor = options.darkColor || strings.empty;
this.position = (typeof options.position === 'number' ? options.position : model.OverviewRulerLane.Center);
this._resolvedColor = null;
}

Expand Down Expand Up @@ -2863,6 +2861,24 @@ export class ModelDecorationOverviewRulerOptions implements model.IModelDecorati
}
}

export class ModelDecorationOverviewRulerOptions extends DecorationOptions {
readonly position: model.OverviewRulerLane;

constructor(options: model.IModelDecorationOverviewRulerOptions) {
super(options);
this.position = (typeof options.position === 'number' ? options.position : model.OverviewRulerLane.Center);
}
}

export class ModelDecorationMinimapOptions extends DecorationOptions {
readonly position: model.MinimapPosition;

constructor(options: model.IModelDecorationMinimapOptions) {
super(options);
this.position = options.position;
}
}

export class ModelDecorationOptions implements model.IModelDecorationOptions {

public static EMPTY: ModelDecorationOptions;
Expand All @@ -2884,6 +2900,7 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions {
readonly showIfCollapsed: boolean;
readonly collapseOnReplaceEdit: boolean;
readonly overviewRuler: ModelDecorationOverviewRulerOptions | null;
readonly minimap: ModelDecorationMinimapOptions | null;
readonly glyphMarginClassName: string | null;
readonly linesDecorationsClassName: string | null;
readonly marginClassName: string | null;
Expand All @@ -2902,6 +2919,7 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions {
this.showIfCollapsed = options.showIfCollapsed || false;
this.collapseOnReplaceEdit = options.collapseOnReplaceEdit || false;
this.overviewRuler = options.overviewRuler ? new ModelDecorationOverviewRulerOptions(options.overviewRuler) : null;
this.minimap = options.minimap ? new ModelDecorationMinimapOptions(options.minimap) : null;
this.glyphMarginClassName = options.glyphMarginClassName ? cleanClassName(options.glyphMarginClassName) : null;
this.linesDecorationsClassName = options.linesDecorationsClassName ? cleanClassName(options.linesDecorationsClassName) : null;
this.marginClassName = options.marginClassName ? cleanClassName(options.marginClassName) : null;
Expand Down
7 changes: 7 additions & 0 deletions src/vs/editor/common/standalone/standaloneEnums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,13 @@ export enum OverviewRulerLane {
Full = 7
}

/**
* Position in the minimap to render the decoration.
*/
export enum MinimapPosition {
Inline = 1
}

/**
* End of line character preference.
*/
Expand Down
10 changes: 9 additions & 1 deletion src/vs/editor/contrib/find/findDecorations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { FindMatch, IModelDecorationsChangeAccessor, IModelDeltaDecoration, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model';
import { FindMatch, IModelDecorationsChangeAccessor, IModelDeltaDecoration, OverviewRulerLane, TrackedRangeStickiness, MinimapPosition } from 'vs/editor/common/model';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { overviewRulerFindMatchForeground } from 'vs/platform/theme/common/colorRegistry';
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
Expand Down Expand Up @@ -269,6 +269,10 @@ export class FindDecorations implements IDisposable {
overviewRuler: {
color: themeColorFromId(overviewRulerFindMatchForeground),
position: OverviewRulerLane.Center
},
minimap: {
color: themeColorFromId(overviewRulerFindMatchForeground),
position: MinimapPosition.Inline
}
});

Expand All @@ -279,6 +283,10 @@ export class FindDecorations implements IDisposable {
overviewRuler: {
color: themeColorFromId(overviewRulerFindMatchForeground),
position: OverviewRulerLane.Center
},
minimap: {
color: themeColorFromId(overviewRulerFindMatchForeground),
position: MinimapPosition.Inline
}
});

Expand Down
1 change: 1 addition & 0 deletions src/vs/editor/standalone/browser/standaloneEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ export function createMonacoEditorAPI(): typeof monaco.editor {
ScrollbarVisibility: standaloneEnums.ScrollbarVisibility,
WrappingIndent: standaloneEnums.WrappingIndent,
OverviewRulerLane: standaloneEnums.OverviewRulerLane,
MinimapPosition: standaloneEnums.MinimapPosition,
EndOfLinePreference: standaloneEnums.EndOfLinePreference,
DefaultEndOfLine: standaloneEnums.DefaultEndOfLine,
EndOfLineSequence: standaloneEnums.EndOfLineSequence,
Expand Down
Loading

0 comments on commit f7930a5

Please sign in to comment.