Use regex parser for web links

This commit is contained in:
Jon Bockhorst
2019-11-01 17:38:08 -05:00
parent ec78fc7e7c
commit e57cb8bf7e
5 changed files with 86 additions and 21 deletions
@@ -16,7 +16,7 @@ export default class WebLinkProvider implements ILinkProvider {
}
export class LinkComputer {
public static computeLink(position: IBufferCellPosition, regex: RegExp, buffer: IBuffer, handler: (event: MouseEvent, uri: string) => void): ILink | undefined {
public static computeLink(position: IBufferCellPosition, regex: RegExp, buffer: IBuffer, handle: (event: MouseEvent, uri: string) => void): ILink | undefined {
const rex = new RegExp(regex.source, (regex.flags || '') + 'g');
const bufferLine = buffer.getLine(position.y - 1);
@@ -29,9 +29,38 @@ export class LinkComputer {
let match;
let stringIndex = -1;
// while ((match = rex.exec(line)) !== null) {
// const uri = match[1];
// }
while ((match = rex.exec(line)) !== null) {
const url = match[1];
if (!url) {
// something matched but does not comply with the given matchIndex
// since this is most likely a bug the regex itself we simply do nothing here
console.log('match found without corresponding matchIndex');
break;
}
// Get index, match.index is for the outer match which includes negated chars
// therefore we cannot use match.index directly, instead we search the position
// of the match group in text again
// also correct regex and string search offsets for the next loop run
stringIndex = line.indexOf(url, stringIndex + 1);
rex.lastIndex = stringIndex + url.length;
if (stringIndex < 0) {
// invalid stringIndex (should not have happened)
break;
}
const range = {
start: {
x: stringIndex + 1,
y: position.y
},
end: {
x: stringIndex + url.length + 1,
y: position.y
}
};
return { range, url, handle };
}
}
}
@@ -45,12 +45,12 @@ export class WebLinksAddon implements ITerminalAddon {
public activate(terminal: Terminal): void {
this._terminal = terminal;
// if ('registerLinkProvider' in this._terminal) {
this._linkProvider = new WebLinkProvider(this._terminal, strictUrlRegex, this._handler);
this._terminal.registerLinkProvider(this._linkProvider);
// } else {
// this._linkMatcherId = this._terminal.registerLinkMatcher(strictUrlRegex, this._handler, this._options);
// }
if ('registerLinkProvider' in this._terminal as any) {
this._linkProvider = new WebLinkProvider(this._terminal, strictUrlRegex, this._handler);
this._terminal.registerLinkProvider(this._linkProvider);
} else {
this._linkMatcherId = this._terminal.registerLinkMatcher(strictUrlRegex, this._handler, this._options);
}
}
public dispose(): void {
+1 -1
View File
@@ -622,7 +622,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this.register(this._mouseZoneManager);
this.register(this.onScroll(() => this._mouseZoneManager.clearAll()));
this.linkifier.attachToDom(this.element, this._mouseZoneManager);
this.linkifier2.attachToDom(this.element, this._mouseService);
this.linkifier2.attachToDom(this.element, this._viewportElement, this._mouseService);
// This event listener must be registered aftre MouseZoneManager is created
this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService.onMouseDown(e)));
+45 -9
View File
@@ -14,6 +14,7 @@ import { EventEmitter, IEvent } from 'common/EventEmitter';
*/
export class Linkifier2 implements ILinkifier2 {
private _element: HTMLElement | undefined;
private _viewportElement: HTMLElement | undefined;
private _linkProviders: ILinkProvider[] = [];
private _mouseService: IMouseService | undefined;
private _linkCache: ILinkCache[] = [];
@@ -31,15 +32,22 @@ export class Linkifier2 implements ILinkifier2 {
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
this._linkProviders.push(linkProvider);
return { dispose: () => console.log('disposing link providers') };
return {
dispose: () => {
// Remove the link provider from the list
this._linkProviders = this._linkProviders.splice(this._linkProviders.indexOf(linkProvider), 1);
}
};
}
public attachToDom(element: HTMLElement, mouseService: IMouseService): void {
public attachToDom(element: HTMLElement, viewportElement: HTMLElement, mouseService: IMouseService): void {
this._element = element;
this._viewportElement = viewportElement;
this._mouseService = mouseService;
this._element.addEventListener('mousemove', this._onMouseMove.bind(this));
this._element.addEventListener('click', this._onMouseDown.bind(this));
this._viewportElement.addEventListener('scroll', this._onScroll.bind(this));
}
private _onMouseMove(event: MouseEvent): void {
@@ -62,6 +70,7 @@ export class Linkifier2 implements ILinkifier2 {
const isInPosition = this._linkAtPosition(cachedLink, position);
const range = cachedLink.range;
// Check if the mouse position contains a link
if (isInPosition && !this._linkCache[i].mouseOver) {
// Show the tooltip
this._onShowTooltip.fire(this._createLinkHoverEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x - 1, range.end.y - scrollOffset - 1, undefined));
@@ -75,13 +84,7 @@ export class Linkifier2 implements ILinkifier2 {
foundLink = true;
} else if (!isInPosition && this._linkCache[i].mouseOver) {
// Hide the tooltip
this._onHideTooltip.fire(this._createLinkHoverEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x - 1, range.end.y - scrollOffset - 1, undefined));
this._element.classList.remove('xterm-cursor-pointer');
if (cachedLink.hideTooltip) {
cachedLink.hideTooltip(event, cachedLink.url);
}
this._hideTooltip(this._element, this._linkCache[i].link, event);
this._linkCache[i].mouseOver = false;
}
}
@@ -114,9 +117,42 @@ export class Linkifier2 implements ILinkifier2 {
});
}
private _onScroll(event: Event): void {
this._invalidateCache();
}
private _handleNewLink(link: ILink | undefined): void {
if (link && !this._linkCache.find(cachedLink => cachedLink.link = link)) {
this._linkCache.push({ link, mouseOver: false });
this._bufferService.buffer.addMarker(link.range.start.y);
}
}
private _invalidateCache(): void {
if (!this._element) {
return;
}
// We want to invalidate the cache
// but we need to check if we need to hide any tooltip
for (let i = 0; i < this._linkCache.length; i++) {
if (this._linkCache[i].mouseOver) {
this._hideTooltip(this._element, this._linkCache[i].link, new MouseEvent('invalid event'));
}
}
this._linkCache = [];
}
private _hideTooltip(element: HTMLElement, link: ILink, event: MouseEvent): void {
const range = link.range;
const scrollOffset = this._bufferService.buffer.ydisp;
this._onHideTooltip.fire(this._createLinkHoverEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x - 1, range.end.y - scrollOffset - 1, undefined));
element.classList.remove('xterm-cursor-pointer');
if (link.hideTooltip) {
link.hideTooltip(event, link.url);
}
}
+1 -1
View File
@@ -98,7 +98,7 @@ export interface ILinkifier2 {
onShowTooltip: IEvent<ILinkifierEvent>;
onHideTooltip: IEvent<ILinkifierEvent>;
attachToDom(element: HTMLElement, mouseService: IMouseService): void;
attachToDom(element: HTMLElement, viewportElement: HTMLElement, mouseService: IMouseService): void;
registerLinkProvider(linkProvider: ILinkProvider): IDisposable;
}