Merge branch 'master' into window_manipulation
This commit is contained in:
@@ -25,6 +25,14 @@ function getCursor(term: TestTerminal): number[] {
|
||||
];
|
||||
}
|
||||
|
||||
function getLines(term: TestTerminal, limit: number = term.rows): string[] {
|
||||
const res: string[] = [];
|
||||
for (let i = 0; i < limit; ++i) {
|
||||
res.push(term.buffer.lines.get(i).translateToString(true));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
describe('InputHandler', () => {
|
||||
describe('save and restore cursor', () => {
|
||||
const terminal = new MockInputHandlingTerminal();
|
||||
@@ -1125,13 +1133,6 @@ describe('InputHandler', () => {
|
||||
beforeEach(() => {
|
||||
term = new TestTerminal({cols: 10, rows: 10});
|
||||
});
|
||||
function getLines(term: TestTerminal, limit: number = term.rows): string[] {
|
||||
const res: string[] = [];
|
||||
for (let i = 0; i < limit; ++i) {
|
||||
res.push(term.buffer.lines.get(i).translateToString(true));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
it('scrollUp', () => {
|
||||
term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[2;4r\x1b[2Sm');
|
||||
assert.deepEqual(getLines(term), ['m', '3', '', '', '4', '5', '6', '7', '8', '9']);
|
||||
@@ -1191,4 +1192,60 @@ describe('InputHandler', () => {
|
||||
assert.deepEqual(getLines(term), ['0', '1', 'n', 'm', '', '', '6', '7', '8', '9']);
|
||||
});
|
||||
});
|
||||
describe('SL/SR/DECIC/DECDC', () => {
|
||||
let term: TestTerminal;
|
||||
beforeEach(() => {
|
||||
term = new TestTerminal({cols: 5, rows: 5, scrollback: 1});
|
||||
});
|
||||
it('SL (scrollLeft)', () => {
|
||||
term.writeSync('12345'.repeat(6));
|
||||
term.writeSync('\x1b[ @');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '2345', '2345', '2345', '2345', '2345']);
|
||||
term.writeSync('\x1b[0 @');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '345', '345', '345', '345', '345']);
|
||||
term.writeSync('\x1b[2 @');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '5', '5', '5', '5', '5']);
|
||||
});
|
||||
it('SR (scrollRight)', () => {
|
||||
term.writeSync('12345'.repeat(6));
|
||||
term.writeSync('\x1b[ A');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 1234', ' 1234', ' 1234', ' 1234', ' 1234']);
|
||||
term.writeSync('\x1b[0 A');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 123', ' 123', ' 123', ' 123', ' 123']);
|
||||
term.writeSync('\x1b[2 A');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 1', ' 1', ' 1', ' 1', ' 1']);
|
||||
});
|
||||
it('insertColumns (DECIC)', () => {
|
||||
term.writeSync('12345'.repeat(6));
|
||||
term.writeSync('\x1b[3;3H');
|
||||
term.writeSync('\x1b[\'}');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);
|
||||
term.reset();
|
||||
term.writeSync('12345'.repeat(6));
|
||||
term.writeSync('\x1b[3;3H');
|
||||
term.writeSync('\x1b[1\'}');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);
|
||||
term.reset();
|
||||
term.writeSync('12345'.repeat(6));
|
||||
term.writeSync('\x1b[3;3H');
|
||||
term.writeSync('\x1b[2\'}');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 3', '12 3', '12 3', '12 3', '12 3']);
|
||||
});
|
||||
it('deleteColumns (DECDC)', () => {
|
||||
term.writeSync('12345'.repeat(6));
|
||||
term.writeSync('\x1b[3;3H');
|
||||
term.writeSync('\x1b[\'~');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '1245', '1245', '1245', '1245', '1245']);
|
||||
term.reset();
|
||||
term.writeSync('12345'.repeat(6));
|
||||
term.writeSync('\x1b[3;3H');
|
||||
term.writeSync('\x1b[1\'~');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '1245', '1245', '1245', '1245', '1245']);
|
||||
term.reset();
|
||||
term.writeSync('12345'.repeat(6));
|
||||
term.writeSync('\x1b[3;3H');
|
||||
term.writeSync('\x1b[2\'~');
|
||||
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '125', '125', '125', '125', '125']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+103
-9
@@ -175,7 +175,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* CSI handler
|
||||
*/
|
||||
this._parser.setCsiHandler({final: '@'}, params => this.insertChars(params));
|
||||
this._parser.setCsiHandler({intermediates: ' ', final: '@'}, params => this.scrollLeft(params));
|
||||
this._parser.setCsiHandler({final: 'A'}, params => this.cursorUp(params));
|
||||
this._parser.setCsiHandler({intermediates: ' ', final: 'A'}, params => this.scrollRight(params));
|
||||
this._parser.setCsiHandler({final: 'B'}, params => this.cursorDown(params));
|
||||
this._parser.setCsiHandler({final: 'C'}, params => this.cursorForward(params));
|
||||
this._parser.setCsiHandler({final: 'D'}, params => this.cursorBackward(params));
|
||||
@@ -217,6 +219,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._parser.setCsiHandler({final: 's'}, params => this.saveCursor(params));
|
||||
this._parser.setCsiHandler({final: 't'}, params => this.manipulateWindowOptions(params));
|
||||
this._parser.setCsiHandler({final: 'u'}, params => this.restoreCursor(params));
|
||||
this._parser.setCsiHandler({intermediates: '\'', final: '}'}, params => this.insertColumns(params));
|
||||
this._parser.setCsiHandler({intermediates: '\'', final: '~'}, params => this.deleteColumns(params));
|
||||
|
||||
/**
|
||||
* execute handler
|
||||
@@ -1034,18 +1038,108 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* CSI Ps T Scroll down Ps lines (default = 1) (SD).
|
||||
*/
|
||||
public scrollDown(params: IParams): void {
|
||||
if (params.length < 2) {
|
||||
let param = params.params[0] || 1;
|
||||
let param = params.params[0] || 1;
|
||||
|
||||
// make buffer local for faster access
|
||||
const buffer = this._bufferService.buffer;
|
||||
// make buffer local for faster access
|
||||
const buffer = this._bufferService.buffer;
|
||||
|
||||
while (param--) {
|
||||
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 1);
|
||||
buffer.lines.splice(buffer.ybase + buffer.scrollTop, 0, buffer.getBlankLine(this._terminal.eraseAttrData()));
|
||||
}
|
||||
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
|
||||
while (param--) {
|
||||
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 1);
|
||||
buffer.lines.splice(buffer.ybase + buffer.scrollTop, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA));
|
||||
}
|
||||
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Ps SP @ Scroll left Ps columns (default = 1) (SL) ECMA-48
|
||||
*
|
||||
* Notation: (Pn)
|
||||
* Representation: CSI Pn 02/00 04/00
|
||||
* Parameter default value: Pn = 1
|
||||
* SL causes the data in the presentation component to be moved by n character positions
|
||||
* if the line orientation is horizontal, or by n line positions if the line orientation
|
||||
* is vertical, such that the data appear to move to the left; where n equals the value of Pn.
|
||||
* The active presentation position is not affected by this control function.
|
||||
*
|
||||
* Supported:
|
||||
* - always left shift (no line orientation setting respected)
|
||||
*/
|
||||
public scrollLeft(params: IParams): void {
|
||||
const buffer = this._bufferService.buffer;
|
||||
if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) {
|
||||
return;
|
||||
}
|
||||
const param = params.params[0] || 1;
|
||||
for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) {
|
||||
const line = buffer.lines.get(buffer.ybase + y);
|
||||
line.deleteCells(0, param, buffer.getNullCell(this._terminal.eraseAttrData()));
|
||||
line.isWrapped = false;
|
||||
}
|
||||
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Ps SP A Scroll right Ps columns (default = 1) (SR) ECMA-48
|
||||
*
|
||||
* Notation: (Pn)
|
||||
* Representation: CSI Pn 02/00 04/01
|
||||
* Parameter default value: Pn = 1
|
||||
* SR causes the data in the presentation component to be moved by n character positions
|
||||
* if the line orientation is horizontal, or by n line positions if the line orientation
|
||||
* is vertical, such that the data appear to move to the right; where n equals the value of Pn.
|
||||
* The active presentation position is not affected by this control function.
|
||||
*
|
||||
* Supported:
|
||||
* - always right shift (no line orientation setting respected)
|
||||
*/
|
||||
public scrollRight(params: IParams): void {
|
||||
const buffer = this._bufferService.buffer;
|
||||
if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) {
|
||||
return;
|
||||
}
|
||||
const param = params.params[0] || 1;
|
||||
for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) {
|
||||
const line = buffer.lines.get(buffer.ybase + y);
|
||||
line.insertCells(0, param, buffer.getNullCell(this._terminal.eraseAttrData()));
|
||||
line.isWrapped = false;
|
||||
}
|
||||
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Pm ' }
|
||||
* Insert Ps Column(s) (default = 1) (DECIC), VT420 and up.
|
||||
*/
|
||||
public insertColumns(params: IParams): void {
|
||||
const buffer = this._bufferService.buffer;
|
||||
if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) {
|
||||
return;
|
||||
}
|
||||
const param = params.params[0] || 1;
|
||||
for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) {
|
||||
const line = this._bufferService.buffer.lines.get(buffer.ybase + y);
|
||||
line.insertCells(buffer.x, param, buffer.getNullCell(this._terminal.eraseAttrData()));
|
||||
line.isWrapped = false;
|
||||
}
|
||||
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Pm ' ~
|
||||
* Delete Ps Column(s) (default = 1) (DECDC), VT420 and up.
|
||||
*/
|
||||
public deleteColumns(params: IParams): void {
|
||||
const buffer = this._bufferService.buffer;
|
||||
if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) {
|
||||
return;
|
||||
}
|
||||
const param = params.params[0] || 1;
|
||||
for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) {
|
||||
const line = buffer.lines.get(buffer.ybase + y);
|
||||
line.deleteCells(buffer.x, param, buffer.getNullCell(this._terminal.eraseAttrData()));
|
||||
line.isWrapped = false;
|
||||
}
|
||||
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+9
-13
@@ -76,7 +76,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
/**
|
||||
* The HTMLElement that the terminal is created in, set by Terminal.open.
|
||||
*/
|
||||
private _parent: HTMLElement;
|
||||
private _parent: HTMLElement | null;
|
||||
private _document: Document;
|
||||
private _viewportScrollArea: HTMLElement;
|
||||
private _viewportElement: HTMLElement;
|
||||
@@ -383,18 +383,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
case 'theme':
|
||||
this._setTheme(this.optionsService.options.theme);
|
||||
break;
|
||||
case 'scrollback':
|
||||
const newBufferLength = this.rows + this.optionsService.options.scrollback;
|
||||
if (this.buffer.lines.length > newBufferLength) {
|
||||
const amountToTrim = this.buffer.lines.length - newBufferLength;
|
||||
const needsRefresh = (this.buffer.ydisp - amountToTrim < 0);
|
||||
this.buffer.lines.trimStart(amountToTrim);
|
||||
this.buffer.ybase = Math.max(this.buffer.ybase - amountToTrim, 0);
|
||||
this.buffer.ydisp = Math.max(this.buffer.ydisp - amountToTrim, 0);
|
||||
if (needsRefresh) {
|
||||
this.refresh(0, this.rows - 1);
|
||||
}
|
||||
}
|
||||
case 'windowsMode':
|
||||
if (this.optionsService.options.windowsMode) {
|
||||
if (!this._windowsMode) {
|
||||
@@ -520,6 +508,10 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
throw new Error('Terminal requires a parent element.');
|
||||
}
|
||||
|
||||
if (!document.body.contains(parent)) {
|
||||
this._logService.warn('Terminal.open was called on an element that was not attached to the DOM');
|
||||
}
|
||||
|
||||
this._document = this._parent.ownerDocument;
|
||||
|
||||
// Create main element container
|
||||
@@ -1469,6 +1461,10 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this._charSizeService.measure();
|
||||
}
|
||||
|
||||
// Sync the scroll area to make sure scroll events don't fire and scroll the viewport to an
|
||||
// invalid location
|
||||
this.viewport.syncScrollArea(true);
|
||||
|
||||
this.refresh(0, this.rows - 1);
|
||||
this._onResize.fire({ cols: x, rows: y });
|
||||
}
|
||||
|
||||
Vendored
+6
-2
@@ -85,7 +85,9 @@ export interface IInputHandler {
|
||||
/** C0 SI */ shiftIn(): void;
|
||||
|
||||
/** CSI @ */ insertChars(params: IParams): void;
|
||||
/** CSI SP @ */ scrollLeft(params: IParams): void;
|
||||
/** CSI A */ cursorUp(params: IParams): void;
|
||||
/** CSI SP A */ scrollRight(params: IParams): void;
|
||||
/** CSI B */ cursorDown(params: IParams): void;
|
||||
/** CSI C */ cursorForward(params: IParams): void;
|
||||
/** CSI D */ cursorBackward(params: IParams): void;
|
||||
@@ -121,6 +123,8 @@ export interface IInputHandler {
|
||||
/** CSI r */ setScrollRegion(params: IParams, collect?: string): void;
|
||||
/** CSI s */ saveCursor(params: IParams): void;
|
||||
/** CSI u */ restoreCursor(params: IParams): void;
|
||||
/** CSI ' } */ insertColumns(params: IParams): void;
|
||||
/** CSI ' ~ */ deleteColumns(params: IParams): void;
|
||||
/** OSC 0
|
||||
OSC 2 */ setTitle(data: string): void;
|
||||
/** ESC E */ nextLine(): void;
|
||||
@@ -173,7 +177,7 @@ export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAcc
|
||||
|
||||
// Portions of the public API that are required by the internal Terminal
|
||||
export interface IPublicTerminal extends IDisposable {
|
||||
textarea: HTMLTextAreaElement;
|
||||
textarea: HTMLTextAreaElement | undefined;
|
||||
rows: number;
|
||||
cols: number;
|
||||
buffer: IBuffer;
|
||||
@@ -226,7 +230,7 @@ export interface IBufferAccessor {
|
||||
}
|
||||
|
||||
export interface IElementAccessor {
|
||||
readonly element: HTMLElement;
|
||||
readonly element: HTMLElement | undefined;
|
||||
}
|
||||
|
||||
export interface ILinkifierAccessor {
|
||||
|
||||
@@ -306,7 +306,9 @@ export class Linkifier implements ILinkifier {
|
||||
e => {
|
||||
this._onLinkTooltip.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg));
|
||||
if (matcher.hoverTooltipCallback) {
|
||||
matcher.hoverTooltipCallback(e, uri);
|
||||
// Note that IViewportRange use 1-based coordinates to align with escape sequences such
|
||||
// as CUP which use 1,1 as the default for row/col
|
||||
matcher.hoverTooltipCallback(e, uri, { start: { row: y1 + 1, col: x1 + 1 }, end: { row: y2 + 1, col: x2 } });
|
||||
}
|
||||
},
|
||||
() => {
|
||||
|
||||
Vendored
+14
-3
@@ -35,7 +35,7 @@ export interface IPartialColorSet {
|
||||
|
||||
export interface IViewport extends IDisposable {
|
||||
scrollBarWidth: number;
|
||||
syncScrollArea(): void;
|
||||
syncScrollArea(immediate?: boolean): void;
|
||||
getLinesScrolled(ev: WheelEvent): number;
|
||||
onWheel(ev: WheelEvent): boolean;
|
||||
onTouchStart(ev: TouchEvent): void;
|
||||
@@ -43,14 +43,25 @@ export interface IViewport extends IDisposable {
|
||||
onThemeChange(colors: IColorSet): void;
|
||||
}
|
||||
|
||||
export interface IViewportRange {
|
||||
start: IViewportCellPosition;
|
||||
end: IViewportCellPosition;
|
||||
}
|
||||
|
||||
export interface IViewportCellPosition {
|
||||
col: number;
|
||||
row: number;
|
||||
}
|
||||
|
||||
export type LinkMatcherHandler = (event: MouseEvent, uri: string) => void;
|
||||
export type LinkMatcherHoverTooltipCallback = (event: MouseEvent, uri: string, position: IViewportRange) => void;
|
||||
export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void;
|
||||
|
||||
export interface ILinkMatcher {
|
||||
id: number;
|
||||
regex: RegExp;
|
||||
handler: LinkMatcherHandler;
|
||||
hoverTooltipCallback?: LinkMatcherHandler;
|
||||
hoverTooltipCallback?: LinkMatcherHoverTooltipCallback;
|
||||
hoverLeaveCallback?: () => void;
|
||||
matchIndex?: number;
|
||||
validationCallback?: LinkMatcherValidationCallback;
|
||||
@@ -96,7 +107,7 @@ export interface ILinkMatcherOptions {
|
||||
/**
|
||||
* A callback that fires when the mouse hovers over a link.
|
||||
*/
|
||||
tooltipCallback?: LinkMatcherHandler;
|
||||
tooltipCallback?: LinkMatcherHoverTooltipCallback;
|
||||
/**
|
||||
* A callback that fires when the mouse leaves a link that was hovered.
|
||||
*/
|
||||
|
||||
+30
-11
@@ -7,7 +7,7 @@ import { Disposable } from 'common/Lifecycle';
|
||||
import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import { IColorSet, IViewport } from 'browser/Types';
|
||||
import { ICharSizeService, IRenderService } from 'browser/services/Services';
|
||||
import { IBufferService } from 'common/services/Services';
|
||||
import { IBufferService, IOptionsService } from 'common/services/Services';
|
||||
|
||||
const FALLBACK_SCROLL_BAR_WIDTH = 15;
|
||||
|
||||
@@ -37,6 +37,7 @@ export class Viewport extends Disposable implements IViewport {
|
||||
private readonly _viewportElement: HTMLElement,
|
||||
private readonly _scrollArea: HTMLElement,
|
||||
@IBufferService private readonly _bufferService: IBufferService,
|
||||
@IOptionsService private readonly _optionsService: IOptionsService,
|
||||
@ICharSizeService private readonly _charSizeService: ICharSizeService,
|
||||
@IRenderService private readonly _renderService: IRenderService
|
||||
) {
|
||||
@@ -60,7 +61,14 @@ export class Viewport extends Disposable implements IViewport {
|
||||
* Refreshes row height, setting line-height, viewport height and scroll area height if
|
||||
* necessary.
|
||||
*/
|
||||
private _refresh(): void {
|
||||
private _refresh(immediate: boolean): void {
|
||||
if (immediate) {
|
||||
this._innerRefresh();
|
||||
if (this._refreshAnimationFrame !== null) {
|
||||
cancelAnimationFrame(this._refreshAnimationFrame);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this._refreshAnimationFrame === null) {
|
||||
this._refreshAnimationFrame = requestAnimationFrame(() => this._innerRefresh());
|
||||
}
|
||||
@@ -88,40 +96,39 @@ export class Viewport extends Disposable implements IViewport {
|
||||
|
||||
this._refreshAnimationFrame = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates dimensions and synchronizes the scroll area if necessary.
|
||||
*/
|
||||
public syncScrollArea(): void {
|
||||
public syncScrollArea(immediate: boolean = false): void {
|
||||
// If buffer height changed
|
||||
if (this._lastRecordedBufferLength !== this._bufferService.buffer.lines.length) {
|
||||
this._lastRecordedBufferLength = this._bufferService.buffer.lines.length;
|
||||
this._refresh();
|
||||
this._refresh(immediate);
|
||||
return;
|
||||
}
|
||||
|
||||
// If viewport height changed
|
||||
if (this._lastRecordedViewportHeight !== this._renderService.dimensions.canvasHeight) {
|
||||
this._refresh();
|
||||
this._refresh(immediate);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the buffer position doesn't match last scroll top
|
||||
const newScrollTop = this._bufferService.buffer.ydisp * this._currentRowHeight;
|
||||
if (this._lastScrollTop !== newScrollTop) {
|
||||
this._refresh();
|
||||
this._refresh(immediate);
|
||||
return;
|
||||
}
|
||||
|
||||
// If element's scroll top changed, this can happen when hiding the element
|
||||
if (this._lastScrollTop !== this._viewportElement.scrollTop) {
|
||||
this._refresh();
|
||||
this._refresh(immediate);
|
||||
return;
|
||||
}
|
||||
|
||||
// If row height changed
|
||||
if (this._renderService.dimensions.scaledCellHeight / window.devicePixelRatio !== this._currentRowHeight) {
|
||||
this._refresh();
|
||||
this._refresh(immediate);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -191,7 +198,7 @@ export class Viewport extends Disposable implements IViewport {
|
||||
}
|
||||
|
||||
// Fallback to WheelEvent.DOM_DELTA_PIXEL
|
||||
let amount = ev.deltaY;
|
||||
let amount = this._applyScrollModifier(ev.deltaY, ev);
|
||||
if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {
|
||||
amount *= this._currentRowHeight;
|
||||
} else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
|
||||
@@ -212,7 +219,7 @@ export class Viewport extends Disposable implements IViewport {
|
||||
}
|
||||
|
||||
// Fallback to WheelEvent.DOM_DELTA_LINE
|
||||
let amount = ev.deltaY;
|
||||
let amount = this._applyScrollModifier(ev.deltaY, ev);
|
||||
if (ev.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
|
||||
amount /= this._currentRowHeight + 0.0; // Prevent integer division
|
||||
this._wheelPartialScroll += amount;
|
||||
@@ -224,6 +231,18 @@ export class Viewport extends Disposable implements IViewport {
|
||||
return amount;
|
||||
}
|
||||
|
||||
private _applyScrollModifier(amount: number, ev: WheelEvent): number {
|
||||
const modifier = this._optionsService.options.fastScrollModifier;
|
||||
// Multiply the scroll speed when the modifier is down
|
||||
if ((modifier === 'alt' && ev.altKey) ||
|
||||
(modifier === 'ctrl' && ev.ctrlKey) ||
|
||||
(modifier === 'shift' && ev.shiftKey)) {
|
||||
return amount * this._optionsService.options.fastScrollSensitivity;
|
||||
}
|
||||
|
||||
return amount * this._optionsService.options.scrollSensitivity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the touchstart event, recording the touch occurred.
|
||||
* @param ev The touch event.
|
||||
|
||||
@@ -303,7 +303,6 @@ export class CharacterJoinerRegistry implements ICharacterJoinerRegistry {
|
||||
// current range
|
||||
ranges[i - 1][1] = Math.max(newRange[1], range[1]);
|
||||
ranges.splice(i, 1);
|
||||
inRange = false;
|
||||
return ranges;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from 'common/buffer/Constants';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { ITerminalOptions, IOptionsService } from 'common/services/Services';
|
||||
import { IOptionsService } from 'common/services/Services';
|
||||
|
||||
export const BOLD_CLASS = 'xterm-bold';
|
||||
export const DIM_CLASS = 'xterm-dim';
|
||||
|
||||
@@ -210,7 +210,7 @@ export class SelectionService implements ISelectionService {
|
||||
for (let i = start[1] + 1; i <= end[1] - 1; i++) {
|
||||
const bufferLine = buffer.lines.get(i);
|
||||
const lineText = buffer.translateBufferLineToString(i, true);
|
||||
if (bufferLine!.isWrapped) {
|
||||
if (bufferLine && bufferLine.isWrapped) {
|
||||
result[result.length - 1] += lineText;
|
||||
} else {
|
||||
result.push(lineText);
|
||||
@@ -221,7 +221,7 @@ export class SelectionService implements ISelectionService {
|
||||
if (start[1] !== end[1]) {
|
||||
const bufferLine = buffer.lines.get(end[1]);
|
||||
const lineText = buffer.translateBufferLineToString(end[1], true, 0, end[0]);
|
||||
if (bufferLine!.isWrapped) {
|
||||
if (bufferLine && bufferLine!.isWrapped) {
|
||||
result[result.length - 1] += lineText;
|
||||
} else {
|
||||
result.push(lineText);
|
||||
|
||||
+18
-12
@@ -167,19 +167,25 @@ export class Buffer implements IBuffer {
|
||||
if (this._rows < newRows) {
|
||||
for (let y = this._rows; y < newRows; y++) {
|
||||
if (this.lines.length < newRows + this.ybase) {
|
||||
if (this.ybase > 0 && this.lines.length <= this.ybase + this.y + addToY + 1) {
|
||||
// There is room above the buffer and there are no empty elements below the line,
|
||||
// scroll up
|
||||
this.ybase--;
|
||||
addToY++;
|
||||
if (this.ydisp > 0) {
|
||||
// Viewport is at the top of the buffer, must increase downwards
|
||||
this.ydisp--;
|
||||
}
|
||||
} else {
|
||||
// Add a blank line if there is no buffer left at the top to scroll to, or if there
|
||||
// are blank lines after the cursor
|
||||
if (this._optionsService.options.windowsMode) {
|
||||
// Just add the new missing rows on Windows as conpty reprints the screen with it's
|
||||
// view of the world. Once a line enters scrollback for conpty it remains there
|
||||
this.lines.push(new BufferLine(newCols, nullCell));
|
||||
} else {
|
||||
if (this.ybase > 0 && this.lines.length <= this.ybase + this.y + addToY + 1) {
|
||||
// There is room above the buffer and there are no empty elements below the line,
|
||||
// scroll up
|
||||
this.ybase--;
|
||||
addToY++;
|
||||
if (this.ydisp > 0) {
|
||||
// Viewport is at the top of the buffer, must increase downwards
|
||||
this.ydisp--;
|
||||
}
|
||||
} else {
|
||||
// Add a blank line if there is no buffer left at the top to scroll to, or if there
|
||||
// are blank lines after the cursor
|
||||
this.lines.push(new BufferLine(newCols, nullCell));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { CharData, IBufferLine, ICellData } from 'common/Types';
|
||||
import { stringFromCodePoint } from 'common/input/TextDecoder';
|
||||
import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Content } from 'common/buffer/Constants';
|
||||
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Content } from 'common/buffer/Constants';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
|
||||
|
||||
@@ -108,6 +108,12 @@ describe('Keyboard', () => {
|
||||
it('should return \\x1b[5C for alt+right', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 39 }, { isMac: false }).key, '\x1b[1;5C'); // CSI 5 C
|
||||
});
|
||||
it('should return \\x1b[5D for alt+up', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 38 }, { isMac: false }).key, '\x1b[1;5A'); // CSI 5 D
|
||||
});
|
||||
it('should return \\x1b[5C for alt+down', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 40 }, { isMac: false }).key, '\x1b[1;5B'); // CSI 5 C
|
||||
});
|
||||
it('should return \\x1ba for alt+a', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 65 }, { isMac: false }).key, '\x1ba');
|
||||
});
|
||||
@@ -120,6 +126,12 @@ describe('Keyboard', () => {
|
||||
it('should return \\x1bf for alt+right', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 39 }, { isMac: true }).key, '\x1bf'); // CSI 5 C
|
||||
});
|
||||
it('should return \\x1bb for alt+up', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 38 }, { isMac: true }).key, '\x1b[1;3A'); // CSI 5 D
|
||||
});
|
||||
it('should return \\x1bf for alt+down', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 40 }, { isMac: true }).key, '\x1b[1;3B'); // CSI 5 C
|
||||
});
|
||||
it('should return undefined for alt+a', () => {
|
||||
assert.strictEqual(testEvaluateKeyboardEvent({ altKey: true, keyCode: 65 }, { isMac: true }).key, undefined), { isMac: true };
|
||||
});
|
||||
|
||||
@@ -113,13 +113,16 @@ export function evaluateKeyboardEvent(
|
||||
break;
|
||||
case 37:
|
||||
// left-arrow
|
||||
if (ev.metaKey) {
|
||||
break;
|
||||
}
|
||||
if (modifiers) {
|
||||
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'D';
|
||||
// HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards
|
||||
// http://unix.stackexchange.com/a/108106
|
||||
// macOS uses different escape sequences than linux
|
||||
if (result.key === C0.ESC + '[1;3D') {
|
||||
result.key = isMac ? C0.ESC + 'b' : C0.ESC + '[1;5D';
|
||||
result.key = C0.ESC + (isMac ? 'b' : '[1;5D');
|
||||
}
|
||||
} else if (applicationCursorMode) {
|
||||
result.key = C0.ESC + 'OD';
|
||||
@@ -129,13 +132,16 @@ export function evaluateKeyboardEvent(
|
||||
break;
|
||||
case 39:
|
||||
// right-arrow
|
||||
if (ev.metaKey) {
|
||||
break;
|
||||
}
|
||||
if (modifiers) {
|
||||
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'C';
|
||||
// HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward
|
||||
// http://unix.stackexchange.com/a/108106
|
||||
// macOS uses different escape sequences than linux
|
||||
if (result.key === C0.ESC + '[1;3C') {
|
||||
result.key = isMac ? C0.ESC + 'f' : C0.ESC + '[1;5C';
|
||||
result.key = C0.ESC + (isMac ? 'f' : '[1;5C');
|
||||
}
|
||||
} else if (applicationCursorMode) {
|
||||
result.key = C0.ESC + 'OC';
|
||||
@@ -145,11 +151,15 @@ export function evaluateKeyboardEvent(
|
||||
break;
|
||||
case 38:
|
||||
// up-arrow
|
||||
if (ev.metaKey) {
|
||||
break;
|
||||
}
|
||||
if (modifiers) {
|
||||
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A';
|
||||
// HACK: Make Alt + up-arrow behave like Ctrl + up-arrow
|
||||
// http://unix.stackexchange.com/a/108106
|
||||
if (result.key === C0.ESC + '[1;3A') {
|
||||
// macOS uses different escape sequences than linux
|
||||
if (!isMac && result.key === C0.ESC + '[1;3A') {
|
||||
result.key = C0.ESC + '[1;5A';
|
||||
}
|
||||
} else if (applicationCursorMode) {
|
||||
@@ -160,11 +170,15 @@ export function evaluateKeyboardEvent(
|
||||
break;
|
||||
case 40:
|
||||
// down-arrow
|
||||
if (ev.metaKey) {
|
||||
break;
|
||||
}
|
||||
if (modifiers) {
|
||||
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B';
|
||||
// HACK: Make Alt + down-arrow behave like Ctrl + down-arrow
|
||||
// http://unix.stackexchange.com/a/108106
|
||||
if (result.key === C0.ESC + '[1;3B') {
|
||||
// macOS uses different escape sequences than linux
|
||||
if (!isMac && result.key === C0.ESC + '[1;3B') {
|
||||
result.key = C0.ESC + '[1;5B';
|
||||
}
|
||||
} else if (applicationCursorMode) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { assert } from 'chai';
|
||||
import { StringToUtf32, stringFromCodePoint, Utf8ToUtf32, utf32ToString } from 'common/input/TextDecoder';
|
||||
import { encode } from 'utf8';
|
||||
|
||||
|
||||
// convert UTF32 codepoints to string
|
||||
function toString(data: Uint32Array, length: number): string {
|
||||
if ((String as any).fromCodePoint) {
|
||||
@@ -214,6 +215,16 @@ describe('text encodings', () => {
|
||||
}
|
||||
assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
|
||||
});
|
||||
it('test break after 3 bytes - issue #2495', () => {
|
||||
const decoder = new Utf8ToUtf32();
|
||||
const target = new Uint32Array(5);
|
||||
const utf8Data = fromByteString('\xf0\xa0\x9c\x8e');
|
||||
let written = decoder.decode(utf8Data.slice(0, 3), target);
|
||||
assert.equal(written, 0);
|
||||
written = decoder.decode(utf8Data.slice(3), target);
|
||||
assert.equal(written, 1);
|
||||
assert(toString(target, written), '𠜎');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -194,7 +194,7 @@ export class Utf8ToUtf32 {
|
||||
target[size++] = cp;
|
||||
}
|
||||
} else {
|
||||
if (codepoint < 0x010000 || codepoint > 0x10FFFF) {
|
||||
if (cp < 0x010000 || cp > 0x10FFFF) {
|
||||
// illegal codepoint
|
||||
} else {
|
||||
target[size++] = cp;
|
||||
|
||||
@@ -12,7 +12,7 @@ import { clone } from 'common/Clone';
|
||||
// This sound is released under the Creative Commons Attribution 3.0 Unported
|
||||
// (CC BY 3.0) license. It was created by 'altemark'. No modifications have been
|
||||
// made, apart from the conversion to base64.
|
||||
export const DEFAULT_BELL_SOUND = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg==';
|
||||
export const DEFAULT_BELL_SOUND = 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq';
|
||||
|
||||
// TODO: Freeze?
|
||||
export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({
|
||||
@@ -23,6 +23,8 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({
|
||||
bellSound: DEFAULT_BELL_SOUND,
|
||||
bellStyle: 'none',
|
||||
drawBoldTextInBrightColors: true,
|
||||
fastScrollModifier: 'alt',
|
||||
fastScrollSensitivity: 5,
|
||||
fontFamily: 'courier-new, courier, monospace',
|
||||
fontSize: 15,
|
||||
fontWeight: 'normal',
|
||||
@@ -31,6 +33,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({
|
||||
letterSpacing: 0,
|
||||
logLevel: 'info',
|
||||
scrollback: 1000,
|
||||
scrollSensitivity: 1,
|
||||
screenReaderMode: false,
|
||||
macOptionIsMeta: false,
|
||||
macOptionClickForcesSelection: false,
|
||||
@@ -47,7 +50,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({
|
||||
screenKeys: false,
|
||||
cancelEvents: false,
|
||||
useFlowControl: false,
|
||||
wordSeparator: ' ()[]{}\'"'
|
||||
wordSeparator: ' ()[]{}\',:;"'
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -119,6 +122,12 @@ export class OptionsService implements IOptionsService {
|
||||
throw new Error(`${key} cannot be less than 0, value: ${value}`);
|
||||
}
|
||||
break;
|
||||
case 'fastScrollSensitivity':
|
||||
case 'scrollSensitivity':
|
||||
if (value <= 0) {
|
||||
throw new Error(`${key} cannot be less than or equal to 0, value: ${value}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -180,6 +180,8 @@ export interface IPartialTerminalOptions {
|
||||
cursorStyle?: 'block' | 'underline' | 'bar';
|
||||
disableStdin?: boolean;
|
||||
drawBoldTextInBrightColors?: boolean;
|
||||
fastScrollModifier?: 'alt' | 'ctrl' | 'shift';
|
||||
fastScrollSensitivity?: number;
|
||||
fontSize?: number;
|
||||
fontFamily?: string;
|
||||
fontWeight?: FontWeight;
|
||||
@@ -194,6 +196,7 @@ export interface IPartialTerminalOptions {
|
||||
rows?: number;
|
||||
screenReaderMode?: boolean;
|
||||
scrollback?: number;
|
||||
scrollSensitivity?: number;
|
||||
tabStopWidth?: number;
|
||||
theme?: ITheme;
|
||||
windowsMode?: boolean;
|
||||
@@ -209,6 +212,8 @@ export interface ITerminalOptions {
|
||||
cursorStyle: 'block' | 'underline' | 'bar';
|
||||
disableStdin: boolean;
|
||||
drawBoldTextInBrightColors: boolean;
|
||||
fastScrollModifier: 'alt' | 'ctrl' | 'shift' | undefined;
|
||||
fastScrollSensitivity: number;
|
||||
fontSize: number;
|
||||
fontFamily: string;
|
||||
fontWeight: FontWeight;
|
||||
@@ -223,6 +228,7 @@ export interface ITerminalOptions {
|
||||
rows: number;
|
||||
screenReaderMode: boolean;
|
||||
scrollback: number;
|
||||
scrollSensitivity: number;
|
||||
tabStopWidth: number;
|
||||
theme: ITheme;
|
||||
windowsMode: boolean;
|
||||
|
||||
@@ -33,14 +33,14 @@ export class Terminal implements ITerminalApi {
|
||||
public get onRender(): IEvent<{ start: number, end: number }> { return this._core.onRender; }
|
||||
public get onResize(): IEvent<{ cols: number, rows: number }> { return this._core.onResize; }
|
||||
|
||||
public get element(): HTMLElement { return this._core.element; }
|
||||
public get element(): HTMLElement | undefined { return this._core.element; }
|
||||
public get parser(): IParser {
|
||||
if (!this._parser) {
|
||||
this._parser = new ParserApi(this._core);
|
||||
}
|
||||
return this._parser;
|
||||
}
|
||||
public get textarea(): HTMLTextAreaElement { return this._core.textarea; }
|
||||
public get textarea(): HTMLTextAreaElement | undefined { return this._core.textarea; }
|
||||
public get rows(): number { return this._core.rows; }
|
||||
public get cols(): number { return this._core.cols; }
|
||||
public get buffer(): IBufferApi { return new BufferApiView(this._core.buffer); }
|
||||
@@ -178,8 +178,8 @@ export class Terminal implements ITerminalApi {
|
||||
|
||||
private _verifyIntegers(...values: number[]): void {
|
||||
values.forEach(value => {
|
||||
if (value % 1 !== 0) {
|
||||
throw new Error('This API does not accept floating point numbers');
|
||||
if (value === Infinity || isNaN(value) || value % 1 !== 0) {
|
||||
throw new Error('This API only accepts integers');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -103,10 +103,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
this._cursorBlinkStateManager.dispose();
|
||||
this._cursorBlinkStateManager = null;
|
||||
}
|
||||
// Request a refresh from the terminal as management of rendering is being
|
||||
// moved back to the terminal
|
||||
this._terminal.refresh(this._bufferService.buffer.y, this._bufferService.buffer.y);
|
||||
}
|
||||
// Request a refresh from the terminal as management of rendering is being
|
||||
// moved back to the terminal
|
||||
this._terminal.refresh(this._bufferService.buffer.y, this._bufferService.buffer.y);
|
||||
}
|
||||
|
||||
public onCursorMove(): void {
|
||||
@@ -148,12 +148,17 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
this._clearCursor();
|
||||
this._ctx.save();
|
||||
this._ctx.fillStyle = this._colors.cursor.css;
|
||||
this._renderBlurCursor(this._bufferService.buffer.x, viewportRelativeCursorY, this._cell);
|
||||
const cursorStyle = this._optionsService.options.cursorStyle;
|
||||
if (cursorStyle && cursorStyle !== 'block') {
|
||||
this._cursorRenderers[cursorStyle](this._bufferService.buffer.x, viewportRelativeCursorY, this._cell);
|
||||
} else {
|
||||
this._renderBlurCursor(this._bufferService.buffer.x, viewportRelativeCursorY, this._cell);
|
||||
}
|
||||
this._ctx.restore();
|
||||
this._state.x = this._bufferService.buffer.x;
|
||||
this._state.y = viewportRelativeCursorY;
|
||||
this._state.isFocused = false;
|
||||
this._state.style = this._optionsService.options.cursorStyle;
|
||||
this._state.style = cursorStyle;
|
||||
this._state.width = this._cell.getWidth();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
`}`;
|
||||
// Cursor
|
||||
styles +=
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${CURSOR_CLASS} {` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
|
||||
` outline: 1px solid ${this._colors.cursor.css};` +
|
||||
` outline-offset: -1px;` +
|
||||
`}` +
|
||||
@@ -197,10 +197,10 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
` background-color: ${this._colors.cursor.css};` +
|
||||
` color: ${this._colors.cursorAccent.css};` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BAR_CLASS} {` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BAR_CLASS} {` +
|
||||
` box-shadow: 1px 0 0 ${this._colors.cursor.css} inset;` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_UNDERLINE_CLASS} {` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_UNDERLINE_CLASS} {` +
|
||||
` box-shadow: 0 -1px 0 ${this._colors.cursor.css} inset;` +
|
||||
`}`;
|
||||
// Selection
|
||||
|
||||
Reference in New Issue
Block a user