Merge remote-tracking branch 'upstream/master' into pr/jerch/3524

This commit is contained in:
Daniel Imms
2021-12-22 08:28:43 -08:00
24 changed files with 240 additions and 66 deletions
+8 -3
View File
@@ -22,12 +22,12 @@
*/
import { Disposable } from 'common/Lifecycle';
import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService, LogLevelEnum } from 'common/services/Services';
import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService, LogLevelEnum, ITerminalOptions } from 'common/services/Services';
import { InstantiationService } from 'common/services/InstantiationService';
import { LogService } from 'common/services/LogService';
import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService';
import { OptionsService } from 'common/services/OptionsService';
import { ITerminalOptions, IDisposable, IBufferLine, IAttributeData, ICoreTerminal, IKeyboardEvent, IScrollEvent, ScrollSource } from 'common/Types';
import { IDisposable, IBufferLine, IAttributeData, ICoreTerminal, IKeyboardEvent, IScrollEvent, ScrollSource, ITerminalOptions as IPublicTerminalOptions } from 'common/Types';
import { CoreService } from 'common/services/CoreService';
import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
import { CoreMouseService } from 'common/services/CoreMouseService';
@@ -86,7 +86,12 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
public get cols(): number { return this._bufferService.cols; }
public get rows(): number { return this._bufferService.rows; }
public get buffers(): IBufferSet { return this._bufferService.buffers; }
public get options(): ITerminalOptions { return this.optionsService.publicOptions; }
public get options(): ITerminalOptions { return this.optionsService.options; }
public set options(options: ITerminalOptions) {
for (const key in options) {
this.optionsService.options[key] = options[key];
}
}
constructor(
options: Partial<ITerminalOptions>
+50
View File
@@ -385,6 +385,56 @@ describe('InputHandler', () => {
assert.equal(bufferService.buffer.lines.get(2)!.translateToString(false), Array(bufferService.cols + 1).join(' '));
});
it('eraseInLine reflow', async () => {
const bufferService = new MockBufferService(80, 30);
const inputHandler = new TestInputHandler(
bufferService,
new MockCharsetService(),
new MockCoreService(),
new MockDirtyRowService(),
new MockLogService(),
new MockOptionsService(),
new MockCoreMouseService(),
new MockUnicodeService()
);
const resetToBaseState = async (): Promise<void> => {
// reset and add a wrapped line
bufferService.buffer.y = 0;
bufferService.buffer.x = 0;
await inputHandler.parseP(Array(bufferService.cols + 1).join('a')); // line 0
await inputHandler.parseP(Array(bufferService.cols + 10).join('a')); // line 1 and 2
for (let i = 3; i < bufferService.rows; ++i) await inputHandler.parseP(Array(bufferService.cols + 1).join('a'));
// confirm precondition that line 2 is wrapped
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);
};
// params[0] - erase from the cursor through the end of the row.
await resetToBaseState();
bufferService.buffer.y = 2;
bufferService.buffer.x = 40;
inputHandler.eraseInLine(Params.fromArray([0]));
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);
bufferService.buffer.y = 2;
bufferService.buffer.x = 0;
inputHandler.eraseInLine(Params.fromArray([0]));
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false);
// params[1] - erase from the beginning of the line through the cursor
await resetToBaseState();
bufferService.buffer.y = 2;
bufferService.buffer.x = 40;
inputHandler.eraseInLine(Params.fromArray([1]));
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);
// params[2] - erase complete line
await resetToBaseState();
bufferService.buffer.y = 2;
bufferService.buffer.x = 40;
inputHandler.eraseInLine(Params.fromArray([2]));
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false);
});
it('eraseInDisplay', async () => {
const bufferService = new MockBufferService(80, 7);
const inputHandler = new TestInputHandler(
+4 -3
View File
@@ -1202,6 +1202,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* @param y row index
* @param start first cell index to be erased
* @param end end - 1 is last erased cell
* @param cleanWrap clear the isWrapped flag
*/
private _eraseInBufferLine(y: number, start: number, end: number, clearWrap: boolean = false): void {
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
@@ -1327,13 +1328,13 @@ export class InputHandler extends Disposable implements IInputHandler {
this._restrictCursor(this._bufferService.cols);
switch (params.params[0]) {
case 0:
this._eraseInBufferLine(this._activeBuffer.y, this._activeBuffer.x, this._bufferService.cols);
this._eraseInBufferLine(this._activeBuffer.y, this._activeBuffer.x, this._bufferService.cols, this._activeBuffer.x === 0);
break;
case 1:
this._eraseInBufferLine(this._activeBuffer.y, 0, this._activeBuffer.x + 1);
this._eraseInBufferLine(this._activeBuffer.y, 0, this._activeBuffer.x + 1, false);
break;
case 2:
this._eraseInBufferLine(this._activeBuffer.y, 0, this._bufferService.cols);
this._eraseInBufferLine(this._activeBuffer.y, 0, this._bufferService.cols, true);
break;
}
this._dirtyRowService.markDirty(this._activeBuffer.y);
+5 -2
View File
@@ -121,16 +121,19 @@ export class MockLogService implements ILogService {
export class MockOptionsService implements IOptionsService {
public serviceBrand: any;
public options: ITerminalOptions = clone(DEFAULT_OPTIONS);
public publicOptions: ITerminalOptions = clone(DEFAULT_OPTIONS);
public onOptionChange: IEvent<string> = new EventEmitter<string>().event;
constructor(testOptions?: Partial<ITerminalOptions>) {
if (testOptions) {
for (const key of Object.keys(testOptions)) {
this.options[key] = testOptions[key];
this.publicOptions[key] = testOptions[key];
}
}
}
public setOptions(options: ITerminalOptions): void {
for (const key of Object.keys(options)) {
this.options[key] = options[key];
}
}
public setOption<T>(key: string, value: T): void {
throw new Error('Method not implemented.');
}
+1
View File
@@ -46,6 +46,7 @@ export interface IKeyboardEvent {
ctrlKey: boolean;
shiftKey: boolean;
metaKey: boolean;
/** @deprecated See KeyboardEvent.keyCode */
keyCode: number;
key: string;
type: string;
+4 -18
View File
@@ -57,17 +57,11 @@ export const DEFAULT_OPTIONS: Readonly<ITerminalOptions> = {
const FONT_WEIGHT_OPTIONS: Extract<FontWeight, string>[] = ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'];
/**
* The set of options that only have an effect when set in the Terminal constructor.
*/
const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows'];
export class OptionsService implements IOptionsService {
public serviceBrand: any;
private _options: ITerminalOptions;
public options: ITerminalOptions;
public publicOptions: ITerminalOptions;
private _onOptionChange = new EventEmitter<string>();
public get onOptionChange(): IEvent<string> { return this._onOptionChange.event; }
@@ -87,11 +81,10 @@ export class OptionsService implements IOptionsService {
}
// set up getters and setters for each option
this.options = this._setupOptions(this._options, false);
this.publicOptions = this._setupOptions(this._options, true);
this.options = this._setupOptions(this._options);
}
private _setupOptions(options: ITerminalOptions, isPublic: boolean): ITerminalOptions {
private _setupOptions(options: ITerminalOptions): ITerminalOptions {
const copiedOptions = { ... options };
for (const propName in copiedOptions) {
Object.defineProperty(copiedOptions, propName, {
@@ -106,13 +99,6 @@ export class OptionsService implements IOptionsService {
throw new Error(`No option with key "${propName}"`);
}
// Throw an error if any constructor only option is modified
// from terminal.options
// Modifications from anywhere else are allowed
if (isPublic && CONSTRUCTOR_ONLY_OPTIONS.includes(propName)) {
throw new Error(`Option "${propName}" can only be set in the constructor`);
}
value = this._sanitizeAndValidateOption(propName, value);
// Don't fire an option change event if they didn't change
if (this._options[propName] !== value) {
@@ -126,7 +112,7 @@ export class OptionsService implements IOptionsService {
}
public setOption(key: string, value: any): void {
this.publicOptions[key] = value;
this.options[key] = value;
}
private _sanitizeAndValidateOption(key: string, value: any): any {
@@ -181,6 +167,6 @@ export class OptionsService implements IOptionsService {
}
public getOption(key: string): any {
return this.publicOptions[key];
return this.options[key];
}
}
+10 -9
View File
@@ -164,6 +164,14 @@ export interface IInstantiationService {
createInstance<Ctor extends new (...args: any[]) => any, R extends InstanceType<Ctor>>(t: Ctor, ...args: GetLeadingNonServiceArgs<ConstructorParameters<Ctor>>): R;
}
export enum LogLevelEnum {
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3,
OFF = 4
}
export const ILogService = createDecorator<ILogService>('LogService');
export interface ILogService {
serviceBrand: undefined;
@@ -181,7 +189,6 @@ export interface IOptionsService {
serviceBrand: undefined;
readonly options: ITerminalOptions;
readonly publicOptions: ITerminalOptions;
readonly onOptionChange: IEvent<string>;
@@ -191,13 +198,7 @@ export interface IOptionsService {
export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number;
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'off';
export enum LogLevelEnum {
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3,
OFF = 4
}
export type RendererType = 'dom' | 'canvas';
export interface ITerminalOptions {
@@ -207,6 +208,7 @@ export interface ITerminalOptions {
bellSound: string;
bellStyle: 'none' | 'sound' /* | 'visual' | 'both' */;
cols: number;
convertEol: boolean;
cursorBlink: boolean;
cursorStyle: 'block' | 'underline' | 'bar';
cursorWidth: number;
@@ -240,7 +242,6 @@ export interface ITerminalOptions {
[key: string]: any;
cancelEvents: boolean;
convertEol: boolean;
termName: string;
}