Skip to content

Commit 89980fc

Browse files
authored
Merge pull request #5758 from Tyriar/5754__5756
CoreMouseService -> MouseStateService, make mouse services more focused
2 parents 1400398 + 16b5da5 commit 89980fc

18 files changed

+553
-470
lines changed

src/browser/CoreBrowserTerminal.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
7878
public browser: IBrowser = Browser as any;
7979

8080
private _customKeyEventHandler: CustomKeyEventHandler | undefined;
81-
private _customWheelEventHandler: CustomWheelEventHandler | undefined;
8281

8382
// Browser services
8483
private readonly _decorationService: DecorationService;
@@ -582,7 +581,6 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
582581
));
583582
this._instantiationService.setService(ISelectionService, this._selectionService);
584583
this._mouseService = this._instantiationService.createInstance(MouseService);
585-
this._mouseService.setCustomWheelEventHandler(this._customWheelEventHandler);
586584
this._instantiationService.setService(IMouseService, this._mouseService);
587585
this._register(this._selectionService.onRequestScrollLines(e => this.scrollLines(e.amount, e.suppressScrollEvent)));
588586
this._register(this._selectionService.onSelectionChange(() => this._onSelectionChange.fire()));
@@ -607,7 +605,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
607605
this._register(addDisposableListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.handleMouseDown(e)));
608606

