mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 04:49:35 +00:00
Implement separate colors for primary and secondary cursors when multiple cursors are present (#181991)
* Add support for separate primary cursor color when multiple cursors are present - Does not change the existing behavior when there's a single cursor. editorCursor.foreground and background are still used. - Add editorCursor.multiple.primary.foreground and background theme colors for the primary cursor. Only used when multiple cursors exist. Fallback to editorCursor.foreground/background when theme colors aren't set. - Add editorCursor.multiple.secondary.foreground and `background theme colors for non-primary cursors. Only used when multiple cursors exist. Fallback to editorCursor.foreground/background when theme colors aren't set. Add cursor-primary and cursor-secondary html classes to target with cursor color styles. No new class is introduced in the single-cursor case. - Currently does not affect overview ruler colors. editorCursor.foreground is still used, even when multiple cursors are present. * Update overview ruler to use primary and secondary cursor colors - This maintains the existing handling for colors being undefined. However, each of these colors have defaults do I'm not sure if it's actually possible for them to be undefined * Fix formatting * Fix compilation errors * Fall back to the existing cursor colors (to avoid breaking existing themes) --------- Co-authored-by: Alex Dima <alexdima@microsoft.com>
This commit is contained in:
parent
763ad71a17
commit
c37edea794
|
@ -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<HTMLCanvasElement>;
|
||||
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) {
|
||||
|
|
|
@ -35,6 +35,12 @@ class ViewCursorRenderData {
|
|||
) { }
|
||||
}
|
||||
|
||||
export enum CursorPlurality {
|
||||
Single,
|
||||
MultiPrimary,
|
||||
MultiSecondary,
|
||||
}
|
||||
|
||||
export class ViewCursor {
|
||||
private readonly _context: ViewContext;
|
||||
private readonly _domNode: FastDomNode<HTMLElement>;
|
||||
|
@ -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);
|
||||
|
|
|
@ -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}; }`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -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.'));
|
||||
|
||||
|
|
Loading…
Reference in a new issue