diff --git a/bin/snapshot-libs.js b/bin/snapshot-libs.js index 791e8ec868b5..481c41fc40d1 100644 --- a/bin/snapshot-libs.js +++ b/bin/snapshot-libs.js @@ -24,7 +24,6 @@ if (false) { require('xterm-addon-fit'); require('xterm-addon-image'); require('xterm-addon-search'); - require('xterm-addon-web-links'); require('xterm-addon-webgl'); require('xterm-addon-canvas'); require('xterm'); diff --git a/lib/components/term.tsx b/lib/components/term.tsx index 45c1464c97cc..fc1132c6617b 100644 --- a/lib/components/term.tsx +++ b/lib/components/term.tsx @@ -13,11 +13,12 @@ import {LigaturesAddon} from 'xterm-addon-ligatures'; import {SearchAddon} from 'xterm-addon-search'; import type {ISearchDecorationOptions} from 'xterm-addon-search'; import {Unicode11Addon} from 'xterm-addon-unicode11'; -import {WebLinksAddon} from 'xterm-addon-web-links'; import {WebglAddon} from 'xterm-addon-webgl'; +import {LinkProvider} from 'xterm-link-provider'; import type {TermProps} from '../../typings/hyper'; import terms from '../terms'; +import {FileURLMatcher, URLMatcher} from '../utils/link-matchers'; import processClipboard from '../utils/paste'; import {decorate} from '../utils/plugins'; @@ -214,10 +215,27 @@ export default class Term extends React.PureComponent< this.term.attachCustomKeyEventHandler(this.keyboardHandler); this.term.loadAddon(this.fitAddon); this.term.loadAddon(this.searchAddon); - this.term.loadAddon( - new WebLinksAddon((event, uri) => { - if (shallActivateWebLink(event)) void shell.openExternal(uri); - }) + this.term.registerLinkProvider( + new LinkProvider( + this.term, + URLMatcher.regex, + (event, text) => { + if (shallActivateWebLink(event)) void shell.openExternal(text); + }, + {}, + URLMatcher.matchIndex + ) + ); + this.term.registerLinkProvider( + new LinkProvider( + this.term, + FileURLMatcher.regex, + (event, text) => { + if (shallActivateWebLink(event)) void shell.openExternal(text); + }, + {}, + FileURLMatcher.matchIndex + ) ); this.term.open(this.termRef); diff --git a/lib/utils/link-matchers.ts b/lib/utils/link-matchers.ts new file mode 100644 index 000000000000..3ab75a6e0e38 --- /dev/null +++ b/lib/utils/link-matchers.ts @@ -0,0 +1,41 @@ +interface linkMatcher { + regex: RegExp; + matchIndex: number; +} + +// Based on the regex used in xterm-addon-web-links +export const URLMatcher: linkMatcher = (() => { + 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 pathCharacterSet = '(\\/[\\/\\w\\.\\-%~:+@]*)*([^:"\'\\s])'; + const pathClause = '(' + pathCharacterSet + ')?'; + 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 + ')'; + return {regex: new RegExp(start + protocolClause + bodyClause + end), matchIndex: 1}; +})(); + +// Simple file url matcher +export const FileURLMatcher: linkMatcher = (() => { + const protocolClause = '(file:\\/\\/)'; + const negatedDomainCharacterSet = '[^\\da-z\\.-]+'; + const pathCharacterSet = '(\\/[\\/\\w\\.\\-%~:+@]*)*([^:"\'\\s])'; + const pathClause = '(' + pathCharacterSet + ')'; + const negatedPathCharacterSet = '[^\\/\\w\\.\\-%]+'; + const bodyClause = pathClause; + const start = '(?:^|' + negatedDomainCharacterSet + ')('; + const end = ')($|' + negatedPathCharacterSet + ')'; + return {regex: new RegExp(start + protocolClause + bodyClause + end), matchIndex: 1}; +})(); diff --git a/package.json b/package.json index 6bb9d041fe0e..594bcb4acd3a 100644 --- a/package.json +++ b/package.json @@ -68,8 +68,8 @@ "xterm-addon-ligatures": "0.7.0", "xterm-addon-search": "0.13.0", "xterm-addon-unicode11": "0.6.0", - "xterm-addon-web-links": "0.9.0", - "xterm-addon-webgl": "0.16.0" + "xterm-addon-webgl": "0.16.0", + "xterm-link-provider": "1.3.1" }, "devDependencies": { "@ava/babel": "2.0.0", diff --git a/webpack.config.ts b/webpack.config.ts index 432b575f2ee2..fc0c2cda15fc 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -124,7 +124,6 @@ const config: webpack.Configuration[] = [ 'xterm-addon-fit': 'require("./node_modules/xterm-addon-fit/lib/xterm-addon-fit.js")', 'xterm-addon-image': 'require("./node_modules/xterm-addon-image/lib/xterm-addon-image.js")', 'xterm-addon-search': 'require("./node_modules/xterm-addon-search/lib/xterm-addon-search.js")', - 'xterm-addon-web-links': 'require("./node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js")', 'xterm-addon-webgl': 'require("./node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js")', 'xterm-addon-canvas': 'require("./node_modules/xterm-addon-canvas/lib/xterm-addon-canvas.js")', xterm: 'require("./node_modules/xterm/lib/xterm.js")' diff --git a/yarn.lock b/yarn.lock index c660be0fb677..efef8d32462c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8318,21 +8318,28 @@ xterm-addon-unicode11@0.6.0: resolved "https://registry.npmjs.org/xterm-addon-unicode11/-/xterm-addon-unicode11-0.6.0.tgz#733fd17bdf2ae6e818493db1d41241c999de0786" integrity sha512-5pkb8YoS/deRtNqQRw8t640mu+Ga8B2MG3RXGQu0bwgcfr8XiXIRI880TWM49ICAHhTmnOLPzIIBIjEnCq7k2A== -xterm-addon-web-links@0.9.0: - version "0.9.0" - resolved "https://registry.npmjs.org/xterm-addon-web-links/-/xterm-addon-web-links-0.9.0.tgz#c65b18588d1f613e703eb6feb7f129e7ff1c63e7" - integrity sha512-LIzi4jBbPlrKMZF3ihoyqayWyTXAwGfu4yprz1aK2p71e9UKXN6RRzVONR0L+Zd+Ik5tPVI9bwp9e8fDTQh49Q== - xterm-addon-webgl@0.16.0: version "0.16.0" resolved "https://registry.npmjs.org/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0.tgz#9872d08a64136f893b27ef9a6412136d3bf563c4" integrity sha512-E8cq1AiqNOv0M/FghPT+zPAEnvIQRDbAbkb04rRYSxUym69elPWVJ4sv22FCLBqM/3LcrmBLl/pELnBebVFKgA== +xterm-link-provider@1.3.1: + version "1.3.1" + resolved "https://registry.npmjs.org/xterm-link-provider/-/xterm-link-provider-1.3.1.tgz#69727223220dfa8758056ad6b2b5394a8454b9cb" + integrity sha512-uOlaIeUED6kJeL2nIIf5YwreO0obMhsC0RWypEUmWkz7SAQewzgwdWFjQ2He7NGcT93c4KUf8bRgAu8cV9bAYA== + dependencies: + xterm "^4.6.0" + xterm@5.3.0: version "5.3.0" resolved "https://registry.npmjs.org/xterm/-/xterm-5.3.0.tgz#867daf9cc826f3d45b5377320aabd996cb0fce46" integrity sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg== +xterm@^4.6.0: + version "4.14.1" + resolved "https://registry.npmjs.org/xterm/-/xterm-4.14.1.tgz#6884cb8fb3b83353b1a98139ea23daedf8e35796" + integrity sha512-jgzNg5BuGPwq5/M4dGnmbghZvHx2jaj+9crSEt15bV34Za49VziBmCu7zIy88zUKKiGTxeo7aVzirFSJArIMFw== + y18n@^5.0.5: version "5.0.5" resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz#8769ec08d03b1ea2df2500acef561743bbb9ab18"