mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Move SelectionService into browser (+strict)
This commit is contained in:
@@ -548,10 +548,12 @@ describe('Terminal', () => {
|
||||
});
|
||||
|
||||
describe('with macOptionIsMeta', () => {
|
||||
let originalIsMac: boolean;
|
||||
beforeEach(() => {
|
||||
term.browser.isMac = true;
|
||||
originalIsMac = term.browser.isMac;
|
||||
term.options.macOptionIsMeta = true;
|
||||
});
|
||||
afterEach(() => term.browser.isMac = originalIsMac);
|
||||
|
||||
it('should interfere with the alt key on keyDown', () => {
|
||||
evKeyDown.altKey = true;
|
||||
@@ -564,9 +566,12 @@ describe('Terminal', () => {
|
||||
});
|
||||
|
||||
describe('On Mac OS', () => {
|
||||
let originalIsMac: boolean;
|
||||
beforeEach(() => {
|
||||
originalIsMac = term.browser.isMac;
|
||||
term.browser.isMac = true;
|
||||
});
|
||||
afterEach(() => term.browser.isMac = originalIsMac);
|
||||
|
||||
it('should not interfere with the alt key on keyDown', () => {
|
||||
evKeyDown.altKey = true;
|
||||
@@ -627,9 +632,12 @@ describe('Terminal', () => {
|
||||
});
|
||||
|
||||
describe('On MS Windows', () => {
|
||||
let originalIsWindows: boolean;
|
||||
beforeEach(() => {
|
||||
originalIsWindows = term.browser.isMSWindows;
|
||||
term.browser.isMSWindows = true;
|
||||
});
|
||||
afterEach(() => term.browser.isMSWindows = originalIsWindows);
|
||||
|
||||
it('should not interfere with the alt + ctrl key on keyDown', () => {
|
||||
evKeyPress.altKey = true;
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ import { C0 } from 'common/data/EscapeSequences';
|
||||
import { InputHandler } from './InputHandler';
|
||||
import { Renderer } from './renderer/Renderer';
|
||||
import { Linkifier } from './Linkifier';
|
||||
import { SelectionService } from './SelectionService';
|
||||
import { SelectionService } from './browser/services/SelectionService';
|
||||
import * as Browser from 'common/Platform';
|
||||
import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import * as Strings from './browser/LocalizableStrings';
|
||||
|
||||
Vendored
+2
-2
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
export interface ISelectionRedrawRequestEvent {
|
||||
start: [number, number];
|
||||
end: [number, number];
|
||||
start: [number, number] | undefined;
|
||||
end: [number, number] | undefined;
|
||||
columnSelectMode: boolean;
|
||||
}
|
||||
|
||||
@@ -13,13 +13,14 @@ import { IBufferService, IOptionsService } from 'common/services/Services';
|
||||
import { MockCharSizeService, MockMouseService } from 'browser/TestUtils.test';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { IBuffer } from 'common/buffer/Types';
|
||||
import { isMSWindows } from '../../../out/common/Platform';
|
||||
|
||||
class TestSelectionService extends SelectionService {
|
||||
constructor(
|
||||
bufferService: IBufferService,
|
||||
optionsService: IOptionsService
|
||||
) {
|
||||
super(() => {}, null, null, new MockCharSizeService(10, 10), bufferService, new MockCoreService(), new MockMouseService(), optionsService);
|
||||
super(() => {}, null!, null!, new MockCharSizeService(10, 10), bufferService, new MockCoreService(), new MockMouseService(), optionsService);
|
||||
}
|
||||
|
||||
public get model(): SelectionModel { return this._model; }
|
||||
@@ -97,21 +98,21 @@ describe('SelectionService', () => {
|
||||
it('should expand selection for wide characters', () => {
|
||||
// Wide characters use a special format
|
||||
const data: [number, string, number, number][] = [
|
||||
[null, '中', 2, '中'.charCodeAt(0)],
|
||||
[null, '', 0, null],
|
||||
[null, '文', 2, '文'.charCodeAt(0)],
|
||||
[null, '', 0, null],
|
||||
[null, ' ', 1, ' '.charCodeAt(0)],
|
||||
[null, 'a', 1, 'a'.charCodeAt(0)],
|
||||
[null, '中', 2, '中'.charCodeAt(0)],
|
||||
[null, '', 0, null],
|
||||
[null, '文', 2, '文'.charCodeAt(0)],
|
||||
[null, '', 0, ''.charCodeAt(0)],
|
||||
[null, 'b', 1, 'b'.charCodeAt(0)],
|
||||
[null, ' ', 1, ' '.charCodeAt(0)],
|
||||
[null, 'f', 1, 'f'.charCodeAt(0)],
|
||||
[null, 'o', 1, 'o'.charCodeAt(0)],
|
||||
[null, 'o', 1, 'o'.charCodeAt(0)]
|
||||
[0, '中', 2, '中'.charCodeAt(0)],
|
||||
[0, '', 0, 0],
|
||||
[0, '文', 2, '文'.charCodeAt(0)],
|
||||
[0, '', 0, 0],
|
||||
[0, ' ', 1, ' '.charCodeAt(0)],
|
||||
[0, 'a', 1, 'a'.charCodeAt(0)],
|
||||
[0, '中', 2, '中'.charCodeAt(0)],
|
||||
[0, '', 0, 0],
|
||||
[0, '文', 2, '文'.charCodeAt(0)],
|
||||
[0, '', 0, ''.charCodeAt(0)],
|
||||
[0, 'b', 1, 'b'.charCodeAt(0)],
|
||||
[0, ' ', 1, ' '.charCodeAt(0)],
|
||||
[0, 'f', 1, 'f'.charCodeAt(0)],
|
||||
[0, 'o', 1, 'o'.charCodeAt(0)],
|
||||
[0, 'o', 1, 'o'.charCodeAt(0)]
|
||||
];
|
||||
const line = new BufferLine(data.length);
|
||||
for (let i = 0; i < data.length; ++i) line.setCell(i, CellData.fromCharData(data[i]));
|
||||
@@ -188,7 +189,7 @@ describe('SelectionService', () => {
|
||||
it('should expand upwards or downards for wrapped lines', () => {
|
||||
buffer.lines.set(0, stringToRow(' foo'));
|
||||
buffer.lines.set(1, stringToRow('bar '));
|
||||
buffer.lines.get(1).isWrapped = true;
|
||||
buffer.lines.get(1)!.isWrapped = true;
|
||||
selectionService.selectWordAt([1, 1]);
|
||||
assert.equal(selectionService.selectionText, 'foobar');
|
||||
selectionService.model.clearSelection();
|
||||
@@ -202,10 +203,10 @@ describe('SelectionService', () => {
|
||||
buffer.lines.set(2, stringToRow('bbbbbbbbbbbbbbbbbbbb'));
|
||||
buffer.lines.set(3, stringToRow('cccccccccccccccccccc'));
|
||||
buffer.lines.set(4, stringToRow('bar '));
|
||||
buffer.lines.get(1).isWrapped = true;
|
||||
buffer.lines.get(2).isWrapped = true;
|
||||
buffer.lines.get(3).isWrapped = true;
|
||||
buffer.lines.get(4).isWrapped = true;
|
||||
buffer.lines.get(1)!.isWrapped = true;
|
||||
buffer.lines.get(2)!.isWrapped = true;
|
||||
buffer.lines.get(3)!.isWrapped = true;
|
||||
buffer.lines.get(4)!.isWrapped = true;
|
||||
selectionService.selectWordAt([18, 0]);
|
||||
assert.equal(selectionService.selectionText, expectedText);
|
||||
selectionService.model.clearSelection();
|
||||
@@ -359,6 +360,8 @@ describe('SelectionService', () => {
|
||||
buffer.lines.set(3, stringToRow('4'));
|
||||
buffer.lines.set(4, stringToRow('5'));
|
||||
selectionService.selectAll();
|
||||
console.log(selectionService.selectionText.length);
|
||||
console.log(isMSWindows);
|
||||
assert.equal(selectionService.selectionText, '1\n2\n3\n4\n5');
|
||||
});
|
||||
});
|
||||
@@ -73,7 +73,7 @@ export class SelectionService implements ISelectionService {
|
||||
* The amount to scroll every drag scroll update (depends on how far the mouse
|
||||
* drag is above or below the terminal).
|
||||
*/
|
||||
private _dragScrollAmount: number;
|
||||
private _dragScrollAmount: number = 0;
|
||||
|
||||
/**
|
||||
* The current selection mode.
|
||||
@@ -84,12 +84,12 @@ export class SelectionService implements ISelectionService {
|
||||
* A setInterval timer that is active while the mouse is down whose callback
|
||||
* scrolls the viewport when necessary.
|
||||
*/
|
||||
private _dragScrollIntervalTimer: NodeJS.Timer;
|
||||
private _dragScrollIntervalTimer: number | undefined;
|
||||
|
||||
/**
|
||||
* The animation frame ID used for refreshing the selection.
|
||||
*/
|
||||
private _refreshAnimationFrame: number;
|
||||
private _refreshAnimationFrame: number | undefined;
|
||||
|
||||
/**
|
||||
* Whether selection is enabled.
|
||||
@@ -101,7 +101,7 @@ export class SelectionService implements ISelectionService {
|
||||
private _trimListener: IDisposable;
|
||||
private _workCell: CellData = new CellData();
|
||||
|
||||
private _mouseDownTimeStamp: number;
|
||||
private _mouseDownTimeStamp: number = 0;
|
||||
|
||||
private _onLinuxMouseSelection = new EventEmitter<string>();
|
||||
public get onLinuxMouseSelection(): IEvent<string> { return this._onLinuxMouseSelection.event; }
|
||||
@@ -120,7 +120,17 @@ export class SelectionService implements ISelectionService {
|
||||
private readonly _mouseService: IMouseService,
|
||||
private readonly _optionsService: IOptionsService
|
||||
) {
|
||||
this._initListeners();
|
||||
// Init listeners
|
||||
this._mouseMoveListener = event => this._onMouseMove(<MouseEvent>event);
|
||||
this._mouseUpListener = event => this._onMouseUp(<MouseEvent>event);
|
||||
this._coreService.onUserInput(() => {
|
||||
if (this.hasSelection) {
|
||||
this.clearSelection();
|
||||
}
|
||||
});
|
||||
this._trimListener = this._bufferService.buffer.lines.onTrim(amount => this._onTrim(amount));
|
||||
this._bufferService.buffers.onBufferActivate(e => this._onBufferActivate(e));
|
||||
|
||||
this.enable();
|
||||
|
||||
this._model = new SelectionModel(this._bufferService);
|
||||
@@ -131,29 +141,10 @@ export class SelectionService implements ISelectionService {
|
||||
this._removeMouseDownListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes listener variables.
|
||||
*/
|
||||
private _initListeners(): void {
|
||||
this._mouseMoveListener = event => this._onMouseMove(<MouseEvent>event);
|
||||
this._mouseUpListener = event => this._onMouseUp(<MouseEvent>event);
|
||||
this._coreService.onUserInput(() => {
|
||||
if (this.hasSelection) {
|
||||
this.clearSelection();
|
||||
}
|
||||
});
|
||||
this.initBuffersListeners();
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this.clearSelection();
|
||||
}
|
||||
|
||||
public initBuffersListeners(): void {
|
||||
this._trimListener = this._bufferService.buffer.lines.onTrim(amount => this._onTrim(amount));
|
||||
this._bufferService.buffers.onBufferActivate(e => this._onBufferActivate(e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the selection manager. This is useful for when terminal mouse
|
||||
* are enabled.
|
||||
@@ -170,8 +161,8 @@ export class SelectionService implements ISelectionService {
|
||||
this._enabled = true;
|
||||
}
|
||||
|
||||
public get selectionStart(): [number, number] { return this._model.finalSelectionStart; }
|
||||
public get selectionEnd(): [number, number] { return this._model.finalSelectionEnd; }
|
||||
public get selectionStart(): [number, number] | undefined { return this._model.finalSelectionStart; }
|
||||
public get selectionEnd(): [number, number] | undefined { return this._model.finalSelectionEnd; }
|
||||
|
||||
/**
|
||||
* Gets whether there is an active text selection.
|
||||
@@ -217,7 +208,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!.isWrapped) {
|
||||
result[result.length - 1] += lineText;
|
||||
} else {
|
||||
result.push(lineText);
|
||||
@@ -228,7 +219,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!.isWrapped) {
|
||||
result[result.length - 1] += lineText;
|
||||
} else {
|
||||
result.push(lineText);
|
||||
@@ -281,7 +272,7 @@ export class SelectionService implements ISelectionService {
|
||||
* selection state.
|
||||
*/
|
||||
private _refresh(): void {
|
||||
this._refreshAnimationFrame = null;
|
||||
this._refreshAnimationFrame = undefined;
|
||||
this._onRedrawRequest.fire({
|
||||
start: this._model.finalSelectionStart,
|
||||
end: this._model.finalSelectionEnd,
|
||||
@@ -298,7 +289,7 @@ export class SelectionService implements ISelectionService {
|
||||
const start = this._model.finalSelectionStart;
|
||||
const end = this._model.finalSelectionEnd;
|
||||
|
||||
if (!start || !end) {
|
||||
if (!start || !end || !coords) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -320,7 +311,7 @@ export class SelectionService implements ISelectionService {
|
||||
const coords = this._getMouseBufferCoords(event);
|
||||
if (coords) {
|
||||
this._selectWordAt(coords, false);
|
||||
this._model.selectionEnd = null;
|
||||
this._model.selectionEnd = undefined;
|
||||
this.refresh(true);
|
||||
}
|
||||
}
|
||||
@@ -359,10 +350,10 @@ export class SelectionService implements ISelectionService {
|
||||
* Gets the 0-based [x, y] buffer coordinates of the current mouse event.
|
||||
* @param event The mouse event.
|
||||
*/
|
||||
private _getMouseBufferCoords(event: MouseEvent): [number, number] {
|
||||
private _getMouseBufferCoords(event: MouseEvent): [number, number] | undefined {
|
||||
const coords = this._mouseService.getCoords(event, this._screenElement, this._bufferService.cols, this._bufferService.rows, true);
|
||||
if (!coords) {
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Convert to 0-based
|
||||
@@ -461,9 +452,11 @@ export class SelectionService implements ISelectionService {
|
||||
*/
|
||||
private _addMouseDownListeners(): void {
|
||||
// Listen on the document so that dragging outside of viewport works
|
||||
this._screenElement.ownerDocument.addEventListener('mousemove', this._mouseMoveListener);
|
||||
this._screenElement.ownerDocument.addEventListener('mouseup', this._mouseUpListener);
|
||||
this._dragScrollIntervalTimer = setInterval(() => this._dragScroll(), DRAG_SCROLL_INTERVAL);
|
||||
if (this._screenElement.ownerDocument) {
|
||||
this._screenElement.ownerDocument.addEventListener('mousemove', this._mouseMoveListener);
|
||||
this._screenElement.ownerDocument.addEventListener('mouseup', this._mouseUpListener);
|
||||
}
|
||||
this._dragScrollIntervalTimer = window.setInterval(() => this._dragScroll(), DRAG_SCROLL_INTERVAL);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -475,7 +468,7 @@ export class SelectionService implements ISelectionService {
|
||||
this._screenElement.ownerDocument.removeEventListener('mouseup', this._mouseUpListener);
|
||||
}
|
||||
clearInterval(this._dragScrollIntervalTimer);
|
||||
this._dragScrollIntervalTimer = null;
|
||||
this._dragScrollIntervalTimer = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -504,7 +497,7 @@ export class SelectionService implements ISelectionService {
|
||||
if (!this._model.selectionStart) {
|
||||
return;
|
||||
}
|
||||
this._model.selectionEnd = null;
|
||||
this._model.selectionEnd = undefined;
|
||||
|
||||
// Ensure the line exists
|
||||
const line = this._bufferService.buffer.lines.get(this._model.selectionStart[1]);
|
||||
@@ -568,6 +561,11 @@ export class SelectionService implements ISelectionService {
|
||||
// to be sent to the pty.
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
// Something went wrong
|
||||
if (!this._model.selectionStart) {
|
||||
throw new Error('Selection start position was not set before mousemove event');
|
||||
}
|
||||
|
||||
// Record the previous position so we know whether to redraw the selection
|
||||
// at the end.
|
||||
const previousSelectionEnd = this._model.selectionEnd ? [this._model.selectionEnd[0], this._model.selectionEnd[1]] : null;
|
||||
@@ -609,7 +607,8 @@ export class SelectionService implements ISelectionService {
|
||||
// have a character.
|
||||
const buffer = this._bufferService.buffer;
|
||||
if (this._model.selectionEnd[1] < buffer.lines.length) {
|
||||
if (buffer.lines.get(this._model.selectionEnd[1]).hasWidth(this._model.selectionEnd[0]) === 0) {
|
||||
const line = buffer.lines.get(this._model.selectionEnd[1]);
|
||||
if (line && line.hasWidth(this._model.selectionEnd[0]) === 0) {
|
||||
this._model.selectionEnd[0]++;
|
||||
}
|
||||
}
|
||||
@@ -627,6 +626,9 @@ export class SelectionService implements ISelectionService {
|
||||
* scrolling of the viewport.
|
||||
*/
|
||||
private _dragScroll(): void {
|
||||
if (!this._model.selectionEnd || !this._model.selectionStart) {
|
||||
return;
|
||||
}
|
||||
if (this._dragScrollAmount) {
|
||||
this._scrollLines(this._dragScrollAmount, false);
|
||||
// Re-evaluate selection
|
||||
@@ -724,16 +726,16 @@ export class SelectionService implements ISelectionService {
|
||||
* Gets positional information for the word at the coordinated specified.
|
||||
* @param coords The coordinates to get the word at.
|
||||
*/
|
||||
private _getWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean, followWrappedLinesAbove: boolean = true, followWrappedLinesBelow: boolean = true): IWordPosition {
|
||||
private _getWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean, followWrappedLinesAbove: boolean = true, followWrappedLinesBelow: boolean = true): IWordPosition | undefined {
|
||||
// Ensure coords are within viewport (eg. not within scroll bar)
|
||||
if (coords[0] >= this._bufferService.cols) {
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const buffer = this._bufferService.buffer;
|
||||
const bufferLine = buffer.lines.get(coords[1]);
|
||||
if (!bufferLine) {
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const line = buffer.translateBufferLineToString(coords[1], false);
|
||||
@@ -840,7 +842,7 @@ export class SelectionService implements ISelectionService {
|
||||
- rightLongCharOffset); // The number of additional chars right of the initial char (inclusive) added by columns with strings longer than 1 (emojis)
|
||||
|
||||
if (!allowWhitespaceOnlySelection && line.slice(startIndex, endIndex).trim() === '') {
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Recurse upwards if the line is wrapped and the word wraps to the above line
|
||||
Vendored
+3
-3
@@ -51,11 +51,11 @@ export interface IRenderService {
|
||||
export interface ISelectionService {
|
||||
readonly selectionText: string;
|
||||
readonly hasSelection: boolean;
|
||||
readonly selectionStart: [number, number];
|
||||
readonly selectionEnd: [number, number];
|
||||
readonly selectionStart: [number, number] | undefined;
|
||||
readonly selectionEnd: [number, number] | undefined;
|
||||
|
||||
readonly onLinuxMouseSelection: IEvent<string>;
|
||||
readonly onRedrawRequest: IEvent<ISelectionRedrawRequestEvent>
|
||||
readonly onRedrawRequest: IEvent<ISelectionRedrawRequestEvent>;
|
||||
readonly onSelectionChange: IEvent<void>;
|
||||
|
||||
disable(): void;
|
||||
|
||||
Vendored
+1
-1
@@ -104,6 +104,7 @@ export interface ITerminalOptions {
|
||||
tabStopWidth: number;
|
||||
theme: ITheme;
|
||||
windowsMode: boolean;
|
||||
wordSeparator: string;
|
||||
|
||||
[key: string]: any;
|
||||
cancelEvents: boolean;
|
||||
@@ -112,7 +113,6 @@ export interface ITerminalOptions {
|
||||
screenKeys: boolean;
|
||||
termName: string;
|
||||
useFlowControl: boolean;
|
||||
wordSeparator?: string;
|
||||
}
|
||||
|
||||
export interface ITheme {
|
||||
|
||||
Reference in New Issue
Block a user