Toggle enable-mouse-events cursor while alt is held for mouseEventsRequireAlt.
Extract AltMouseCursorController to bind DOM listeners only when the option and mouse protocol are active. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
import { assert } from 'chai';
|
||||
import { MouseService } from 'browser/services/MouseService';
|
||||
import { AltMouseCursorController, MouseService } from 'browser/services/MouseService';
|
||||
import { MouseStateService } from 'common/services/MouseStateService';
|
||||
import { CoreMouseAction, CoreMouseButton } from 'common/Types';
|
||||
import { IBufferService, ICoreService, ILogService, IOptionsService } from 'common/services/Services';
|
||||
@@ -332,4 +332,22 @@ describe('MouseService mouseEventsRequireAlt', () => {
|
||||
assert.isTrue(sent);
|
||||
assert.deepEqual(reports, ['\x1b[<0;1;1M']);
|
||||
});
|
||||
|
||||
it('should toggle enable-mouse-events class when alt modifier changes', () => {
|
||||
const element = createTestMouseTargetElement();
|
||||
const altMouseCursor = new AltMouseCursorController(element, {
|
||||
addEventListener: () => {},
|
||||
removeEventListener: () => {}
|
||||
} as any, () => true);
|
||||
|
||||
const sync = (altHeld: boolean) => altMouseCursor.syncFromModifier({
|
||||
getModifierState: (key: string) => key === 'Alt' && altHeld
|
||||
} as KeyboardEvent);
|
||||
|
||||
assert.isFalse(element.classList.contains('enable-mouse-events'));
|
||||
sync(true);
|
||||
assert.isTrue(element.classList.contains('enable-mouse-events'));
|
||||
sync(false);
|
||||
assert.isFalse(element.classList.contains('enable-mouse-events'));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import { addDisposableListener } from 'browser/Dom';
|
||||
import { IBufferService, IMouseStateService, ICoreService, ILogService, IOptionsService } from 'common/services/Services';
|
||||
import { CoreMouseAction, CoreMouseButton, CoreMouseEventType, ICoreMouseEvent, IDisposable } from 'common/Types';
|
||||
import { C0 } from 'common/data/EscapeSequences';
|
||||
import { toDisposable } from 'common/Lifecycle';
|
||||
import { DisposableStore, MutableDisposable, toDisposable } from 'common/Lifecycle';
|
||||
import { ICoreBrowserService, IMouseCoordsService, IMouseService, IMouseServiceTarget, IRenderService, ISelectionService } from './Services';
|
||||
import { Gesture, EventType as GestureEventType, IGestureEvent } from 'browser/scrollable/touch';
|
||||
|
||||
@@ -25,6 +25,7 @@ export class MouseService implements IMouseService {
|
||||
private _lastEvent: ICoreMouseEvent | null = null;
|
||||
private _wheelPartialScroll: number = 0;
|
||||
private _touchScrollAccumulator: number = 0;
|
||||
private _altMouseCursor: AltMouseCursorController | undefined;
|
||||
|
||||
constructor(
|
||||
@IRenderService private readonly _renderService: IRenderService,
|
||||
@@ -63,11 +64,19 @@ export class MouseService implements IMouseService {
|
||||
mousedrag: (ev: Event) => this._handleMouseDrag(ctx, ev as MouseEvent),
|
||||
mousemove: (ev: Event) => this._handleMouseMove(ctx, ev as MouseEvent)
|
||||
};
|
||||
this._altMouseCursor = new AltMouseCursorController(
|
||||
element,
|
||||
document,
|
||||
() => this._mouseStateService.areMouseEventsActive
|
||||
&& !!this._optionsService.rawOptions.mouseEventsRequireAlt
|
||||
);
|
||||
register(this._altMouseCursor);
|
||||
register(this._mouseStateService.onProtocolChange(events => {
|
||||
this._handleProtocolChange(ctx, eventListeners, events);
|
||||
}));
|
||||
register(this._optionsService.onSpecificOptionChange('mouseEventsRequireAlt', () => {
|
||||
this._syncMouseModeState(element);
|
||||
this._altMouseCursor?.sync();
|
||||
}));
|
||||
// force initial onProtocolChange so we dont miss early mouse requests
|
||||
this._mouseStateService.activeProtocol = this._mouseStateService.activeProtocol;
|
||||
@@ -372,7 +381,7 @@ export class MouseService implements IMouseService {
|
||||
private _syncMouseModeState(element: HTMLElement): void {
|
||||
if (this._mouseStateService.areMouseEventsActive) {
|
||||
if (this._optionsService.rawOptions.mouseEventsRequireAlt) {
|
||||
element.classList.remove('enable-mouse-events');
|
||||
this._altMouseCursor?.resetClass();
|
||||
this._selectionService.enable();
|
||||
} else {
|
||||
element.classList.add('enable-mouse-events');
|
||||
@@ -396,6 +405,7 @@ export class MouseService implements IMouseService {
|
||||
this._logService.debug('Unbinding from mouse events.');
|
||||
}
|
||||
this._syncMouseModeState(element);
|
||||
this._altMouseCursor?.sync();
|
||||
|
||||
// add/remove handlers from requestedEvents
|
||||
if (!(events & CoreMouseEventType.MOVE)) {
|
||||
@@ -565,3 +575,64 @@ export class MouseService implements IMouseService {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles `enable-mouse-events` on the terminal element while alt is held when
|
||||
* `mouseEventsRequireAlt` is active. DOM listeners are only registered while active.
|
||||
*/
|
||||
export class AltMouseCursorController implements IDisposable {
|
||||
private readonly _listeners = new MutableDisposable<IDisposable>();
|
||||
|
||||
constructor(
|
||||
private readonly _element: HTMLElement,
|
||||
private readonly _document: Document,
|
||||
private readonly _isActive: () => boolean
|
||||
) {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._listeners.dispose();
|
||||
}
|
||||
|
||||
public sync(): void {
|
||||
this._listeners.clear();
|
||||
|
||||
if (!this._isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const store = new DisposableStore();
|
||||
const syncFromModifier = (ev: KeyboardEvent | MouseEvent) => this.syncFromModifier(ev);
|
||||
store.add(addDisposableListener(this._document, 'keydown', syncFromModifier));
|
||||
store.add(addDisposableListener(this._document, 'keyup', syncFromModifier));
|
||||
store.add(addDisposableListener(this._element, 'mousemove', syncFromModifier));
|
||||
const targetWindow = this._element.ownerDocument?.defaultView;
|
||||
if (targetWindow) {
|
||||
store.add(addDisposableListener(targetWindow, 'blur', () => {
|
||||
if (this._isActive()) {
|
||||
this.resetClass();
|
||||
}
|
||||
}));
|
||||
}
|
||||
this._listeners.value = store;
|
||||
}
|
||||
|
||||
public resetClass(): void {
|
||||
this._updateClass(false);
|
||||
}
|
||||
|
||||
public syncFromModifier(ev: KeyboardEvent | MouseEvent): void {
|
||||
if (!this._isActive()) {
|
||||
return;
|
||||
}
|
||||
this._updateClass(ev.getModifierState('Alt'));
|
||||
}
|
||||
|
||||
private _updateClass(altHeld: boolean): void {
|
||||
if (altHeld) {
|
||||
this._element.classList.add('enable-mouse-events');
|
||||
} else {
|
||||
this._element.classList.remove('enable-mouse-events');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user