146 lines
4.1 KiB
TypeScript
146 lines
4.1 KiB
TypeScript
/**
|
|
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
|
* @license MIT
|
|
*/
|
|
|
|
import { ILinkProvider, ILink, Terminal, IViewportRange } from 'xterm';
|
|
|
|
export interface ILinkProviderOptions {
|
|
hover?(event: MouseEvent, text: string, location: IViewportRange): void;
|
|
leave?(event: MouseEvent, text: string): void;
|
|
urlRegex?: RegExp;
|
|
}
|
|
|
|
export class WebLinkProvider implements ILinkProvider {
|
|
|
|
constructor(
|
|
private readonly _terminal: Terminal,
|
|
private readonly _regex: RegExp,
|
|
private readonly _handler: (event: MouseEvent, uri: string) => void,
|
|
private readonly _options: ILinkProviderOptions = {}
|
|
) {
|
|
|
|
}
|
|
|
|
public provideLinks(y: number, callback: (links: ILink[] | undefined) => void): void {
|
|
const links = LinkComputer.computeLink(y, this._regex, this._terminal, this._handler);
|
|
callback(this._addCallbacks(links));
|
|
}
|
|
|
|
private _addCallbacks(links: ILink[]): ILink[] {
|
|
return links.map(link => {
|
|
link.leave = this._options.leave;
|
|
link.hover = (event: MouseEvent, uri: string): void => {
|
|
if (this._options.hover) {
|
|
const { range } = link;
|
|
this._options.hover(event, uri, range);
|
|
}
|
|
};
|
|
return link;
|
|
});
|
|
}
|
|
}
|
|
|
|
export class LinkComputer {
|
|
public static computeLink(y: number, regex: RegExp, terminal: Terminal, activate: (event: MouseEvent, uri: string) => void): ILink[] {
|
|
const rex = new RegExp(regex.source, (regex.flags || '') + 'g');
|
|
|
|
const [line, startLineIndex] = LinkComputer._translateBufferLineToStringWithWrap(y - 1, false, terminal);
|
|
|
|
let match;
|
|
let stringIndex = -1;
|
|
const result: ILink[] = [];
|
|
|
|
while ((match = rex.exec(line)) !== null) {
|
|
const text = match[1];
|
|
if (!text) {
|
|
// 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(text, stringIndex + 1);
|
|
rex.lastIndex = stringIndex + text.length;
|
|
if (stringIndex < 0) {
|
|
// invalid stringIndex (should not have happened)
|
|
break;
|
|
}
|
|
|
|
let endX = stringIndex + text.length;
|
|
let endY = startLineIndex + 1;
|
|
|
|
while (endX > terminal.cols) {
|
|
endX -= terminal.cols;
|
|
endY++;
|
|
}
|
|
|
|
let startX = stringIndex + 1;
|
|
let startY = startLineIndex + 1;
|
|
while (startX > terminal.cols) {
|
|
startX -= terminal.cols;
|
|
startY++;
|
|
}
|
|
|
|
const range = {
|
|
start: {
|
|
x: startX,
|
|
y: startY
|
|
},
|
|
end: {
|
|
x: endX,
|
|
y: endY
|
|
}
|
|
};
|
|
|
|
result.push({ range, text, activate });
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Gets the entire line for the buffer line
|
|
* @param line The line being translated.
|
|
* @param trimRight Whether to trim whitespace to the right.
|
|
* @param terminal The terminal
|
|
*/
|
|
private static _translateBufferLineToStringWithWrap(lineIndex: number, trimRight: boolean, terminal: Terminal): [string, number] {
|
|
let lineString = '';
|
|
let lineWrapsToNext: boolean;
|
|
let prevLinesToWrap: boolean;
|
|
|
|
do {
|
|
const line = terminal.buffer.active.getLine(lineIndex);
|
|
if (!line) {
|
|
break;
|
|
}
|
|
|
|
if (line.isWrapped) {
|
|
lineIndex--;
|
|
}
|
|
|
|
prevLinesToWrap = line.isWrapped;
|
|
} while (prevLinesToWrap);
|
|
|
|
const startLineIndex = lineIndex;
|
|
|
|
do {
|
|
const nextLine = terminal.buffer.active.getLine(lineIndex + 1);
|
|
lineWrapsToNext = nextLine ? nextLine.isWrapped : false;
|
|
const line = terminal.buffer.active.getLine(lineIndex);
|
|
if (!line) {
|
|
break;
|
|
}
|
|
lineString += line.translateToString(!lineWrapsToNext && trimRight).substring(0, terminal.cols);
|
|
lineIndex++;
|
|
} while (lineWrapsToNext);
|
|
|
|
return [lineString, startLineIndex];
|
|
}
|
|
}
|