Skip to content

Commit a924a46

Browse files
committed
Use linkifier2 to double click select links
Also works with right click select. Fixes xtermjs#682.
1 parent 7dd7460 commit a924a46

File tree

5 files changed

+35
-21
lines changed

5 files changed

+35
-21
lines changed

src/browser/Linkifier2.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,20 @@
33
* @license MIT
44
*/
55

6-
import { ILinkifier2, ILinkProvider, IBufferCellPosition, ILink, ILinkifierEvent, ILinkDecorations } from 'browser/Types';
6+
import { ILinkifier2, ILinkProvider, IBufferCellPosition, ILink, ILinkifierEvent, ILinkDecorations, ILinkWithState } from 'browser/Types';
77
import { IDisposable } from 'common/Types';
88
import { IMouseService, IRenderService } from './services/Services';
99
import { IBufferService } from 'common/services/Services';
1010
import { EventEmitter, IEvent } from 'common/EventEmitter';
1111
import { Disposable, getDisposeArrayDisposable, disposeArray } from 'common/Lifecycle';
1212
import { addDisposableDomListener } from 'browser/Lifecycle';
1313

14-
interface ILinkState {
15-
decorations: ILinkDecorations;
16-
isHovered: boolean;
17-
}
18-
19-
interface ILinkWithState {
20-
link: ILink;
21-
state?: ILinkState;
22-
}
23-
2414
export class Linkifier2 extends Disposable implements ILinkifier2 {
2515
private _element: HTMLElement | undefined;
2616
private _mouseService: IMouseService | undefined;
2717
private _renderService: IRenderService | undefined;
2818
private _linkProviders: ILinkProvider[] = [];
19+
public get currentLink(): ILinkWithState | undefined { return this._currentLink; }
2920
protected _currentLink: ILinkWithState | undefined;
3021
private _lastMouseEvent: MouseEvent | undefined;
3122
private _linkCacheDisposables: IDisposable[] = [];

src/browser/Terminal.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,9 @@ export class Terminal extends CoreTerminal implements ITerminal {
482482

483483
this._selectionService = this.register(this._instantiationService.createInstance(SelectionService,
484484
this.element,
485-
this.screenElement));
485+
this.screenElement,
486+
this.linkifier2
487+
));
486488
this._instantiationService.setService(ISelectionService, this._selectionService);
487489
this.register(this._selectionService.onRequestScrollLines(e => this.scrollLines(e.amount, e.suppressScrollEvent)));
488490
this.register(this._selectionService.onSelectionChange(() => this._onSelectionChange.fire()));

src/browser/Types.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,19 @@ export interface ILinkifier {
205205
deregisterLinkMatcher(matcherId: number): boolean;
206206
}
207207

208+
interface ILinkState {
209+
decorations: ILinkDecorations;
210+
isHovered: boolean;
211+
}
212+
export interface ILinkWithState {
213+
link: ILink;
214+
state?: ILinkState;
215+
}
216+
208217
export interface ILinkifier2 {
209218
onShowLinkUnderline: IEvent<ILinkifierEvent>;
210219
onHideLinkUnderline: IEvent<ILinkifierEvent>;
220+
currentLink: ILinkWithState | undefined;
211221

212222
attachToDom(element: HTMLElement, mouseService: IMouseService, renderService: IRenderService): void;
213223
registerLinkProvider(linkProvider: ILinkProvider): IDisposable;

src/browser/services/SelectionService.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class TestSelectionService extends SelectionService {
2121
optionsService: IOptionsService,
2222
renderService: IRenderService
2323
) {
24-
super(null!, null!, bufferService, new MockCoreService(), new MockMouseService(), optionsService, renderService);
24+
super(null!, null!, null!, bufferService, new MockCoreService(), new MockMouseService(), optionsService, renderService);
2525
}
2626

2727
public get model(): SelectionModel { return this._model; }

src/browser/services/SelectionService.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { SelectionModel } from 'browser/selection/SelectionModel';
1111
import { CellData } from 'common/buffer/CellData';
1212
import { EventEmitter, IEvent } from 'common/EventEmitter';
1313
import { ICharSizeService, IMouseService, ISelectionService, IRenderService } from 'browser/services/Services';
14+
import { ILinkifier2 } from 'browser/Types';
1415
import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services';
1516
import { getCoordsRelativeToElement } from 'browser/input/Mouse';
1617
import { moveToCellSequence } from 'browser/input/MoveToCell';
@@ -121,6 +122,7 @@ export class SelectionService extends Disposable implements ISelectionService {
121122
constructor(
122123
private readonly _element: HTMLElement,
123124
private readonly _screenElement: HTMLElement,
125+
private readonly _linkifier: ILinkifier2,
124126
@IBufferService private readonly _bufferService: IBufferService,
125127
@ICoreService private readonly _coreService: ICoreService,
126128
@IMouseService private readonly _mouseService: IMouseService,
@@ -316,13 +318,22 @@ export class SelectionService extends Disposable implements ISelectionService {
316318
* Selects word at the current mouse event coordinates.
317319
* @param event The mouse event.
318320
*/
319-
private _selectWordAtCursor(event: MouseEvent): void {
321+
private _selectWordAtCursor(event: MouseEvent, allowWhitespaceOnlySelection: boolean): boolean {
322+
const range = this._linkifier.currentLink?.link?.range;
323+
if (range) {
324+
const scrollOffset = this._bufferService.buffer.ydisp;
325+
this._model.selectionStart = [range.start.x - 1, range.start.y - scrollOffset - 1];
326+
this._model.selectionEnd = [range.end.x, range.end.y - scrollOffset - 1];
327+
return true;
328+
}
329+
320330
const coords = this._getMouseBufferCoords(event);
321331
if (coords) {
322-
this._selectWordAt(coords, false);
332+
this._selectWordAt(coords, allowWhitespaceOnlySelection);
323333
this._model.selectionEnd = undefined;
324-
this.refresh(true);
334+
return true;
325335
}
336+
return false;
326337
}
327338

328339
/**
@@ -527,14 +538,12 @@ export class SelectionService extends Disposable implements ISelectionService {
527538
}
528539

529540
/**
530-
* Performs a double click, selecting the current work.
541+
* Performs a double click, selecting the current word.
531542
* @param event The mouse event.
532543
*/
533544
private _onDoubleClick(event: MouseEvent): void {
534-
const coords = this._getMouseBufferCoords(event);
535-
if (coords) {
545+
if (this._selectWordAtCursor(event, true)) {
536546
this._activeSelectionMode = SelectionMode.WORD;
537-
this._selectWordAt(coords, true);
538547
}
539548
}
540549

@@ -764,7 +773,9 @@ export class SelectionService extends Disposable implements ISelectionService {
764773

765774
public rightClickSelect(ev: MouseEvent): void {
766775
if (!this._isClickInSelection(ev)) {
767-
this._selectWordAtCursor(ev);
776+
if (this._selectWordAtCursor(ev, false)) {
777+
this.refresh(true);
778+
}
768779
this._fireEventIfSelectionChanged();
769780
}
770781
}

0 commit comments

Comments
 (0)