609607
// apply mouse event classes set by escape codes before terminal was attached
610-
if (this.coreMouseService.areMouseEventsActive) {
608+
if (this.mouseStateService.areMouseEventsActive) {
611609
this._selectionService.disable();
612610
this.element.classList.add('enable-mouse-events');
613611
} else {
@@ -727,8 +725,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
727725
}
728726

729727
public attachCustomWheelEventHandler(customWheelEventHandler: CustomWheelEventHandler): void {
730-
this._customWheelEventHandler = customWheelEventHandler;
731-
this._mouseService?.setCustomWheelEventHandler(customWheelEventHandler);
728+
this.mouseStateService.setCustomWheelEventHandler(customWheelEventHandler);
732729
}
733730

734731
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
@@ -1098,6 +1095,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
10981095

10991096
this._setup();
11001097
super.reset();
1098+
this._mouseService?.reset();
11011099
this._selectionService?.reset();
11021100
this._decorationService.reset();
11031101

src/browser/TestUtils.test.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, I
1212
import { Buffer } from 'common/buffer/Buffer';
1313
import * as Browser from 'common/Platform';
1414
import { CoreBrowserTerminal } from 'browser/CoreBrowserTerminal';
15-
import { IUnicodeService, IOptionsService, ICoreService, ICoreMouseService } from 'common/services/Services';
15+
import { IUnicodeService, IOptionsService, ICoreService, IMouseStateService } from 'common/services/Services';
1616
import { IFunctionIdentifier, IParams } from 'common/parser/Types';
1717
import { AttributeData } from 'common/buffer/AttributeData';
1818
import { ISelectionRedrawRequestEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
@@ -51,7 +51,7 @@ export class MockTerminal implements ITerminal {
5151
public dimensions: IRenderDimensionsApi | undefined;
5252
public markers!: IMarker[];
5353
public linkifier: ILinkifier2 | undefined;
54-
public coreMouseService!: ICoreMouseService;
54+
public mouseStateService!: IMouseStateService;
5555
public coreService!: ICoreService;
5656
public optionsService!: IOptionsService;
5757
public unicodeService!: IUnicodeService;
@@ -393,13 +393,8 @@ export class MockMouseService implements IMouseService {
393393
throw new Error('Not implemented');
394394
}
395395

396-
public setCustomWheelEventHandler(): void {
397-
throw new Error('Not implemented');
398-
}
399-
400-
public bindMouse(): void {
401-
throw new Error('Not implemented');
402-
}
396+
public bindMouse(): void { }
397+
public reset(): void { }
403398
}
404399

405400
export class MockRenderService implements IRenderService {

src/browser/Viewport.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import { ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
77
import { ViewportConstants } from 'browser/shared/Constants';
88
import { Disposable, toDisposable } from 'common/Lifecycle';
9-
import { IBufferService, ICoreMouseService, IOptionsService } from 'common/services/Services';
9+
import { IBufferService, IMouseStateService, IOptionsService } from 'common/services/Services';
1010
import { CoreMouseEventType } from 'common/Types';
1111
import { addDisposableListener, scheduleAtNextAnimationFrame } from 'browser/Dom';
1212
import { SmoothScrollableElement } from 'browser/scrollable/scrollableElement';
@@ -34,7 +34,7 @@ export class Viewport extends Disposable {
3434
screenElement: HTMLElement,
3535
@IBufferService private readonly _bufferService: IBufferService,
3636
@ICoreBrowserService coreBrowserService: ICoreBrowserService,
37-
@ICoreMouseService coreMouseService: ICoreMouseService,
37+
@IMouseStateService mouseStateService: IMouseStateService,
3838
@IThemeService themeService: IThemeService,
3939
@IOptionsService private readonly _optionsService: IOptionsService,
4040
@IRenderService private readonly _renderService: IRenderService
@@ -65,7 +65,7 @@ export class Viewport extends Disposable {
6565
'scrollbar'
6666
], () => this._scrollableElement.updateOptions(this._getChangeOptions())));
6767
// Don't handle mouse wheel if wheel events are supported by the current mouse prototcol
68-
this._register(coreMouseService.onProtocolChange(type => {
68+
this._register(mouseStateService.onProtocolChange(type => {
6969
this._scrollableElement.updateOptions({
7070
handleMouseWheel: !(type & CoreMouseEventType.WHEEL)
7171
});

src/browser/public/Terminal.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export class Terminal extends Disposable implements ITerminalApi {
103103
public get modes(): IModes {
104104
const m = this._core.coreService.decPrivateModes;
105105
let mouseTrackingMode: 'none' | 'x10' | 'vt200' | 'drag' | 'any' = 'none';
106-
switch (this._core.coreMouseService.activeProtocol) {
106+
switch (this._core.mouseStateService.activeProtocol) {
107107
case 'X10': mouseTrackingMode = 'x10'; break;
108108
case 'VT200': mouseTrackingMode = 'vt200'; break;
109109
case 'DRAG': mouseTrackingMode = 'drag'; break;
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/**
2+
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
3+
* @license MIT
4+
*/
5+
import { assert } from 'chai';
6+
import { MouseService } from 'browser/services/MouseService';
7+
import { MouseStateService } from 'common/services/MouseStateService';
8+
import { CoreMouseAction, CoreMouseButton } from 'common/Types';
9+
import { IBufferService, ICoreService, ILogService, IOptionsService } from 'common/services/Services';
10+
import { MockCoreBrowserService, MockRenderService, MockSelectionService } from 'browser/TestUtils.test';
11+
12+
function toBytes(s: string | undefined): number[] {
13+
if (!s) {
14+
return [];
15+
}
16+
const res: number[] = [];
17+
for (let i = 0; i < s.length; ++i) {
18+
res.push(s.charCodeAt(i));
19+
}
20+
return res;
21+
}
22+
23+
// Minimal mocks for deps that MouseService touches in these tests
24+
const bufferService: IBufferService = {
25+
buffer: { hasScrollback: true } as any,
26+
cols: 500,
27+
rows: 500
28+
} as any;
29+
30+
const optionsService: IOptionsService = {
31+
rawOptions: {
32+
logLevel: 'info',
33+
fastScrollSensitivity: 1,
34+
scrollSensitivity: 1
35+
}
36+
} as any;
37+
38+
const logService: ILogService = {
39+
debug: () => {},
40+
info: () => {},
41+
warn: () => {},
42+
error: () => {}
43+
} as any;
44+
45+
describe('MouseService _triggerMouseEvent', () => {
46+
let mouseService: MouseService;
47+
let mouseStateService: MouseStateService;
48+
let coreService: ICoreService;
49+
let reports: string[];
50+
51+
beforeEach(() => {
52+
reports = [];
53+
mouseStateService = new MouseStateService();
54+
coreService = {
55+
triggerDataEvent: (data: string) => reports.push(data),
56+
triggerBinaryEvent: (data: string) => reports.push(data),
57+
decPrivateModes: { applicationCursorKeys: false }
58+
} as any;
59+
60+
mouseService = new MouseService(
61+
new MockRenderService(),
62+
{
63+
getMouseReportCoords: (_ev: MouseEvent, _el: HTMLElement) => ({ col: 0, row: 0, x: 0, y: 0 })
64+
} as any,
65+
mouseStateService,
66+
coreService,
67+
bufferService,
68+
optionsService,
69+
new MockSelectionService(),
70+
logService,
71+
new MockCoreBrowserService()
72+
);
73+
});
74+
75+
function trigger(e: Parameters<any>[0]): boolean {
76+
return (mouseService as any)._triggerMouseEvent(e);
77+
}
78+
79+
it('NONE', () => {
80+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);
81+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), false);
82+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), false);
83+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), false);
84+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), false);
85+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), false);
86+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), false);
87+
});
88+
89+
it('X10', () => {
90+
mouseStateService.activeProtocol = 'X10';
91+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
92+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), false);
93+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), false);
94+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true);
95+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true);
96+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), false);
97+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), false);
98+
});
99+
100+
it('VT200', () => {
101+
mouseStateService.activeProtocol = 'VT200';
102+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
103+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), true);
104+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), false);
105+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true);
106+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true);
107+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), true);
108+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), false);
109+
});
110+
111+
it('DRAG', () => {
112+
mouseStateService.activeProtocol = 'DRAG';
113+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
114+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), true);
115+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), true);
116+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true);
117+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true);
118+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), true);
119+
});
120+
121+
it('ANY', () => {
122+
mouseStateService.activeProtocol = 'ANY';
123+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
124+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), true);
125+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), true);
126+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true);
127+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true);
128+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), true);
129+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), true);
130+
// should not report in any case
131+
// invalid button + action combinations
132+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.MOVE }), false);
133+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.DOWN }), false);
134+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.UP }), false);
135+
// invalid coords
136+
assert.equal(trigger({ col: -1, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);
137+
assert.equal(trigger({ col: 500, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);
138+
assert.equal(trigger({ col: 0, row: -1, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);
139+
assert.equal(trigger({ col: 0, row: 500, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);
140+
});
141+
142+
describe('coords', () => {
143+
it('DEFAULT encoding', () => {
144+
mouseStateService.activeProtocol = 'ANY';
145+
for (let i = 0; i < bufferService.cols; ++i) {
146+
assert.equal(trigger({ col: i, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
147+
if (i > 222) {
148+
// supress mouse reports if we are out of addressible range (max. 222)
149+
assert.deepEqual(toBytes(reports.pop()), []);
150+
} else {
151+
assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]);
152+
}
153+
}
154+
});
155+
156+
it('SGR encoding', () => {
157+
mouseStateService.activeProtocol = 'ANY';
158+
mouseStateService.activeEncoding = 'SGR';
159+
for (let i = 0; i < bufferService.cols; ++i) {
160+
assert.equal(trigger({ col: i, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
161+
assert.deepEqual(reports.pop(), `\x1b[<0;${i + 1};1M`);
162+
}
163+
});
164+
165+
it('SGR_PIXELS encoding', () => {
166+
mouseStateService.activeProtocol = 'ANY';
167+
mouseStateService.activeEncoding = 'SGR_PIXELS';
168+
for (let i = 0; i < 500; ++i) {
169+
assert.equal(trigger({ col: 0, row: 0, x: i, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
170+
assert.deepEqual(reports.pop(), `\x1b[<0;${i};0M`);
171+
}
172+
});
173+
});
174+
175+
it('eventCodes with modifiers (DEFAULT encoding)', () => {
176+
// TODO: implement AUX button tests
177+
mouseStateService.activeProtocol = 'ANY';
178+
mouseStateService.activeEncoding = 'DEFAULT';
179+
// all buttons + down + no modifer
180+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN, ctrl: false, alt: false, shift: false }), true);
181+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN, ctrl: false, alt: false, shift: false }), true);
182+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN, ctrl: false, alt: false, shift: false }), true);
183+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.DOWN, ctrl: false, alt: false, shift: false }), true);
184+
assert.deepEqual(reports, ['\x1b[M !!', '\x1b[M!!!', '\x1b[M"!!', '\x1b[Ma!!']);
185+
reports = [];
186+
187+
// all buttons + up + no modifier
188+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP, ctrl: false, alt: false, shift: false }), true);
189+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.UP, ctrl: false, alt: false, shift: false }), true);
190+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.UP, ctrl: false, alt: false, shift: false }), true);
191+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP, ctrl: false, alt: false, shift: false }), true);
192+
assert.deepEqual(reports, ['\x1b[M#!!', '\x1b[M#!!', '\x1b[M#!!', '\x1b[M`!!']);
193+
reports = [];
194+
195+
// all buttons + move + no modifier
196+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: false }), true);
197+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: false }), true);
198+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: false }), true);
199+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: false }), true);
200+
assert.deepEqual(reports, ['\x1b[M@!!', '\x1b[MA!!', '\x1b[MB!!', '\x1b[MC!!']);
201+
reports = [];
202+
203+
// button none + move + modifiers
204+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: true, alt: false, shift: false }), true);
205+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: false, alt: true, shift: false }), true);
206+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: true }), true);
207+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: true, alt: true, shift: false }), true);
208+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: false, alt: true, shift: true }), true);
209+
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: true, alt: true, shift: true }), true);
210+
assert.deepEqual(reports, ['\x1b[MS!!', '\x1b[MK!!', '\x1b[MG!!', '\x1b[M[!!', '\x1b[MO!!', '\x1b[M_!!']);
211+
reports = [];
212+
});
213+
});

0 commit comments

Comments
 (0)