diff --git a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts index 1338656acab..0ca31065a45 100644 --- a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts +++ b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts @@ -10,7 +10,7 @@ import { ViewPart } from 'vs/editor/browser/view/viewPart'; import { Position } from 'vs/editor/common/core/position'; import { IEditorConfiguration } from 'vs/editor/common/config/editorConfiguration'; import { TokenizationRegistry } from 'vs/editor/common/languages'; -import { editorCursorForeground, editorOverviewRulerBorder, editorOverviewRulerBackground } from 'vs/editor/common/core/editorColorRegistry'; +import { editorCursorForeground, editorOverviewRulerBorder, editorOverviewRulerBackground, editorMultiCursorSecondaryForeground, editorMultiCursorPrimaryForeground } from 'vs/editor/common/core/editorColorRegistry'; import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext'; import { ViewContext } from 'vs/editor/common/viewModel/viewContext'; import { EditorTheme } from 'vs/editor/common/editorTheme'; @@ -29,7 +29,9 @@ class Settings { public readonly borderColor: string | null; public readonly hideCursor: boolean; - public readonly cursorColor: string | null; + public readonly cursorColorSingle: string | null; + public readonly cursorColorPrimary: string | null; + public readonly cursorColorSecondary: string | null; public readonly themeType: 'light' | 'dark' | 'hcLight' | 'hcDark'; public readonly backgroundColor: Color | null; @@ -55,8 +57,12 @@ class Settings { this.borderColor = borderColor ? borderColor.toString() : null; this.hideCursor = options.get(EditorOption.hideCursorInOverviewRuler); - const cursorColor = theme.getColor(editorCursorForeground); - this.cursorColor = cursorColor ? cursorColor.transparent(0.7).toString() : null; + const cursorColorSingle = theme.getColor(editorCursorForeground); + this.cursorColorSingle = cursorColorSingle ? cursorColorSingle.transparent(0.7).toString() : null; + const cursorColorPrimary = theme.getColor(editorMultiCursorPrimaryForeground); + this.cursorColorPrimary = cursorColorPrimary ? cursorColorPrimary.transparent(0.7).toString() : null; + const cursorColorSecondary = theme.getColor(editorMultiCursorSecondaryForeground); + this.cursorColorSecondary = cursorColorSecondary ? cursorColorSecondary.transparent(0.7).toString() : null; this.themeType = theme.type; @@ -189,7 +195,9 @@ class Settings { && this.renderBorder === other.renderBorder && this.borderColor === other.borderColor && this.hideCursor === other.hideCursor - && this.cursorColor === other.cursorColor + && this.cursorColorSingle === other.cursorColorSingle + && this.cursorColorPrimary === other.cursorColorPrimary + && this.cursorColorSecondary === other.cursorColorSecondary && this.themeType === other.themeType && Color.equals(this.backgroundColor, other.backgroundColor) && this.top === other.top @@ -213,6 +221,11 @@ const enum OverviewRulerLane { Full = 7 } +type Cursor = { + position: Position; + color: string | null; +}; + const enum ShouldRenderValue { NotNeeded = 0, Maybe = 1, @@ -226,10 +239,10 @@ export class DecorationsOverviewRuler extends ViewPart { private readonly _tokensColorTrackerListener: IDisposable; private readonly _domNode: FastDomNode; private _settings!: Settings; - private _cursorPositions: Position[]; + private _cursorPositions: Cursor[]; private _renderedDecorations: OverviewRulerDecorationsGroup[] = []; - private _renderedCursorPositions: Position[] = []; + private _renderedCursorPositions: Cursor[] = []; constructor(context: ViewContext) { super(context); @@ -249,7 +262,7 @@ export class DecorationsOverviewRuler extends ViewPart { } }); - this._cursorPositions = [new Position(1, 1)]; + this._cursorPositions = [{ position: new Position(1, 1), color: this._settings.cursorColorSingle }]; } public override dispose(): void { @@ -298,9 +311,13 @@ export class DecorationsOverviewRuler extends ViewPart { public override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { this._cursorPositions = []; for (let i = 0, len = e.selections.length; i < len; i++) { - this._cursorPositions[i] = e.selections[i].getPosition(); + let color = this._settings.cursorColorSingle; + if (len > 1) { + color = i === 0 ? this._settings.cursorColorPrimary : this._settings.cursorColorSecondary; + } + this._cursorPositions.push({ position: e.selections[i].getPosition(), color }); } - this._cursorPositions.sort(Position.compare); + this._cursorPositions.sort((a, b) => Position.compare(a.position, b.position)); return this._markRenderingIsMaybeNeeded(); } public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { @@ -352,7 +369,7 @@ export class DecorationsOverviewRuler extends ViewPart { if (this._actualShouldRender === ShouldRenderValue.Maybe && !OverviewRulerDecorationsGroup.equalsArr(this._renderedDecorations, decorations)) { this._actualShouldRender = ShouldRenderValue.Needed; } - if (this._actualShouldRender === ShouldRenderValue.Maybe && !equals(this._renderedCursorPositions, this._cursorPositions, (a, b) => a.lineNumber === b.lineNumber)) { + if (this._actualShouldRender === ShouldRenderValue.Maybe && !equals(this._renderedCursorPositions, this._cursorPositions, (a, b) => a.position.lineNumber === b.position.lineNumber && a.color === b.color)) { this._actualShouldRender = ShouldRenderValue.Needed; } if (this._actualShouldRender === ShouldRenderValue.Maybe) { @@ -443,17 +460,21 @@ export class DecorationsOverviewRuler extends ViewPart { } // Draw cursors - if (!this._settings.hideCursor && this._settings.cursorColor) { + if (!this._settings.hideCursor) { const cursorHeight = (2 * this._settings.pixelRatio) | 0; const halfCursorHeight = (cursorHeight / 2) | 0; const cursorX = this._settings.x[OverviewRulerLane.Full]; const cursorW = this._settings.w[OverviewRulerLane.Full]; - canvasCtx.fillStyle = this._settings.cursorColor; let prevY1 = -100; let prevY2 = -100; + let prevColor: string | null = null; for (let i = 0, len = this._cursorPositions.length; i < len; i++) { - const cursor = this._cursorPositions[i]; + const color = this._cursorPositions[i].color; + if (!color) { + continue; + } + const cursor = this._cursorPositions[i].position; let yCenter = (viewLayout.getVerticalOffsetForLineNumber(cursor.lineNumber) * heightRatio) | 0; if (yCenter < halfCursorHeight) { @@ -464,9 +485,9 @@ export class DecorationsOverviewRuler extends ViewPart { const y1 = yCenter - halfCursorHeight; const y2 = y1 + cursorHeight; - if (y1 > prevY2 + 1) { + if (y1 > prevY2 + 1 || color !== prevColor) { // flush prev - if (i !== 0) { + if (i !== 0 && prevColor) { canvasCtx.fillRect(cursorX, prevY1, cursorW, prevY2 - prevY1); } prevY1 = y1; @@ -477,8 +498,12 @@ export class DecorationsOverviewRuler extends ViewPart { prevY2 = y2; } } + prevColor = color; + canvasCtx.fillStyle = color; + } + if (prevColor) { + canvasCtx.fillRect(cursorX, prevY1, cursorW, prevY2 - prevY1); } - canvasCtx.fillRect(cursorX, prevY1, cursorW, prevY2 - prevY1); } if (this._settings.renderBorder && this._settings.borderColor && this._settings.overviewRulerLanes > 0) { diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts index 36506e9fed0..4502698cfc5 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts @@ -35,6 +35,12 @@ class ViewCursorRenderData { ) { } } +export enum CursorPlurality { + Single, + MultiPrimary, + MultiSecondary, +} + export class ViewCursor { private readonly _context: ViewContext; private readonly _domNode: FastDomNode; @@ -47,11 +53,12 @@ export class ViewCursor { private _isVisible: boolean; private _position: Position; + private _pluralityClass: string; private _lastRenderedContent: string; private _renderData: ViewCursorRenderData | null; - constructor(context: ViewContext) { + constructor(context: ViewContext, plurality: CursorPlurality) { this._context = context; const options = this._context.configuration.options; const fontInfo = options.get(EditorOption.fontInfo); @@ -73,6 +80,8 @@ export class ViewCursor { this._domNode.setDisplay('none'); this._position = new Position(1, 1); + this._pluralityClass = ''; + this.setPlurality(plurality); this._lastRenderedContent = ''; this._renderData = null; @@ -86,6 +95,23 @@ export class ViewCursor { return this._position; } + public setPlurality(plurality: CursorPlurality) { + switch (plurality) { + default: + case CursorPlurality.Single: + this._pluralityClass = ''; + break; + + case CursorPlurality.MultiPrimary: + this._pluralityClass = 'cursor-primary'; + break; + + case CursorPlurality.MultiSecondary: + this._pluralityClass = 'cursor-secondary'; + break; + } + } + public show(): void { if (!this._isVisible) { this._domNode.setVisibility('inherit'); @@ -229,7 +255,7 @@ export class ViewCursor { this._domNode.domNode.textContent = this._lastRenderedContent; } - this._domNode.setClassName(`cursor ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME} ${this._renderData.textContentClassName}`); + this._domNode.setClassName(`cursor ${this._pluralityClass} ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME} ${this._renderData.textContentClassName}`); this._domNode.setDisplay('block'); this._domNode.setTop(this._renderData.top); diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts index 1be969b7497..ceb27ce5ea3 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts @@ -7,10 +7,14 @@ import 'vs/css!./viewCursors'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; import { IntervalTimer, TimeoutTimer } from 'vs/base/common/async'; import { ViewPart } from 'vs/editor/browser/view/viewPart'; -import { IViewCursorRenderData, ViewCursor } from 'vs/editor/browser/viewParts/viewCursors/viewCursor'; +import { IViewCursorRenderData, ViewCursor, CursorPlurality } from 'vs/editor/browser/viewParts/viewCursors/viewCursor'; import { TextEditorCursorBlinkingStyle, TextEditorCursorStyle, EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; -import { editorCursorBackground, editorCursorForeground } from 'vs/editor/common/core/editorColorRegistry'; +import { + editorCursorBackground, editorCursorForeground, + editorMultiCursorPrimaryForeground, editorMultiCursorPrimaryBackground, + editorMultiCursorSecondaryForeground, editorMultiCursorSecondaryBackground +} from 'vs/editor/common/core/editorColorRegistry'; import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext'; import { ViewContext } from 'vs/editor/common/viewModel/viewContext'; import * as viewEvents from 'vs/editor/common/viewEvents'; @@ -57,7 +61,7 @@ export class ViewCursors extends ViewPart { this._isVisible = false; - this._primaryCursor = new ViewCursor(this._context); + this._primaryCursor = new ViewCursor(this._context, CursorPlurality.Single); this._secondaryCursors = []; this._renderData = []; @@ -88,6 +92,7 @@ export class ViewCursors extends ViewPart { } // --- begin event handlers + public override onCompositionStart(e: viewEvents.ViewCompositionStartEvent): boolean { this._isComposingInput = true; this._updateBlinking(); @@ -120,6 +125,7 @@ export class ViewCursors extends ViewPart { this._secondaryCursors.length !== secondaryPositions.length || (this._cursorSmoothCaretAnimation === 'explicit' && reason !== CursorChangeReason.Explicit) ); + this._primaryCursor.setPlurality(secondaryPositions.length ? CursorPlurality.MultiPrimary : CursorPlurality.Single); this._primaryCursor.onCursorPositionChanged(position, pauseAnimation); this._updateBlinking(); @@ -127,7 +133,7 @@ export class ViewCursors extends ViewPart { // Create new cursors const addCnt = secondaryPositions.length - this._secondaryCursors.length; for (let i = 0; i < addCnt; i++) { - const newCursor = new ViewCursor(this._context); + const newCursor = new ViewCursor(this._context, CursorPlurality.MultiSecondary); this._domNode.domNode.insertBefore(newCursor.getDomNode().domNode, this._primaryCursor.getDomNode().domNode.nextSibling); this._secondaryCursors.push(newCursor); } @@ -160,7 +166,6 @@ export class ViewCursors extends ViewPart { return true; } - public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { // true for inline decorations that can end up relayouting text return true; @@ -263,6 +268,7 @@ export class ViewCursors extends ViewPart { } } } + // --- end blinking logic private _updateDomClassName(): void { @@ -375,16 +381,29 @@ export class ViewCursors extends ViewPart { } registerThemingParticipant((theme, collector) => { - const caret = theme.getColor(editorCursorForeground); - if (caret) { - let caretBackground = theme.getColor(editorCursorBackground); - if (!caretBackground) { - caretBackground = caret.opposite(); - } - collector.addRule(`.monaco-editor .cursors-layer .cursor { background-color: ${caret}; border-color: ${caret}; color: ${caretBackground}; }`); - if (isHighContrast(theme.type)) { - collector.addRule(`.monaco-editor .cursors-layer.has-selection .cursor { border-left: 1px solid ${caretBackground}; border-right: 1px solid ${caretBackground}; }`); + type CursorTheme = { + foreground: string; + background: string; + class: string; + }; + + const cursorThemes: CursorTheme[] = [ + { class: '.cursor', foreground: editorCursorForeground, background: editorCursorBackground }, + { class: '.cursor-primary', foreground: editorMultiCursorPrimaryForeground, background: editorMultiCursorPrimaryBackground }, + { class: '.cursor-secondary', foreground: editorMultiCursorSecondaryForeground, background: editorMultiCursorSecondaryBackground }, + ]; + + for (const cursorTheme of cursorThemes) { + const caret = theme.getColor(cursorTheme.foreground); + if (caret) { + let caretBackground = theme.getColor(cursorTheme.background); + if (!caretBackground) { + caretBackground = caret.opposite(); + } + collector.addRule(`.monaco-editor .cursors-layer ${cursorTheme.class} { background-color: ${caret}; border-color: ${caret}; color: ${caretBackground}; }`); + if (isHighContrast(theme.type)) { + collector.addRule(`.monaco-editor .cursors-layer.has-selection ${cursorTheme.class} { border-left: 1px solid ${caretBackground}; border-right: 1px solid ${caretBackground}; }`); + } } } - }); diff --git a/src/vs/editor/common/core/editorColorRegistry.ts b/src/vs/editor/common/core/editorColorRegistry.ts index 95b738b7b6f..88e0419268f 100644 --- a/src/vs/editor/common/core/editorColorRegistry.ts +++ b/src/vs/editor/common/core/editorColorRegistry.ts @@ -20,6 +20,10 @@ export const editorSymbolHighlightBorder = registerColor('editor.symbolHighlight export const editorCursorForeground = registerColor('editorCursor.foreground', { dark: '#AEAFAD', light: Color.black, hcDark: Color.white, hcLight: '#0F4A85' }, nls.localize('caret', 'Color of the editor cursor.')); export const editorCursorBackground = registerColor('editorCursor.background', null, nls.localize('editorCursorBackground', 'The background color of the editor cursor. Allows customizing the color of a character overlapped by a block cursor.')); +export const editorMultiCursorPrimaryForeground = registerColor('editorMultiCursor.primary.foreground', { dark: editorCursorForeground, light: editorCursorForeground, hcDark: editorCursorForeground, hcLight: editorCursorForeground }, nls.localize('editorMultiCursorPrimaryForeground', 'Color of the primary editor cursor when multiple cursors are present.')); +export const editorMultiCursorPrimaryBackground = registerColor('editorMultiCursor.primary.background', { dark: editorCursorBackground, light: editorCursorBackground, hcDark: editorCursorBackground, hcLight: editorCursorBackground }, nls.localize('editorMultiCursorPrimaryBackground', 'The background color of the primary editor cursor when multiple cursors are present. Allows customizing the color of a character overlapped by a block cursor.')); +export const editorMultiCursorSecondaryForeground = registerColor('editorMultiCursor.secondary.foreground', { dark: editorCursorForeground, light: editorCursorForeground, hcDark: editorCursorForeground, hcLight: editorCursorForeground }, nls.localize('editorMultiCursorSecondaryForeground', 'Color of secondary editor cursors when multiple cursors are present.')); +export const editorMultiCursorSecondaryBackground = registerColor('editorMultiCursor.secondary.background', { dark: editorCursorBackground, light: editorCursorBackground, hcDark: editorCursorBackground, hcLight: editorCursorBackground }, nls.localize('editorMultiCursorSecondaryBackground', 'The background color of secondary editor cursors when multiple cursors are present. Allows customizing the color of a character overlapped by a block cursor.')); export const editorWhitespaces = registerColor('editorWhitespace.foreground', { dark: '#e3e4e229', light: '#33333333', hcDark: '#e3e4e229', hcLight: '#CCCCCC' }, nls.localize('editorWhitespaces', 'Color of whitespace characters in the editor.')); export const editorLineNumbers = registerColor('editorLineNumber.foreground', { dark: '#858585', light: '#237893', hcDark: Color.white, hcLight: '#292929' }, nls.localize('editorLineNumbers', 'Color of editor line numbers.'));