Skip to content

Commit

Permalink
Render diff decorations in the minimap, closes #20934
Browse files Browse the repository at this point in the history
  • Loading branch information
Rachel Macfarlane committed Nov 5, 2019
1 parent 259dd1b commit c8199b5
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 20 deletions.
28 changes: 21 additions & 7 deletions src/vs/editor/browser/viewParts/minimap/minimap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import * as platform from 'vs/base/common/platform';
import * as strings from 'vs/base/common/strings';
import { ILine, RenderedLinesCollection } from 'vs/editor/browser/view/viewLayer';
import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart';
import { RenderMinimap, EditorOption } from 'vs/editor/common/config/editorOptions';
import { RenderMinimap, EditorOption, MINIMAP_GUTTER_WIDTH } from 'vs/editor/common/config/editorOptions';
import { Range } from 'vs/editor/common/core/range';
import { RGBA8 } from 'vs/editor/common/core/rgba';
import { IConfiguration, ScrollType } from 'vs/editor/common/editorCommon';
Expand All @@ -32,6 +32,7 @@ import { Selection } from 'vs/editor/common/core/selection';
import { Color } from 'vs/base/common/color';
import { GestureEvent, EventType, Gesture } from 'vs/base/browser/touch';
import { MinimapCharRendererFactory } from 'vs/editor/browser/viewParts/minimap/minimapCharRendererFactory';
import { MinimapPosition } from 'vs/editor/common/model';

