From cfb33e8b0ca37826071bd6fee0bb1de1a9b2c519 Mon Sep 17 00:00:00 2001 From: Jan Keromnes Date: Mon, 13 Jan 2020 18:44:40 +0000 Subject: [PATCH] [terminal] Always open terminal links on touchevents e.g. when tapping a link on iPad. Signed-off-by: Jan Keromnes --- CHANGELOG.md | 2 ++ .../src/browser/terminal-linkmatcher.ts | 28 +++++++++++++++++-- .../src/browser/terminal-widget-impl.ts | 15 ++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e8ae43dcbb4e..64425a7e7e76e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## v0.15.0 +- [terminal] always open terminal links on touchevents (e.g. when tapping a link on iPad) [#6875](https://github.com/eclipse-theia/theia/pull/6875) + Breaking changes: - [task] renamed method `getStrigifiedTaskSchema()` has been renamed to `getStringifiedTaskSchema()` [#6780](https://github.com/eclipse-theia/theia/pull/6780) diff --git a/packages/terminal/src/browser/terminal-linkmatcher.ts b/packages/terminal/src/browser/terminal-linkmatcher.ts index e4b7a106e4714..49b7172c3f85f 100644 --- a/packages/terminal/src/browser/terminal-linkmatcher.ts +++ b/packages/terminal/src/browser/terminal-linkmatcher.ts @@ -37,16 +37,18 @@ export abstract class AbstractCmdClickTerminalContribution implements TerminalCo const validate = this.getValidate(terminalWidget); const wrappedHandler = (event: MouseEvent, match: string) => { event.preventDefault(); - if (this.isCommandPressed(event)) { + if (this.isCommandPressed(event) || this.wasTouchEvent(event, terminalWidget.lastTouchEndEvent)) { handler(event, match); } else { term.focus(); } }; const matcherId = term.registerLinkMatcher(regexp, wrappedHandler, { - willLinkActivate: (event: MouseEvent, uri: string) => this.isCommandPressed(event), + willLinkActivate: (event: MouseEvent, uri: string) => this.isCommandPressed(event) || this.wasTouchEvent(event, terminalWidget.lastTouchEndEvent), tooltipCallback: (event: MouseEvent, uri: string) => { - terminalWidget.showHoverMessage(event.clientX, event.clientY, this.getHoverMessage()); + if (!this.wasTouchEvent(event, terminalWidget.lastTouchEndEvent)) { + terminalWidget.showHoverMessage(event.clientX, event.clientY, this.getHoverMessage()); + } }, leaveCallback: (event: MouseEvent, uri: string) => { terminalWidget.hideHover(); @@ -64,6 +66,26 @@ export abstract class AbstractCmdClickTerminalContribution implements TerminalCo return isOSX ? event.metaKey : event.ctrlKey; } + protected wasTouchEvent(event: MouseEvent, lastTouchEnd: TouchEvent | undefined): boolean { + if (!lastTouchEnd) { + return false; + } + if ((event.timeStamp - lastTouchEnd.timeStamp) > 400) { + // A 'touchend' event typically precedes a matching 'click' event by 50ms. + return false; + } + if (Math.abs(event.pageX - (lastTouchEnd as unknown as MouseEvent).pageX) > 5) { + // Matching 'touchend' and 'click' events typically have the same page coordinates, + // plus or minus 1 pixel. + return false; + } + if (Math.abs(event.pageY - (lastTouchEnd as unknown as MouseEvent).pageY) > 5) { + return false; + } + // We have a match! This link was tapped. + return true; + } + protected getHoverMessage(): string { if (isOSX) { return 'Cmd + click to follow link'; diff --git a/packages/terminal/src/browser/terminal-widget-impl.ts b/packages/terminal/src/browser/terminal-widget-impl.ts index b84c03a3f9801..48ea8a2f1841b 100644 --- a/packages/terminal/src/browser/terminal-widget-impl.ts +++ b/packages/terminal/src/browser/terminal-widget-impl.ts @@ -53,6 +53,7 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget protected closeOnDispose = true; protected waitForConnection: Deferred | undefined; protected hoverMessage: HTMLDivElement; + protected lastTouchEnd: TouchEvent | undefined; @inject(WorkspaceService) protected readonly workspaceService: WorkspaceService; @inject(WebSocketConnectionProvider) protected readonly webSocketConnectionProvider: WebSocketConnectionProvider; @@ -173,6 +174,16 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget this.toDispose.push(this.onTermDidClose); this.toDispose.push(this.onDidOpenEmitter); + const touchEndListener = (event: TouchEvent) => { + if (this.node.contains(event.target as Node)) { + this.lastTouchEnd = event; + } + }; + document.addEventListener('touchend', touchEndListener, { passive: true }); + this.onDispose(() => { + document.removeEventListener('touchend', touchEndListener); + }); + this.toDispose.push(this.term.onSelectionChange(() => { if (this.copyOnSelection) { this.copyOnSelectionHandler.copy(this.term.getSelection()); @@ -229,6 +240,10 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget return this.shellTerminalServer.getProcessId(this.terminalId); } + get lastTouchEndEvent(): TouchEvent | undefined { + return this.lastTouchEnd; + } + onDispose(onDispose: () => void): void { this.toDispose.push(Disposable.create(onDispose)); }