Skip to content

Commit

Permalink
Adds visual marker at start/end of the selection and selectionStyle o…
Browse files Browse the repository at this point in the history
…ption to Terminal

We introduce a new configuration option - selectionStyle - used to style the text selection layer.
It has three 'modes': plain (the current default), 'mark-end' (add a marked to the end) and 'mark-start' (add a marker to the beginning).

This is useful when integrating with applications that need to allow manipulating either end of the selection range via keyboard
  • Loading branch information
akariv committed Mar 31, 2021
1 parent dd952ac commit 23f991c
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 12 deletions.
3 changes: 2 additions & 1 deletion src/browser/public/Terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ export class Terminal implements ITerminalApi {
public paste(data: string): void {
this._core.paste(data);
}
public getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string;
public getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'logLevel' | 'selectionStyle' | 'rendererType' | 'termName' | 'wordSeparator'): string;
public getOption(key: 'allowTransparency' | 'altClickMovesCursor' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell'): boolean;
public getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number;
public getOption(key: 'fontWeight' | 'fontWeightBold'): FontWeight;
Expand All @@ -191,6 +191,7 @@ export class Terminal implements ITerminalApi {
public setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void;
public setOption(key: 'theme', value: ITheme): void;
public setOption(key: 'cols' | 'rows', value: number): void;
public setOption(key: 'selectionStyle', value: 'plain' | 'mark-start' | 'mark-end'): void;
public setOption(key: string, value: any): void;
public setOption(key: any, value: any): void {
this._core.optionsService.setOption(key, value);
Expand Down
45 changes: 36 additions & 9 deletions src/browser/renderer/SelectionRenderLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface ISelectionState {
end?: [number, number];
columnSelectMode?: boolean;
ydisp?: number;
selectionStyle?: string;
}

export class SelectionRenderLayer extends BaseRenderLayer {
Expand All @@ -35,7 +36,8 @@ export class SelectionRenderLayer extends BaseRenderLayer {
start: undefined,
end: undefined,
columnSelectMode: undefined,
ydisp: undefined
ydisp: undefined,
selectionStyle: undefined
};
}

Expand All @@ -53,8 +55,10 @@ export class SelectionRenderLayer extends BaseRenderLayer {
}

public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
const selectionStyle: string = this._optionsService.getOption('selectionStyle') || 'plain';

// Selection has not changed
if (!this._didStateChange(start, end, columnSelectMode, this._bufferService.buffer.ydisp)) {
if (!this._didStateChange(start, end, columnSelectMode, this._bufferService.buffer.ydisp, selectionStyle)) {
return;
}

Expand All @@ -80,6 +84,7 @@ export class SelectionRenderLayer extends BaseRenderLayer {
}

this._ctx.fillStyle = this._colors.selectionTransparent.css;
this._ctx.strokeStyle = this._colors.cursor.css;

if (columnSelectMode) {
const startCol = start[0];
Expand All @@ -88,9 +93,23 @@ export class SelectionRenderLayer extends BaseRenderLayer {
this._fillCells(startCol, viewportCappedStartRow, width, height);
} else {
// Draw first row
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
const startRowEndCol = viewportCappedStartRow === viewportEndRow ? end[0] : this._bufferService.cols;
this._fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1);
let startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
let startRowEndCol = viewportCappedStartRow === viewportEndRow ? end[0] : this._bufferService.cols;
switch (selectionStyle) {
case 'mark-start':
this._strokeRectAtCell(startCol, viewportCappedStartRow, 1, 1);
startCol += 1;
break;
case 'mark-end':
if (viewportCappedStartRow === viewportCappedEndRow) {
this._strokeRectAtCell(startRowEndCol - 1, viewportCappedStartRow, 1, 1);
startRowEndCol -= 1;
}
break;
}
if (startRowEndCol - startCol > 0) {
this._fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1);
}

// Draw middle rows
const middleRowsCount = Math.max(viewportCappedEndRow - viewportCappedStartRow - 1, 0);
Expand All @@ -99,8 +118,14 @@ export class SelectionRenderLayer extends BaseRenderLayer {
// Draw final row
if (viewportCappedStartRow !== viewportCappedEndRow) {
// Only draw viewportEndRow if it's not the same as viewportStartRow
const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
this._fillCells(0, viewportCappedEndRow, endCol, 1);
let endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
if (selectionStyle === 'mark-end') {
this._strokeRectAtCell(endCol - 1, viewportCappedEndRow, 1, 1);
endCol -= 1;
}
if (endCol > 0) {
this._fillCells(0, viewportCappedEndRow, endCol, 1);
}
}
}

Expand All @@ -109,13 +134,15 @@ export class SelectionRenderLayer extends BaseRenderLayer {
this._state.end = [end[0], end[1]];
this._state.columnSelectMode = columnSelectMode;
this._state.ydisp = this._bufferService.buffer.ydisp;
this._state.selectionStyle = selectionStyle;
}

private _didStateChange(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean, ydisp: number): boolean {
private _didStateChange(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean, ydisp: number, selectionStyle: string): boolean {
return !this._areCoordinatesEqual(start, this._state.start) ||
!this._areCoordinatesEqual(end, this._state.end) ||
columnSelectMode !== this._state.columnSelectMode ||
ydisp !== this._state.ydisp;
ydisp !== this._state.ydisp ||
selectionStyle !== this._state.selectionStyle;
}

private _areCoordinatesEqual(coord1: [number, number] | undefined, coord2: [number, number] | undefined): boolean {
Expand Down
3 changes: 2 additions & 1 deletion src/common/services/OptionsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({
altClickMovesCursor: true,
convertEol: false,
termName: 'xterm',
cancelEvents: false
cancelEvents: false,
selectionStyle: 'plain'
});

const FONT_WEIGHT_OPTIONS: Extract<FontWeight, string>[] = ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'];
Expand Down
1 change: 1 addition & 0 deletions src/common/services/Services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ export interface ITerminalOptions {
screenReaderMode: boolean;
scrollback: number;
scrollSensitivity: number;
selectionStyle: 'plain' | 'mark-start' | 'mark-end';
tabStopWidth: number;
theme: ITheme;
windowsMode: boolean;
Expand Down
14 changes: 13 additions & 1 deletion typings/xterm.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,12 @@ declare module 'xterm' {
*/
scrollSensitivity?: number;

/**
* Styling for the text selection shade - plain shading,
* or add a marker at the start/end of the shade
*/
selectionStyle?: 'plain' | 'mark-start' | 'mark-end';

/**
* The size of tab stops in the terminal.
*/
Expand Down Expand Up @@ -946,7 +952,7 @@ declare module 'xterm' {
* Retrieves an option's value from the terminal.
* @param key The option key.
*/
getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string;
getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'logLevel' | 'selectionStyle' | 'rendererType' | 'termName' | 'wordSeparator'): string;
/**
* Retrieves an option's value from the terminal.
* @param key The option key.
Expand Down Expand Up @@ -1022,6 +1028,12 @@ declare module 'xterm' {
* @param value The option value.
*/
setOption(key: 'cols' | 'rows', value: number): void;
/**
* Sets an option on the terminal.
* @param key The option key.
* @param value The option value.
*/
setOption(key: 'selectionStyle', value: 'plain' | 'mark-start' | 'mark-end'): void;
/**
* Sets an option on the terminal.
* @param key The option key.
Expand Down

0 comments on commit 23f991c

Please sign in to comment.