Merge branch 'master' into fix/memory-leak
This commit is contained in:
@@ -72,6 +72,9 @@ export class MockTerminal implements ITerminal {
|
||||
public focus(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public input(data: string, wasUserInput: boolean = true): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public resize(columns: number, rows: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
+15
-2
@@ -36,6 +36,8 @@ export class Viewport extends Disposable implements IViewport {
|
||||
private _activeBuffer: IBuffer;
|
||||
private _renderDimensions: IRenderDimensions;
|
||||
|
||||
private _smoothScrollAnimationFrame: number = 0;
|
||||
|
||||
// Stores a partial line amount when scrolling, this is used to keep track of how much of a line
|
||||
// is scrolled so we can "scroll" over partial lines and feel natural on touchpads. This is a
|
||||
// quick fix and could have a more robust solution in place that reset the value when needed.
|
||||
@@ -49,6 +51,8 @@ export class Viewport extends Disposable implements IViewport {
|
||||
target: -1
|
||||
};
|
||||
|
||||
private _ensureTimeout: number;
|
||||
|
||||
private readonly _onRequestScrollLines = this.register(new EventEmitter<{ amount: number, suppressScrollEvent: boolean }>());
|
||||
public readonly onRequestScrollLines = this._onRequestScrollLines.event;
|
||||
|
||||
@@ -81,7 +85,7 @@ export class Viewport extends Disposable implements IViewport {
|
||||
this.register(this._optionsService.onSpecificOptionChange('scrollback', () => this.syncScrollArea()));
|
||||
|
||||
// Perform this async to ensure the ICharSizeService is ready.
|
||||
setTimeout(() => this.syncScrollArea());
|
||||
this._ensureTimeout = window.setTimeout(() => this.syncScrollArea());
|
||||
}
|
||||
|
||||
private _handleThemeChange(colors: ReadonlyColorSet): void {
|
||||
@@ -211,7 +215,12 @@ export class Viewport extends Disposable implements IViewport {
|
||||
|
||||
// Continue or finish smooth scroll
|
||||
if (percent < 1) {
|
||||
this._coreBrowserService.window.requestAnimationFrame(() => this._smoothScroll());
|
||||
if (!this._smoothScrollAnimationFrame) {
|
||||
this._smoothScrollAnimationFrame = this._coreBrowserService.window.requestAnimationFrame(() => {
|
||||
this._smoothScrollAnimationFrame = 0;
|
||||
this._smoothScroll();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this._clearSmoothScrollState();
|
||||
}
|
||||
@@ -398,4 +407,8 @@ export class Viewport extends Disposable implements IViewport {
|
||||
this._viewportElement.scrollTop += deltaY;
|
||||
return this._bubbleScroll(ev, deltaY);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
clearTimeout(this._ensureTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,8 +159,9 @@ export class CompositionHelper {
|
||||
// otherwise input characters can be duplicated. (Issue #3191)
|
||||
currentCompositionPosition.start += this._dataAlreadySent.length;
|
||||
if (this._isComposing) {
|
||||
// Use the end position to get the string if a new composition has started.
|
||||
input = this._textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end);
|
||||
// Use the start position of the new composition to get the string
|
||||
// if a new composition has started.
|
||||
input = this._textarea.value.substring(currentCompositionPosition.start, this._compositionPosition.start);
|
||||
} else {
|
||||
// Don't use the end position here in order to pick up any characters after the
|
||||
// composition has finished, for example when typing a non-composition character
|
||||
|
||||
@@ -20,6 +20,8 @@ import { IBufferNamespace as IBufferNamespaceApi, IDecoration, IDecorationOption
|
||||
*/
|
||||
const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows'];
|
||||
|
||||
let $value = 0;
|
||||
|
||||
export class Terminal extends Disposable implements ITerminalApi {
|
||||
private _core: ITerminal;
|
||||
private _addonManager: AddonManager;
|
||||
@@ -138,6 +140,9 @@ export class Terminal extends Disposable implements ITerminalApi {
|
||||
public focus(): void {
|
||||
this._core.focus();
|
||||
}
|
||||
public input(data: string, wasUserInput: boolean = true): void {
|
||||
this._core.input(data, wasUserInput);
|
||||
}
|
||||
public resize(columns: number, rows: number): void {
|
||||
this._verifyIntegers(columns, rows);
|
||||
this._core.resize(columns, rows);
|
||||
@@ -246,16 +251,16 @@ export class Terminal extends Disposable implements ITerminalApi {
|
||||
}
|
||||
|
||||
private _verifyIntegers(...values: number[]): void {
|
||||
for (const value of values) {
|
||||
if (value === Infinity || isNaN(value) || value % 1 !== 0) {
|
||||
for ($value of values) {
|
||||
if ($value === Infinity || isNaN($value) || $value % 1 !== 0) {
|
||||
throw new Error('This API only accepts integers');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _verifyPositiveIntegers(...values: number[]): void {
|
||||
for (const value of values) {
|
||||
if (value && (value === Infinity || isNaN(value) || value % 1 !== 0 || value < 0)) {
|
||||
for ($value of values) {
|
||||
if ($value && ($value === Infinity || isNaN($value) || $value % 1 !== 0 || $value < 0)) {
|
||||
throw new Error('This API only accepts positive integers');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,14 +183,23 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
` font-style: italic;` +
|
||||
`}`;
|
||||
// Blink animation
|
||||
const blinkAnimationUnderlineId = `blink_underline_${this._terminalClass}`;
|
||||
const blinkAnimationBarId = `blink_bar_${this._terminalClass}`;
|
||||
const blinkAnimationBlockId = `blink_block_${this._terminalClass}`;
|
||||
styles +=
|
||||
`@keyframes blink_box_shadow` + `_` + this._terminalClass + ` {` +
|
||||
`@keyframes ${blinkAnimationUnderlineId} {` +
|
||||
` 50% {` +
|
||||
` border-bottom-style: hidden;` +
|
||||
` }` +
|
||||
`}`;
|
||||
styles +=
|
||||
`@keyframes blink_block` + `_` + this._terminalClass + ` {` +
|
||||
`@keyframes ${blinkAnimationBarId} {` +
|
||||
` 50% {` +
|
||||
` box-shadow: none;` +
|
||||
` }` +
|
||||
`}`;
|
||||
styles +=
|
||||
`@keyframes ${blinkAnimationBlockId} {` +
|
||||
` 0% {` +
|
||||
` background-color: ${colors.cursor.css};` +
|
||||
` color: ${colors.cursorAccent.css};` +
|
||||
@@ -202,13 +211,23 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
`}`;
|
||||
// Cursor
|
||||
styles +=
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}:not(.${RowCss.CURSOR_STYLE_BLOCK_CLASS}) {` +
|
||||
` animation: blink_box_shadow` + `_` + this._terminalClass + ` 1s step-end infinite;` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_UNDERLINE_CLASS} {` +
|
||||
` animation: ${blinkAnimationUnderlineId} 1s step-end infinite;` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_BAR_CLASS} {` +
|
||||
` animation: ${blinkAnimationBarId} 1s step-end infinite;` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` +
|
||||
` animation: blink_block` + `_` + this._terminalClass + ` 1s step-end infinite;` +
|
||||
` animation: ${blinkAnimationBlockId} 1s step-end infinite;` +
|
||||
`}` +
|
||||
// !important helps fix an issue where the cursor will not render on top of the selection,
|
||||
// however it's very hard to fix this issue and retain the blink animation without the use of
|
||||
// !important. So this edge case fails when cursor blink is on.
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` +
|
||||
` background-color: ${colors.cursor.css};` +
|
||||
` color: ${colors.cursorAccent.css};` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS}:not(.${RowCss.CURSOR_BLINK_CLASS}) {` +
|
||||
` background-color: ${colors.cursor.css} !important;` +
|
||||
` color: ${colors.cursorAccent.css} !important;` +
|
||||
`}` +
|
||||
@@ -324,6 +343,9 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
}
|
||||
|
||||
this._selectionRenderModel.update(this._terminal, start, end, columnSelectMode);
|
||||
if (!this._selectionRenderModel.hasSelection) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Translate from buffer position to viewport position
|
||||
const viewportStartRow = this._selectionRenderModel.viewportStartRow;
|
||||
@@ -331,11 +353,6 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
const viewportCappedStartRow = this._selectionRenderModel.viewportCappedStartRow;
|
||||
const viewportCappedEndRow = this._selectionRenderModel.viewportCappedEndRow;
|
||||
|
||||
// No need to draw the selection
|
||||
if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the selections
|
||||
const documentFragment = this._document.createDocumentFragment();
|
||||
|
||||
|
||||
@@ -134,9 +134,14 @@ export class WidthCache implements IDisposable {
|
||||
public get(c: string, bold: boolean | number, italic: boolean | number): number {
|
||||
let cp = 0;
|
||||
if (!bold && !italic && c.length === 1 && (cp = c.charCodeAt(0)) < WidthCacheSettings.FLAT_SIZE) {
|
||||
return this._flat[cp] !== WidthCacheSettings.FLAT_UNSET
|
||||
? this._flat[cp]
|
||||
: (this._flat[cp] = this._measure(c, 0));
|
||||
if (this._flat[cp] !== WidthCacheSettings.FLAT_UNSET) {
|
||||
return this._flat[cp];
|
||||
}
|
||||
const width = this._measure(c, 0);
|
||||
if (width > 0) {
|
||||
this._flat[cp] = width;
|
||||
}
|
||||
return width;
|
||||
}
|
||||
let key = c;
|
||||
if (bold) key += 'B';
|
||||
@@ -147,7 +152,9 @@ export class WidthCache implements IDisposable {
|
||||
if (bold) variant |= FontVariant.BOLD;
|
||||
if (italic) variant |= FontVariant.ITALIC;
|
||||
width = this._measure(c, variant);
|
||||
this._holey!.set(key, width);
|
||||
if (width > 0) {
|
||||
this._holey!.set(key, width);
|
||||
}
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ export class CellColorResolver {
|
||||
$bg = this._themeService.colors.ansi[this.result.fg & Attributes.PCOLOR_MASK].rgba;
|
||||
break;
|
||||
case Attributes.CM_RGB:
|
||||
$bg = (this.result.fg & Attributes.RGB_MASK) << 8 | 0xFF;
|
||||
$bg = ((this.result.fg & Attributes.RGB_MASK) << 8) | 0xFF;
|
||||
break;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
@@ -105,7 +105,7 @@ export class CellColorResolver {
|
||||
$bg = this._themeService.colors.ansi[this.result.bg & Attributes.PCOLOR_MASK].rgba;
|
||||
break;
|
||||
case Attributes.CM_RGB:
|
||||
$bg = this.result.bg & Attributes.RGB_MASK << 8 | 0xFF;
|
||||
$bg = ((this.result.bg & Attributes.RGB_MASK) << 8) | 0xFF;
|
||||
break;
|
||||
// No need to consider default bg color here as it's not possible
|
||||
}
|
||||
@@ -143,7 +143,7 @@ export class CellColorResolver {
|
||||
$fg = this._themeService.colors.ansi[this.result.bg & Attributes.PCOLOR_MASK].rgba;
|
||||
break;
|
||||
case Attributes.CM_RGB:
|
||||
$fg = this.result.bg & Attributes.RGB_MASK << 8 | 0xFF;
|
||||
$fg = ((this.result.bg & Attributes.RGB_MASK) << 8) | 0xFF;
|
||||
break;
|
||||
// No need to consider default bg color here as it's not possible
|
||||
}
|
||||
@@ -154,7 +154,7 @@ export class CellColorResolver {
|
||||
$fg = this._themeService.colors.ansi[this.result.fg & Attributes.PCOLOR_MASK].rgba;
|
||||
break;
|
||||
case Attributes.CM_RGB:
|
||||
$fg = (this.result.fg & Attributes.RGB_MASK) << 8 | 0xFF;
|
||||
$fg = ((this.result.fg & Attributes.RGB_MASK) << 8) | 0xFF;
|
||||
break;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
|
||||
@@ -23,10 +23,43 @@ export function isRestrictedPowerlineGlyph(codepoint: number): boolean {
|
||||
return 0xE0B0 <= codepoint && codepoint <= 0xE0B7;
|
||||
}
|
||||
|
||||
function isNerdFontGlyph(codepoint: number): boolean {
|
||||
return 0xE000 <= codepoint && codepoint <= 0xF8FF;
|
||||
}
|
||||
|
||||
function isBoxOrBlockGlyph(codepoint: number): boolean {
|
||||
return 0x2500 <= codepoint && codepoint <= 0x259F;
|
||||
}
|
||||
|
||||
export function isEmoji(codepoint: number): boolean {
|
||||
return (
|
||||
codepoint >= 0x1F600 && codepoint <= 0x1F64F || // Emoticons
|
||||
codepoint >= 0x1F300 && codepoint <= 0x1F5FF || // Misc Symbols and Pictographs
|
||||
codepoint >= 0x1F680 && codepoint <= 0x1F6FF || // Transport and Map
|
||||
codepoint >= 0x2600 && codepoint <= 0x26FF || // Misc symbols
|
||||
codepoint >= 0x2700 && codepoint <= 0x27BF || // Dingbats
|
||||
codepoint >= 0xFE00 && codepoint <= 0xFE0F || // Variation Selectors
|
||||
codepoint >= 0x1F900 && codepoint <= 0x1F9FF || // Supplemental Symbols and Pictographs
|
||||
codepoint >= 0x1F1E6 && codepoint <= 0x1F1FF
|
||||
);
|
||||
}
|
||||
|
||||
export function allowRescaling(codepoint: number | undefined, width: number, glyphSizeX: number, deviceCellWidth: number): boolean {
|
||||
return (
|
||||
// Is single cell width
|
||||
width === 1 &&
|
||||
// Glyph exceeds cell bounds, add 50% to avoid hurting readability by rescaling glyphs that
|
||||
// barely overlap
|
||||
glyphSizeX > Math.ceil(deviceCellWidth * 1.5) &&
|
||||
// Never rescale ascii
|
||||
codepoint !== undefined && codepoint > 0xFF &&
|
||||
// Never rescale emoji
|
||||
!isEmoji(codepoint) &&
|
||||
// Never rescale powerline or nerd fonts
|
||||
!isPowerlineGlyph(codepoint) && !isNerdFontGlyph(codepoint)
|
||||
);
|
||||
}
|
||||
|
||||
export function treatGlyphAsBackgroundColor(codepoint: number): boolean {
|
||||
return isPowerlineGlyph(codepoint) || isBoxOrBlockGlyph(codepoint);
|
||||
}
|
||||
|
||||
@@ -87,7 +87,8 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
'fontSize',
|
||||
'fontWeight',
|
||||
'fontWeightBold',
|
||||
'minimumContrastRatio'
|
||||
'minimumContrastRatio',
|
||||
'rescaleOverlappingGlyphs'
|
||||
], () => {
|
||||
this.clear();
|
||||
this.handleResize(bufferService.cols, bufferService.rows);
|
||||
@@ -247,7 +248,7 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
return;
|
||||
}
|
||||
if (this._isPaused) {
|
||||
this._pausedResizeTask.set(() => this._renderer.value!.handleResize(cols, rows));
|
||||
this._pausedResizeTask.set(() => this._renderer.value?.handleResize(cols, rows));
|
||||
} else {
|
||||
this._renderer.value.handleResize(cols, rows);
|
||||
}
|
||||
|
||||
+5
-2
@@ -3,7 +3,6 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { isNode } from 'common/Platform';
|
||||
import { IColor, IColorRGB } from 'common/Types';
|
||||
|
||||
let $r = 0;
|
||||
@@ -117,9 +116,10 @@ export namespace color {
|
||||
* '#rrggbbaa').
|
||||
*/
|
||||
export namespace css {
|
||||
// Attempt to set get the shared canvas context
|
||||
let $ctx: CanvasRenderingContext2D | undefined;
|
||||
let $litmusColor: CanvasGradient | undefined;
|
||||
if (!isNode) {
|
||||
try {
|
||||
// This is guaranteed to run in the first window, so document should be correct
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 1;
|
||||
@@ -133,6 +133,9 @@ export namespace css {
|
||||
$litmusColor = $ctx.createLinearGradient(0, 0, 1, 1);
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// noop
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a css string to an IColor, this should handle all valid CSS color strings and will
|
||||
|
||||
@@ -168,6 +168,10 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
|
||||
this._writeBuffer.writeSync(data, maxSubsequentCalls);
|
||||
}
|
||||
|
||||
public input(data: string, wasUserInput: boolean = true): void {
|
||||
this.coreService.triggerDataEvent(data, wasUserInput);
|
||||
}
|
||||
|
||||
public resize(x: number, y: number): void {
|
||||
if (isNaN(x) || isNaN(y)) {
|
||||
return;
|
||||
|
||||
@@ -2979,7 +2979,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
if (args[1]) {
|
||||
return this._createHyperlink(args[0], args[1]);
|
||||
}
|
||||
if (args[0]) {
|
||||
if (args[0].trim()) {
|
||||
return false;
|
||||
}
|
||||
return this._finishHyperlink();
|
||||
|
||||
@@ -14,7 +14,7 @@ interface INavigator {
|
||||
declare const navigator: INavigator;
|
||||
declare const process: unknown;
|
||||
|
||||
export const isNode = (typeof process !== 'undefined') ? true : false;
|
||||
export const isNode = (typeof process !== 'undefined' && 'title' in (process as any)) ? true : false;
|
||||
const userAgent = (isNode) ? 'node' : navigator.userAgent;
|
||||
const platform = (isNode) ? 'node' : navigator.platform;
|
||||
|
||||
|
||||
+86
-10
@@ -3,16 +3,27 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IdleTaskQueue } from 'common/TaskQueue';
|
||||
|
||||
// Work variables to avoid garbage collection.
|
||||
let i = 0;
|
||||
|
||||
/**
|
||||
* A generic list that is maintained in sorted order and allows values with duplicate keys. This
|
||||
* list is based on binary search and as such locating a key will take O(log n) amortized, this
|
||||
* includes the by key iterator.
|
||||
* A generic list that is maintained in sorted order and allows values with duplicate keys. Deferred
|
||||
* batch insertion and deletion is used to significantly reduce the time it takes to insert and
|
||||
* delete a large amount of items in succession. This list is based on binary search and as such
|
||||
* locating a key will take O(log n) amortized, this includes the by key iterator.
|
||||
*/
|
||||
export class SortedList<T> {
|
||||
private readonly _array: T[] = [];
|
||||
private _array: T[] = [];
|
||||
|
||||
private readonly _insertedValues: T[] = [];
|
||||
private readonly _flushInsertedTask = new IdleTaskQueue();
|
||||
private _isFlushingInserted = false;
|
||||
|
||||
private readonly _deletedIndices: number[] = [];
|
||||
private readonly _flushDeletedTask = new IdleTaskQueue();
|
||||
private _isFlushingDeleted = false;
|
||||
|
||||
constructor(
|
||||
private readonly _getKey: (value: T) => number
|
||||
@@ -21,18 +32,50 @@ export class SortedList<T> {
|
||||
|
||||
public clear(): void {
|
||||
this._array.length = 0;
|
||||
this._insertedValues.length = 0;
|
||||
this._flushInsertedTask.clear();
|
||||
this._isFlushingInserted = false;
|
||||
this._deletedIndices.length = 0;
|
||||
this._flushDeletedTask.clear();
|
||||
this._isFlushingDeleted = false;
|
||||
}
|
||||
|
||||
public insert(value: T): void {
|
||||
if (this._array.length === 0) {
|
||||
this._array.push(value);
|
||||
return;
|
||||
this._flushCleanupDeleted();
|
||||
if (this._insertedValues.length === 0) {
|
||||
this._flushInsertedTask.enqueue(() => this._flushInserted());
|
||||
}
|
||||
this._insertedValues.push(value);
|
||||
}
|
||||
|
||||
private _flushInserted(): void {
|
||||
const sortedAddedValues = this._insertedValues.sort((a, b) => this._getKey(a) - this._getKey(b));
|
||||
let sortedAddedValuesIndex = 0;
|
||||
let arrayIndex = 0;
|
||||
|
||||
const newArray = new Array(this._array.length + this._insertedValues.length);
|
||||
|
||||
for (let newArrayIndex = 0; newArrayIndex < newArray.length; newArrayIndex++) {
|
||||
if (arrayIndex >= this._array.length || this._getKey(sortedAddedValues[sortedAddedValuesIndex]) <= this._getKey(this._array[arrayIndex])) {
|
||||
newArray[newArrayIndex] = sortedAddedValues[sortedAddedValuesIndex];
|
||||
sortedAddedValuesIndex++;
|
||||
} else {
|
||||
newArray[newArrayIndex] = this._array[arrayIndex++];
|
||||
}
|
||||
}
|
||||
|
||||
this._array = newArray;
|
||||
this._insertedValues.length = 0;
|
||||
}
|
||||
|
||||
private _flushCleanupInserted(): void {
|
||||
if (!this._isFlushingInserted && this._insertedValues.length > 0) {
|
||||
this._flushInsertedTask.flush();
|
||||
}
|
||||
i = this._search(this._getKey(value));
|
||||
this._array.splice(i, 0, value);
|
||||
}
|
||||
|
||||
public delete(value: T): boolean {
|
||||
this._flushCleanupInserted();
|
||||
if (this._array.length === 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -49,14 +92,43 @@ export class SortedList<T> {
|
||||
}
|
||||
do {
|
||||
if (this._array[i] === value) {
|
||||
this._array.splice(i, 1);
|
||||
if (this._deletedIndices.length === 0) {
|
||||
this._flushDeletedTask.enqueue(() => this._flushDeleted());
|
||||
}
|
||||
this._deletedIndices.push(i);
|
||||
return true;
|
||||
}
|
||||
} while (++i < this._array.length && this._getKey(this._array[i]) === key);
|
||||
return false;
|
||||
}
|
||||
|
||||
private _flushDeleted(): void {
|
||||
this._isFlushingDeleted = true;
|
||||
const sortedDeletedIndices = this._deletedIndices.sort((a, b) => a - b);
|
||||
let sortedDeletedIndicesIndex = 0;
|
||||
const newArray = new Array(this._array.length - sortedDeletedIndices.length);
|
||||
let newArrayIndex = 0;
|
||||
for (let i = 0; i < this._array.length; i++) {
|
||||
if (sortedDeletedIndices[sortedDeletedIndicesIndex] === i) {
|
||||
sortedDeletedIndicesIndex++;
|
||||
} else {
|
||||
newArray[newArrayIndex++] = this._array[i];
|
||||
}
|
||||
}
|
||||
this._array = newArray;
|
||||
this._deletedIndices.length = 0;
|
||||
this._isFlushingDeleted = false;
|
||||
}
|
||||
|
||||
private _flushCleanupDeleted(): void {
|
||||
if (!this._isFlushingDeleted && this._deletedIndices.length > 0) {
|
||||
this._flushDeletedTask.flush();
|
||||
}
|
||||
}
|
||||
|
||||
public *getKeyIterator(key: number): IterableIterator<T> {
|
||||
this._flushCleanupInserted();
|
||||
this._flushCleanupDeleted();
|
||||
if (this._array.length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -73,6 +145,8 @@ export class SortedList<T> {
|
||||
}
|
||||
|
||||
public forEachByKey(key: number, callback: (value: T) => void): void {
|
||||
this._flushCleanupInserted();
|
||||
this._flushCleanupDeleted();
|
||||
if (this._array.length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -89,6 +163,8 @@ export class SortedList<T> {
|
||||
}
|
||||
|
||||
public values(): IterableIterator<T> {
|
||||
this._flushCleanupInserted();
|
||||
this._flushCleanupDeleted();
|
||||
// Duplicate the array to avoid issues when _array changes while iterating
|
||||
return [...this._array].values();
|
||||
}
|
||||
|
||||
@@ -45,7 +45,8 @@ export class DecorationService extends Disposable implements IDecorationService
|
||||
const decoration = new Decoration(options);
|
||||
if (decoration) {
|
||||
const markerDispose = decoration.marker.onDispose(() => decoration.dispose());
|
||||
decoration.onDispose(() => {
|
||||
const listener = decoration.onDispose(() => {
|
||||
listener.dispose();
|
||||
if (decoration) {
|
||||
if (this._decorations.delete(decoration)) {
|
||||
this._onDecorationRemoved.fire(decoration);
|
||||
|
||||
@@ -44,6 +44,7 @@ export const DEFAULT_OPTIONS: Readonly<Required<ITerminalOptions>> = {
|
||||
allowTransparency: false,
|
||||
tabStopWidth: 8,
|
||||
theme: {},
|
||||
rescaleOverlappingGlyphs: false,
|
||||
rightClickSelectsWord: isMac,
|
||||
windowOptions: {},
|
||||
windowsMode: false,
|
||||
|
||||
@@ -234,6 +234,7 @@ export interface ITerminalOptions {
|
||||
macOptionIsMeta?: boolean;
|
||||
macOptionClickForcesSelection?: boolean;
|
||||
minimumContrastRatio?: number;
|
||||
rescaleOverlappingGlyphs?: boolean;
|
||||
rightClickSelectsWord?: boolean;
|
||||
rows?: number;
|
||||
screenReaderMode?: boolean;
|
||||
|
||||
@@ -81,6 +81,10 @@ export class Terminal extends CoreTerminal {
|
||||
this._onBell.fire();
|
||||
}
|
||||
|
||||
public input(data: string, wasUserInput: boolean = true): void {
|
||||
this.coreService.triggerDataEvent(data, wasUserInput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the terminal.
|
||||
*
|
||||
|
||||
@@ -80,6 +80,7 @@ export class Terminal extends Disposable implements ITerminalApi {
|
||||
public get onResize(): IEvent<{ cols: number, rows: number }> { return this._core.onResize; }
|
||||
public get onScroll(): IEvent<number> { return this._core.onScroll; }
|
||||
public get onTitleChange(): IEvent<string> { return this._core.onTitleChange; }
|
||||
public get onWriteParsed(): IEvent<void> { return this._core.onWriteParsed; }
|
||||
|
||||
public get parser(): IParser {
|
||||
this._checkProposedApi();
|
||||
@@ -134,6 +135,9 @@ export class Terminal extends Disposable implements ITerminalApi {
|
||||
this._publicOptions[propName] = options[propName];
|
||||
}
|
||||
}
|
||||
public input(data: string, wasUserInput: boolean = true): void {
|
||||
this._core.input(data, wasUserInput);
|
||||
}
|
||||
public resize(columns: number, rows: number): void {
|
||||
this._verifyIntegers(columns, rows);
|
||||
this._core.resize(columns, rows);
|
||||
|
||||
Reference in New Issue
Block a user