function getMinimapLineHeight(renderMinimap: RenderMinimap, scale: number): number {
if (renderMinimap === RenderMinimap.Text) {
Expand All @@ -54,6 +55,8 @@ function getMinimapCharWidth(renderMinimap: RenderMinimap, scale: number): numbe
*/
const MOUSE_DRAG_RESET_DISTANCE = 140;

const GUTTER_DECORATION_WIDTH = 2;

class MinimapOptions {

public readonly renderMinimap: RenderMinimap;
Expand Down Expand Up @@ -815,9 +818,20 @@ export class Minimap extends ViewPart {
continue;
}

const decorationColor = (<ModelDecorationMinimapOptions>decoration.options.minimap).getColor(this._context.theme);
for (let line = decoration.range.startLineNumber; line <= decoration.range.endLineNumber; line++) {
const decorationColor = (<ModelDecorationMinimapOptions>decoration.options.minimap).getColor(this._context.theme);
this.renderDecorationOnLine(canvasContext, lineOffsetMap, decoration.range, decorationColor, layout, line, lineHeight, lineHeight, tabSize, characterWidth);
switch (decoration.options.minimap.position) {

case MinimapPosition.Inline:
this.renderDecorationOnLine(canvasContext, lineOffsetMap, decoration.range, decorationColor, layout, line, lineHeight, lineHeight, tabSize, characterWidth);
continue;

case MinimapPosition.Gutter:
const y = (line - layout.startLineNumber) * lineHeight;
const x = 2;
this.renderDecoration(canvasContext, decorationColor, x, y, GUTTER_DECORATION_WIDTH, lineHeight);
continue;
}
}
}
}
Expand All @@ -840,7 +854,7 @@ export class Minimap extends ViewPart {
const isFirstDecorationForLine = !lineIndexToXOffset;
if (!lineIndexToXOffset) {
const lineData = this._context.model.getLineContent(lineNumber);
lineIndexToXOffset = [0];
lineIndexToXOffset = [MINIMAP_GUTTER_WIDTH];
for (let i = 1; i < lineData.length + 1; i++) {
const charCode = lineData.charCodeAt(i - 1);
const dx = charCode === CharCode.Tab
Expand All @@ -856,7 +870,7 @@ export class Minimap extends ViewPart {
}

const { startColumn, endColumn, startLineNumber, endLineNumber } = decorationRange;
const x = startLineNumber === lineNumber ? lineIndexToXOffset[startColumn - 1] : 0;
const x = startLineNumber === lineNumber ? lineIndexToXOffset[startColumn - 1] : MINIMAP_GUTTER_WIDTH;

const endColumnForLine = endLineNumber > lineNumber ? lineIndexToXOffset.length - 1 : endColumn - 1;

Expand All @@ -875,7 +889,7 @@ export class Minimap extends ViewPart {

private renderLineHighlight(canvasContext: CanvasRenderingContext2D, decorationColor: Color | undefined, y: number, height: number): void {
canvasContext.fillStyle = decorationColor && decorationColor.transparent(0.5).toString() || '';
canvasContext.fillRect(0, y, canvasContext.canvas.width, height);
canvasContext.fillRect(MINIMAP_GUTTER_WIDTH, y, canvasContext.canvas.width, height);
}

private renderDecoration(canvasContext: CanvasRenderingContext2D, decorationColor: Color | undefined, x: number, y: number, width: number, height: number) {
Expand Down Expand Up @@ -1062,7 +1076,7 @@ export class Minimap extends ViewPart {
const charWidth = getMinimapCharWidth(renderMinimap, fontScale);
const maxDx = target.width - charWidth;

let dx = 0;
let dx = MINIMAP_GUTTER_WIDTH;
let charIndex = 0;
let tabsCharDelta = 0;

Expand Down
8 changes: 7 additions & 1 deletion src/vs/editor/common/config/editorOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,12 @@ export interface IEditorConstructionOptions extends IEditorOptions {
dimension?: IDimension;
}

/**
* @internal
* The width of the minimap gutter, in pixels.
*/
export const MINIMAP_GUTTER_WIDTH = 8;

/**
* Configuration options for the diff editor.
*/
Expand Down Expand Up @@ -1697,7 +1703,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
// (typicalHalfwidthCharacterWidth + minimapCharWidth) * minimapWidth = (remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth
// minimapWidth = ((remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth)

minimapWidth = Math.max(0, Math.floor(((remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth)));
minimapWidth = Math.max(0, Math.floor(((remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth))) + MINIMAP_GUTTER_WIDTH;
let minimapColumns = minimapWidth / minimapCharWidth;
if (minimapColumns > minimapMaxColumn) {
minimapWidth = Math.floor(minimapMaxColumn * minimapCharWidth);
Expand Down
3 changes: 2 additions & 1 deletion src/vs/editor/common/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export enum OverviewRulerLane {
* Position in the minimap to render the decoration.
*/
export enum MinimapPosition {
Inline = 1
Inline = 1,
Gutter = 2
}

export interface IDecorationOptions {
Expand Down
3 changes: 2 additions & 1 deletion src/vs/editor/common/standalone/standaloneEnums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,8 @@ export enum OverviewRulerLane {
* Position in the minimap to render the decoration.
*/
export enum MinimapPosition {
Inline = 1
Inline = 1,
Gutter = 2
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/vs/monaco.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1193,7 +1193,8 @@ declare namespace monaco.editor {
* Position in the minimap to render the decoration.
*/
export enum MinimapPosition {
Inline = 1
Inline = 1,
Gutter = 2
}

export interface IDecorationOptions {
Expand Down
56 changes: 48 additions & 8 deletions src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import { basename, isEqualOrParent } from 'vs/base/common/resources';
import { MenuId, IMenuService, IMenu, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions';
import { createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IChange, IEditorModel, ScrollType, IEditorContribution, IDiffEditorModel } from 'vs/editor/common/editorCommon';
import { OverviewRulerLane, ITextModel, IModelDecorationOptions } from 'vs/editor/common/model';
import { OverviewRulerLane, ITextModel, IModelDecorationOptions, MinimapPosition } from 'vs/editor/common/model';
import { sortedDiff, firstIndex } from 'vs/base/common/arrays';
import { IMarginData } from 'vs/editor/browser/controller/mouseTarget';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
Expand Down Expand Up @@ -824,14 +824,32 @@ export const editorGutterDeletedBackground = registerColor('editorGutter.deleted
hc: new Color(new RGBA(141, 14, 20))
}, nls.localize('editorGutterDeletedBackground', "Editor gutter background color for lines that are deleted."));

export const minimapGutterModifiedBackground = registerColor('minimapGutter.modifiedBackground', {
dark: new Color(new RGBA(12, 125, 157)),
light: new Color(new RGBA(102, 175, 224)),
hc: new Color(new RGBA(0, 73, 122))
}, nls.localize('minimapGutterModifiedBackground', "Minimap gutter background color for lines that are modified."));

export const minimapGutterAddedBackground = registerColor('minimapGutter.addedBackground', {
dark: new Color(new RGBA(88, 124, 12)),
light: new Color(new RGBA(129, 184, 139)),
hc: new Color(new RGBA(27, 82, 37))
}, nls.localize('minimapGutterAddedBackground', "Minimap gutter background color for lines that are added."));

export const minimapGutterDeletedBackground = registerColor('minimapGutter.deletedBackground', {
dark: new Color(new RGBA(148, 21, 27)),
light: new Color(new RGBA(202, 75, 81)),
hc: new Color(new RGBA(141, 14, 20))
}, nls.localize('minimapGutterDeletedBackground', "Minimap gutter background color for lines that are deleted."));

const overviewRulerDefault = new Color(new RGBA(0, 122, 204, 0.6));
export const overviewRulerModifiedForeground = registerColor('editorOverviewRuler.modifiedForeground', { dark: overviewRulerDefault, light: overviewRulerDefault, hc: overviewRulerDefault }, nls.localize('overviewRulerModifiedForeground', 'Overview ruler marker color for modified content.'));
export const overviewRulerAddedForeground = registerColor('editorOverviewRuler.addedForeground', { dark: overviewRulerDefault, light: overviewRulerDefault, hc: overviewRulerDefault }, nls.localize('overviewRulerAddedForeground', 'Overview ruler marker color for added content.'));
export const overviewRulerDeletedForeground = registerColor('editorOverviewRuler.deletedForeground', { dark: overviewRulerDefault, light: overviewRulerDefault, hc: overviewRulerDefault }, nls.localize('overviewRulerDeletedForeground', 'Overview ruler marker color for deleted content.'));

class DirtyDiffDecorator extends Disposable {

static createDecoration(className: string, foregroundColor: string, options: { gutter: boolean, overview: boolean, isWholeLine: boolean }): ModelDecorationOptions {
static createDecoration(className: string, options: { gutter: boolean, overview: { active: boolean, color: string }, minimap: { active: boolean, color: string }, isWholeLine: boolean }): ModelDecorationOptions {
const decorationOptions: IModelDecorationOptions = {
isWholeLine: options.isWholeLine,
};
Expand All @@ -840,13 +858,20 @@ class DirtyDiffDecorator extends Disposable {
decorationOptions.linesDecorationsClassName = `dirty-diff-glyph ${className}`;
}

if (options.overview) {
if (options.overview.active) {
decorationOptions.overviewRuler = {
color: themeColorFromId(foregroundColor),
color: themeColorFromId(options.overview.color),
position: OverviewRulerLane.Left
};
}

if (options.minimap.active) {
decorationOptions.minimap = {
color: themeColorFromId(options.minimap.color),
position: MinimapPosition.Gutter
};
}

return ModelDecorationOptions.createDynamic(decorationOptions);
}

Expand All @@ -866,11 +891,26 @@ class DirtyDiffDecorator extends Disposable {
const decorations = configurationService.getValue<string>('scm.diffDecorations');
const gutter = decorations === 'all' || decorations === 'gutter';
const overview = decorations === 'all' || decorations === 'overview';
const options = { gutter, overview, isWholeLine: true };
const minimap = decorations === 'all' || decorations === 'minimap';

this.modifiedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-modified', overviewRulerModifiedForeground, options);
this.addedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-added', overviewRulerAddedForeground, options);
this.deletedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-deleted', overviewRulerDeletedForeground, { ...options, isWholeLine: false });
this.modifiedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-modified', {
gutter,
overview: { active: overview, color: overviewRulerModifiedForeground },
minimap: { active: minimap, color: minimapGutterModifiedBackground },
isWholeLine: true
});
this.addedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-added', {
gutter,
overview: { active: overview, color: overviewRulerAddedForeground },
minimap: { active: minimap, color: minimapGutterAddedBackground },
isWholeLine: true
});
this.deletedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-deleted', {
gutter,
overview: { active: overview, color: overviewRulerDeletedForeground },
minimap: { active: minimap, color: minimapGutterDeletedBackground },
isWholeLine: false
});

this._register(model.onDidChange(this.onDidChange, this));
}
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/contrib/scm/browser/scm.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
},
'scm.diffDecorations': {
type: 'string',
enum: ['all', 'gutter', 'overview', 'none'],
enum: ['all', 'gutter', 'overview', 'minimap', 'none'],
default: 'all',
description: localize('diffDecorations', "Controls diff decorations in the editor.")
},
Expand Down

0 comments on commit c8199b5

Please sign in to comment.