diff --git a/demo/main.js b/demo/main.js
index 7feb79396..d9c0151ad 100644
--- a/demo/main.js
+++ b/demo/main.js
@@ -3,6 +3,7 @@ import * as attach from '../build/addons/attach/attach';
import * as fit from '../build/addons/fit/fit';
import * as fullscreen from '../build/addons/fullscreen/fullscreen';
import * as search from '../build/addons/search/search';
+import * as webLinks from '../build/addons/webLinks/webLinks';
import * as winptyCompat from '../build/addons/winptyCompat/winptyCompat';
@@ -10,6 +11,7 @@ Terminal.applyAddon(attach);
Terminal.applyAddon(fit);
Terminal.applyAddon(fullscreen);
Terminal.applyAddon(search);
+Terminal.applyAddon(webLinks);
Terminal.applyAddon(winptyCompat);
@@ -121,6 +123,7 @@ function createTerminal() {
term.open(terminalContainer);
term.winptyCompatInit();
+ term.webLinksInit();
term.fit();
term.focus();
diff --git a/src/Linkifier.ts b/src/Linkifier.ts
index a9617952d..f5e59f25d 100644
--- a/src/Linkifier.ts
+++ b/src/Linkifier.ts
@@ -8,30 +8,6 @@ import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkMatcherValidatio
import { MouseZone } from './input/MouseZoneManager';
import { EventEmitter } from './EventEmitter';
-const protocolClause = '(https?:\\/\\/)';
-const domainCharacterSet = '[\\da-z\\.-]+';
-const negatedDomainCharacterSet = '[^\\da-z\\.-]+';
-const domainBodyClause = '(' + domainCharacterSet + ')';
-const tldClause = '([a-z\\.]{2,6})';
-const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})';
-const localHostClause = '(localhost)';
-const portClause = '(:\\d{1,5})';
-const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';
-const pathClause = '(\\/[\\/\\w\\.\\-%~]*)*';
-const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*';
-const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?';
-const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';
-const negatedPathCharacterSet = '[^\\/\\w\\.\\-%]+';
-const bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause;
-const start = '(?:^|' + negatedDomainCharacterSet + ')(';
-const end = ')($|' + negatedPathCharacterSet + ')';
-const strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);
-
-/**
- * The ID of the built in http(s) link matcher.
- */
-const HYPERTEXT_LINK_MATCHER_ID = 0;
-
/**
* The Linkifier applies links to rows shortly after they have been refreshed.
*/
@@ -47,7 +23,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
private _mouseZoneManager: IMouseZoneManager;
private _rowsTimeoutId: number;
- private _nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID;
+ private _nextLinkMatcherId = 0;
private _rowsToLinkify: {start: number, end: number};
constructor(
@@ -58,7 +34,6 @@ export class Linkifier extends EventEmitter implements ILinkifier {
start: null,
end: null
};
- this.registerLinkMatcher(strictUrlRegex, null, { matchIndex: 1 });
}
/**
@@ -111,23 +86,6 @@ export class Linkifier extends EventEmitter implements ILinkifier {
this._rowsToLinkify.end = null;
}
- /**
- * Attaches a handler for hypertext links, overriding default behavior for
- * tandard http(s) links.
- * @param handler The handler to use, this can be cleared with null.
- */
- public setHypertextLinkHandler(handler: LinkMatcherHandler): void {
- this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].handler = handler;
- }
-
- /**
- * Attaches a validation callback for hypertext links.
- * @param callback The callback to use, this can be cleared with null.
- */
- public setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void {
- this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].validationCallback = callback;
- }
-
/**
* Registers a link matcher, allowing custom link patterns to be matched and
* handled.
@@ -139,7 +97,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
* @return The ID of the new matcher, this can be used to deregister.
*/
public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options: ILinkMatcherOptions = {}): number {
- if (this._nextLinkMatcherId !== HYPERTEXT_LINK_MATCHER_ID && !handler) {
+ if (!handler) {
throw new Error('handler must be defined');
}
const matcher: ILinkMatcher = {
@@ -222,7 +180,6 @@ export class Linkifier extends EventEmitter implements ILinkifier {
private _doLinkifyRow(rowIndex: number, text: string, matcher: ILinkMatcher, offset: number = 0): void {
// Iterate over nodes as we want to consider text nodes
let result = [];
- const isHttpLinkMatcher = matcher.id === HYPERTEXT_LINK_MATCHER_ID;
// Find the first match
let match = text.match(matcher.regex);
diff --git a/src/Terminal.ts b/src/Terminal.ts
index 721996c96..fe9a1b9cc 100644
--- a/src/Terminal.ts
+++ b/src/Terminal.ts
@@ -1326,36 +1326,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
this.customKeyEventHandler = customKeyEventHandler;
}
- /**
- * Attaches a http(s) link handler, forcing web links to behave differently to
- * regular tags. This will trigger a refresh as links potentially need to be
- * reconstructed. Calling this with null will remove the handler.
- * @param handler The handler callback function.
- */
- public setHypertextLinkHandler(handler: LinkMatcherHandler): void {
- if (!this.linkifier) {
- throw new Error('Cannot attach a hypertext link handler before Terminal.open is called');
- }
- this.linkifier.setHypertextLinkHandler(handler);
- // Refresh to force links to refresh
- this.refresh(0, this.rows - 1);
- }
-
- /**
- * Attaches a validation callback for hypertext links. This is useful to use
- * validation logic or to do something with the link's element and url.
- * @param callback The callback to use, this can
- * be cleared with null.
- */
- public setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void {
- if (!this.linkifier) {
- throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called');
- }
- this.linkifier.setHypertextValidationCallback(callback);
- // // Refresh to force links to refresh
- this.refresh(0, this.rows - 1);
- }
-
/**
* Registers a link matcher, allowing custom link patterns to be matched and
* handled.
@@ -1367,12 +1337,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* @return The ID of the new matcher, this can be used to deregister.
*/
public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number {
- if (this.linkifier) {
- const matcherId = this.linkifier.registerLinkMatcher(regex, handler, options);
- this.refresh(0, this.rows - 1);
- return matcherId;
- }
- return 0;
+ const matcherId = this.linkifier.registerLinkMatcher(regex, handler, options);
+ this.refresh(0, this.rows - 1);
+ return matcherId;
}
/**
@@ -1380,10 +1347,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* @param matcherId The link matcher's ID (returned after register)
*/
public deregisterLinkMatcher(matcherId: number): void {
- if (this.linkifier) {
- if (this.linkifier.deregisterLinkMatcher(matcherId)) {
- this.refresh(0, this.rows - 1);
- }
+ if (this.linkifier.deregisterLinkMatcher(matcherId)) {
+ this.refresh(0, this.rows - 1);
}
}
diff --git a/src/Types.ts b/src/Types.ts
index be292dbf3..b09397d7d 100644
--- a/src/Types.ts
+++ b/src/Types.ts
@@ -14,7 +14,7 @@ export type XtermListener = (...args: any[]) => void;
export type CharData = [number, string, number, number];
export type LineData = CharData[];
-export type LinkMatcherHandler = (event: MouseEvent, uri: string) => boolean | void;
+export type LinkMatcherHandler = (event: MouseEvent, uri: string) => void;
export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void;
export enum LinkHoverEventTypes {
@@ -298,8 +298,6 @@ export interface ISelectionManager {
export interface ILinkifier extends IEventEmitter {
attachToDom(mouseZoneManager: IMouseZoneManager): void;
linkifyRows(start: number, end: number): void;
- setHypertextLinkHandler(handler: LinkMatcherHandler): void;
- setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void;
registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number;
deregisterLinkMatcher(matcherId: number): boolean;
}
diff --git a/src/addons/webLinks/package.json b/src/addons/webLinks/package.json
new file mode 100644
index 000000000..f200cab4a
--- /dev/null
+++ b/src/addons/webLinks/package.json
@@ -0,0 +1,5 @@
+{
+ "name": "xterm.weblinks",
+ "main": "weblinks.js",
+ "private": true
+}
diff --git a/src/addons/webLinks/tsconfig.json b/src/addons/webLinks/tsconfig.json
new file mode 100644
index 000000000..7549370b3
--- /dev/null
+++ b/src/addons/webLinks/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es5",
+ "rootDir": ".",
+ "outDir": "../../../lib/addons/webLinks/",
+ "sourceMap": true,
+ "removeComments": true,
+ "declaration": true
+ }
+}
diff --git a/src/addons/webLinks/webLinks.ts b/src/addons/webLinks/webLinks.ts
new file mode 100644
index 000000000..5e4ac029f
--- /dev/null
+++ b/src/addons/webLinks/webLinks.ts
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+///
+
+import { Terminal } from 'xterm';
+
+const protocolClause = '(https?:\\/\\/)';
+const domainCharacterSet = '[\\da-z\\.-]+';
+const negatedDomainCharacterSet = '[^\\da-z\\.-]+';
+const domainBodyClause = '(' + domainCharacterSet + ')';
+const tldClause = '([a-z\\.]{2,6})';
+const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})';
+const localHostClause = '(localhost)';
+const portClause = '(:\\d{1,5})';
+const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';
+const pathClause = '(\\/[\\/\\w\\.\\-%~]*)*';
+const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*';
+const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?';
+const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';
+const negatedPathCharacterSet = '[^\\/\\w\\.\\-%]+';
+const bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause;
+const start = '(?:^|' + negatedDomainCharacterSet + ')(';
+const end = ')($|' + negatedPathCharacterSet + ')';
+const strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);
+
+function handleLink(event: MouseEvent, uri: string): void {
+ window.open(uri, '_blank');
+}
+
+export function webLinksInit(term: Terminal): void {
+ term.registerLinkMatcher(strictUrlRegex, handleLink, { matchIndex: 1 });
+}
+
+export function apply(terminalConstructor: typeof Terminal): void {
+ (terminalConstructor.prototype).webLinksInit = function (): void {
+ webLinksInit(this);
+ };
+}
diff --git a/src/addons/winptyCompat/package.json b/src/addons/winptyCompat/package.json
index 2e9fc1644..fc929497c 100644
--- a/src/addons/winptyCompat/package.json
+++ b/src/addons/winptyCompat/package.json
@@ -1,5 +1,5 @@
{
- "name": "xterm.winptyCompat",
+ "name": "xterm.winptycompat",
"main": "winptyCompat.js",
"private": true
}
diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts
index dd08b55e7..4de9e4edb 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -395,7 +395,7 @@ declare module 'xterm' {
* @param options Options for the link matcher.
* @return The ID of the new matcher, this can be used to deregister.
*/
- registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void, options?: ILinkMatcherOptions): number;
+ registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number;
/**
* (EXPERIMENTAL) Deregisters a link matcher if it has been registered.