Add altClickForMouseEvents option for issue #5952
When enabled during mouse events mode, click/drag/move events require the alt key while wheel events are unaffected. Selection stays enabled by default and takes precedence over macOptionClickForcesSelection. Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
This commit is contained in:
co-authored by
Daniel Imms
parent
95d9224b8c
commit
fbb2a9710f
@@ -619,8 +619,10 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
|
||||
// apply mouse event classes set by escape codes before terminal was attached
|
||||
if (this.mouseStateService.areMouseEventsActive) {
|
||||
this._selectionService.disable();
|
||||
this.element.classList.add('enable-mouse-events');
|
||||
if (!this.options.altClickForMouseEvents) {
|
||||
this._selectionService.disable();
|
||||
this.element.classList.add('enable-mouse-events');
|
||||
}
|
||||
} else {
|
||||
this._selectionService.enable();
|
||||
}
|
||||
|
||||
@@ -153,6 +153,13 @@ export class MouseService implements IMouseService {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (but !== CoreMouseButton.WHEEL
|
||||
&& this._optionsService.rawOptions.altClickForMouseEvents
|
||||
&& this._mouseStateService.areMouseEventsActive
|
||||
&& !ev.altKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._triggerMouseEvent({
|
||||
col: pos.col,
|
||||
row: pos.row,
|
||||
@@ -361,8 +368,12 @@ export class MouseService implements IMouseService {
|
||||
if (this._optionsService.rawOptions.logLevel === 'debug') {
|
||||
this._logService.debug('Binding to mouse events:', this._explainEvents(events));
|
||||
}
|
||||
element.classList.add('enable-mouse-events');
|
||||
this._selectionService.disable();
|
||||
if (this._optionsService.rawOptions.altClickForMouseEvents) {
|
||||
this._selectionService.enable();
|
||||
} else {
|
||||
element.classList.add('enable-mouse-events');
|
||||
this._selectionService.disable();
|
||||
}
|
||||
} else {
|
||||
this._logService.debug('Unbinding from mouse events.');
|
||||
element.classList.remove('enable-mouse-events');
|
||||
|
||||
@@ -7,7 +7,7 @@ import { assert } from 'chai';
|
||||
import { SelectionService, SelectionMode } from './SelectionService';
|
||||
import { SelectionModel } from 'browser/selection/SelectionModel';
|
||||
import { IBufferLine } from 'common/Types';
|
||||
import { MockBufferService, MockOptionsService, MockCoreService, createCellData } from 'common/TestUtils.test';
|
||||
import { MockBufferService, MockOptionsService, MockCoreService, MockMouseStateService, createCellData } from 'common/TestUtils.test';
|
||||
import { BufferLine } from 'common/buffer/BufferLine';
|
||||
import { BufferLineStringCache } from 'common/buffer/BufferLineStringCache';
|
||||
import { IBufferService, IOptionsService } from 'common/services/Services';
|
||||
@@ -22,9 +22,10 @@ class TestSelectionService extends SelectionService {
|
||||
constructor(
|
||||
bufferService: IBufferService,
|
||||
optionsService: IOptionsService,
|
||||
renderService: IRenderService
|
||||
renderService: IRenderService,
|
||||
public readonly mouseStateService: MockMouseStateService
|
||||
) {
|
||||
super(null!, null!, null!, bufferService, new MockCoreService(), new MockMouseService(), optionsService, renderService, new MockCoreBrowserService());
|
||||
super(null!, null!, null!, bufferService, new MockCoreService(), new MockMouseService(), optionsService, mouseStateService, renderService, new MockCoreBrowserService());
|
||||
}
|
||||
|
||||
public get model(): SelectionModel { return this._model; }
|
||||
@@ -45,16 +46,18 @@ describe('SelectionService', () => {
|
||||
let buffer: IBuffer;
|
||||
let bufferService: IBufferService;
|
||||
let optionsService: IOptionsService;
|
||||
let mouseStateService: MockMouseStateService;
|
||||
let selectionService: TestSelectionService;
|
||||
|
||||
beforeEach(() => {
|
||||
optionsService = new MockOptionsService();
|
||||
mouseStateService = new MockMouseStateService();
|
||||
bufferService = new MockBufferService(20, 20, optionsService);
|
||||
buffer = bufferService.buffer;
|
||||
const renderService = new MockRenderService();
|
||||
renderService.dimensions.css.canvas.height = 10 * 20;
|
||||
renderService.dimensions.css.canvas.width = 10 * 20;
|
||||
selectionService = new TestSelectionService(bufferService, optionsService, renderService);
|
||||
selectionService = new TestSelectionService(bufferService, optionsService, renderService, mouseStateService);
|
||||
});
|
||||
|
||||
function stringToRow(text: string): IBufferLine {
|
||||
@@ -499,5 +502,21 @@ describe('SelectionService', () => {
|
||||
assert.isFalse(selectionService.areCoordsInSelection([2, 1], [2, 0], [2, 1]));
|
||||
});
|
||||
});
|
||||
|
||||
describe('shouldForceSelection', () => {
|
||||
it('should force selection without alt when altClickForMouseEvents is enabled', () => {
|
||||
optionsService.options.altClickForMouseEvents = true;
|
||||
mouseStateService.areMouseEventsActive = true;
|
||||
assert.isTrue(selectionService.shouldForceSelection({ altKey: false } as MouseEvent));
|
||||
assert.isFalse(selectionService.shouldForceSelection({ altKey: true } as MouseEvent));
|
||||
});
|
||||
|
||||
it('should take precedence over macOptionClickForcesSelection', () => {
|
||||
optionsService.options.altClickForMouseEvents = true;
|
||||
optionsService.options.macOptionClickForcesSelection = true;
|
||||
mouseStateService.areMouseEventsActive = true;
|
||||
assert.isFalse(selectionService.shouldForceSelection({ altKey: true } as MouseEvent));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import { IBufferLine, ICellData, IDisposable } from 'common/Types';
|
||||
import { getRangeLength } from 'common/buffer/BufferRange';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { IBuffer } from 'common/buffer/Types';
|
||||
import { IBufferService, ICoreService, IOptionsService } from 'common/services/Services';
|
||||
import { IBufferService, ICoreService, IMouseStateService, IOptionsService } from 'common/services/Services';
|
||||
import { Emitter } from 'common/Event';
|
||||
|
||||
const enum Constants {
|
||||
@@ -127,6 +127,7 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
@ICoreService private readonly _coreService: ICoreService,
|
||||
@IMouseCoordsService private readonly _mouseCoordsService: IMouseCoordsService,
|
||||
@IOptionsService private readonly _optionsService: IOptionsService,
|
||||
@IMouseStateService private readonly _mouseStateService: IMouseStateService,
|
||||
@IRenderService private readonly _renderService: IRenderService,
|
||||
@ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService
|
||||
) {
|
||||
@@ -434,6 +435,10 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
* @param event The mouse event.
|
||||
*/
|
||||
public shouldForceSelection(event: MouseEvent): boolean {
|
||||
if (this._optionsService.rawOptions.altClickForMouseEvents && this._mouseStateService.areMouseEventsActive) {
|
||||
return !event.altKey;
|
||||
}
|
||||
|
||||
if (Browser.isMac) {
|
||||
return event.altKey && this._optionsService.rawOptions.macOptionClickForcesSelection;
|
||||
}
|
||||
@@ -458,6 +463,10 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._optionsService.rawOptions.altClickForMouseEvents && this._mouseStateService.areMouseEventsActive && event.altKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow selection when using a specific modifier key, even when disabled
|
||||
if (!this._enabled) {
|
||||
if (!this.shouldForceSelection(event)) {
|
||||
@@ -596,6 +605,9 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
* @param event the mouse or keyboard event
|
||||
*/
|
||||
public shouldColumnSelect(event: KeyboardEvent | MouseEvent): boolean {
|
||||
if (this._optionsService.rawOptions.altClickForMouseEvents && this._mouseStateService.areMouseEventsActive) {
|
||||
return false;
|
||||
}
|
||||
return event.altKey && !(Browser.isMac && this._optionsService.rawOptions.macOptionClickForcesSelection);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ export const DEFAULT_OPTIONS: Readonly<Required<ITerminalOptions>> = {
|
||||
windowOptions: {},
|
||||
windowsPty: {},
|
||||
wordSeparator: ' ()[]{}\',"`',
|
||||
altClickForMouseEvents: false,
|
||||
altClickMovesCursor: true,
|
||||
convertEol: false,
|
||||
termName: 'xterm',
|
||||
|
||||
@@ -209,6 +209,7 @@ export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'off';
|
||||
export interface ITerminalOptions {
|
||||
allowProposedApi?: boolean;
|
||||
allowTransparency?: boolean;
|
||||
altClickForMouseEvents?: boolean;
|
||||
altClickMovesCursor?: boolean;
|
||||
cols?: number;
|
||||
convertEol?: boolean;
|
||||
|
||||
Vendored
+10
@@ -38,6 +38,16 @@ declare module '@xterm/headless' {
|
||||
*/
|
||||
altClickMovesCursor?: boolean;
|
||||
|
||||
/**
|
||||
* When enabled and the terminal is in mouse events mode, mouse click, drag,
|
||||
* and move events are only sent to the underlying application when the alt
|
||||
* key is held. Wheel events are not affected. This allows normal text
|
||||
* selection by default while still supporting application mouse interaction
|
||||
* and scrolling when holding alt. When enabled, this takes precedence over
|
||||
* `macOptionClickForcesSelection`.
|
||||
*/
|
||||
altClickForMouseEvents?: boolean;
|
||||
|
||||
/**
|
||||
* When enabled the cursor will be set to the beginning of the next line
|
||||
* with every new line. This is equivalent to sending '\r\n' for each '\n'.
|
||||
|
||||
Vendored
+10
@@ -45,6 +45,16 @@ declare module '@xterm/xterm' {
|
||||
*/
|
||||
altClickMovesCursor?: boolean;
|
||||
|
||||
/**
|
||||
* When enabled and the terminal is in mouse events mode, mouse click, drag,
|
||||
* and move events are only sent to the underlying application when the alt
|
||||
* key is held. Wheel events are not affected. This allows normal text
|
||||
* selection by default while still supporting application mouse interaction
|
||||
* and scrolling when holding alt. When enabled, this takes precedence over
|
||||
* `macOptionClickForcesSelection`.
|
||||
*/
|
||||
altClickForMouseEvents?: boolean;
|
||||
|
||||
/**
|
||||
* When enabled the cursor will be set to the beginning of the next line
|
||||
* with every new line. This is equivalent to sending `\r\n` for each `\n`.
|
||||
|
||||
Reference in New Issue
Block a user