Skip to content

Commit

Permalink
[terminal] Added basic link matching
Browse files Browse the repository at this point in the history
See #3724

Signed-off-by: Sven Efftinge <sven.efftinge@typefox.io>
  • Loading branch information
svenefftinge committed May 15, 2019
1 parent 09e8c27 commit 62eabbb
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 1 deletion.
9 changes: 9 additions & 0 deletions packages/terminal/src/browser/terminal-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import { ContainerModule, Container } from 'inversify';
import { CommandContribution, MenuContribution } from '@theia/core/lib/common';
import { bindContributionProvider } from '@theia/core';
import { KeybindingContribution, WebSocketConnectionProvider, WidgetFactory, KeybindingContext } from '@theia/core/lib/browser';
import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { TerminalFrontendContribution } from './terminal-frontend-contribution';
Expand All @@ -28,6 +29,7 @@ import { TerminalActiveContext } from './terminal-keybinding-contexts';
import { createCommonBindings } from '../common/terminal-common-module';
import { TerminalService } from './base/terminal-service';
import { bindTerminalPreferences } from './terminal-preferences';
import { URLMatcher, ITerminalLinkMatcher, LocalhostMatcher } from './terminal-linkmatcher';

import '../../src/browser/terminal.css';
import 'xterm/lib/xterm.css';
Expand Down Expand Up @@ -80,4 +82,11 @@ export default new ContainerModule(bind => {
bind(IShellTerminalServer).toService(ShellTerminalServerProxy);

createCommonBindings(bind);

// link matchers
bindContributionProvider(bind, ITerminalLinkMatcher);
bind(URLMatcher).toSelf().inSingletonScope();
bind(ITerminalLinkMatcher).toService(URLMatcher);
bind(LocalhostMatcher).toSelf().inSingletonScope();
bind(ITerminalLinkMatcher).toService(LocalhostMatcher);
});
55 changes: 55 additions & 0 deletions packages/terminal/src/browser/terminal-linkmatcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/********************************************************************************
* Copyright (C) 2019 TypeFox and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { ILinkMatcherOptions } from 'xterm';
import { injectable, inject } from 'inversify';
import { MaybePromise, } from '@theia/core';
import { WindowService } from '@theia/core/lib/browser/window/window-service';

export const ITerminalLinkMatcher = Symbol('ITerminalLinkMatcher');

export interface ITerminalLinkMatcher {
getRegex(): MaybePromise<RegExp>;
readonly options?: ILinkMatcherOptions;
handler(event: MouseEvent, uri: string): void;
}

@injectable()
export class URLMatcher implements ITerminalLinkMatcher {

@inject(WindowService)
protected readonly windowService: WindowService;

getRegex() { return /(https?:\/\/)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/; }

handler = (event: MouseEvent, uri: string) => {
this.windowService.openNewWindow(uri);
}
}

@injectable()
export class LocalhostMatcher implements ITerminalLinkMatcher {

@inject(WindowService)
protected readonly windowService: WindowService;

getRegex() { return /(https?:\/\/)?(localhost|127\.0\.0\.1|0\.0\.0\.0)(:[0-9]{1,5})?([-a-zA-Z0-9@:%_\+.~#?&//=]*)/; }

handler = (event: MouseEvent, matched: string) => {
const uri = matched.startsWith('http') ? matched : `http://${matched}`;
this.windowService.openNewWindow(uri);
}
}
18 changes: 17 additions & 1 deletion packages/terminal/src/browser/terminal-widget-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import * as Xterm from 'xterm';
import { proposeGeometry } from 'xterm/lib/addons/fit/fit';
import { inject, injectable, named, postConstruct } from 'inversify';
import { Disposable, Event, Emitter, ILogger, DisposableCollection } from '@theia/core';
import { ContributionProvider, Disposable, Event, Emitter, ILogger, DisposableCollection } from '@theia/core';
import { Widget, Message, WebSocketConnectionProvider, StatefulWidget, isFirefox, MessageLoop, KeyCode } from '@theia/core/lib/browser';
import { isOSX } from '@theia/core/lib/common';
import { WorkspaceService } from '@theia/workspace/lib/browser';
Expand All @@ -30,6 +30,7 @@ import { TerminalWidgetOptions, TerminalWidget } from './base/terminal-widget';
import { MessageConnection } from 'vscode-jsonrpc';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { TerminalPreferences } from './terminal-preferences';
import { ITerminalLinkMatcher } from './terminal-linkmatcher';

export const TERMINAL_WIDGET_FACTORY_ID = 'terminal';

Expand Down Expand Up @@ -69,6 +70,7 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget
@inject(ILogger) @named('terminal') protected readonly logger: ILogger;
@inject('terminal-dom-id') public readonly id: string;
@inject(TerminalPreferences) protected readonly preferences: TerminalPreferences;
@inject(ContributionProvider) @named(ITerminalLinkMatcher) protected readonly terminalLinkMatchers: ContributionProvider<ITerminalLinkMatcher>;

protected readonly onDidOpenEmitter = new Emitter<void>();
readonly onDidOpen: Event<void> = this.onDidOpenEmitter.event;
Expand Down Expand Up @@ -109,6 +111,7 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget
selection: cssProps.selection
},
});

this.toDispose.push(this.preferences.onPreferenceChanged(change => {
const lastSeparator = change.preferenceName.lastIndexOf('.');
if (lastSeparator > 0) {
Expand All @@ -134,6 +137,7 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget
this.title.label = title;
}
});
this.registerLinkMatchers();

this.toDispose.push(this.terminalWatcher.onTerminalError(({ terminalId, error }) => {
if (terminalId === this.terminalId) {
Expand Down Expand Up @@ -162,6 +166,18 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget
this.toDispose.push(this.onDidOpenEmitter);
}

protected async registerLinkMatchers() {
for (const linkMatcher of this.terminalLinkMatchers.getContributions()) {
const regexp = await linkMatcher.getRegex();
const matcherId = this.term.registerLinkMatcher(regexp, linkMatcher.handler, linkMatcher.options);
this.toDispose.push({
dispose: () => {
this.term.deregisterLinkMatcher(matcherId);
}
});
}
}

get processId(): Promise<number> {
return (async () => {
if (!IBaseTerminalServer.validateId(this.terminalId)) {
Expand Down

0 comments on commit 62eabbb

Please sign in to comment.