linkHandler option and default handler

This commit is contained in:
Daniel Imms
2022-08-07 07:35:46 -07:00
parent ace99d125d
commit d9b8c6a838
6 changed files with 72 additions and 10 deletions
+2 -2
View File
@@ -835,12 +835,12 @@ function addAnsiHyperlink() {
term.writeln('\x1b]8;;https://xtermjs.org\x07https://xtermjs.org\x1b]8;;\x07\x1b[C<- null cell');
term.writeln(`\nAdjacent links:`);
term.writeln('\x1b]8;;https://github.com\x07GitHub\x1b]8;;https://xtermjs.org\x07xterm.js\x1b]8;;\x07');
term.writeln(`\nShared ID link:`);
term.writeln(`\nShared ID link (underline should be shared):`);
term.writeln('╔════╗');
term.writeln('║\x1b]8;id=testid;https://github.com\x07GitH\x1b]8;;\x07║');
term.writeln('║\x1b]8;id=testid;https://github.com\x07ub\x1b]8;;\x07 ║');
term.writeln('╚════╝');
term.writeln(`\nWrapped link with no ID (not meant to share underline):`);
term.writeln(`\nWrapped link with no ID (not necessarily meant to share underline):`);
term.writeln('╔════╗');
term.writeln('║ ║');
term.writeln('║ ║');
+29 -5
View File
@@ -5,16 +5,22 @@
import { ILink, ILinkProvider } from 'browser/Types';
import { CellData } from 'common/buffer/CellData';
import { IBufferService, IOscLinkService } from 'common/services/Services';
import { IBufferService, IOptionsService, IOscLinkService } from 'common/services/Services';
export class OscLinkProvider implements ILinkProvider {
constructor(
@IBufferService private readonly _bufferService: IBufferService,
@IOptionsService private readonly _optionsService: IOptionsService,
@IOscLinkService private readonly _oscLinkService: IOscLinkService
) {
}
public provideLinks(y: number, callback: (links: ILink[] | undefined) => void): void {
// OSC links only work when a link handler is set
// if (this._optionsService.rawOptions.linkHandler === null) {
// return;
// }
const line = this._bufferService.buffer.lines.get(y - 1);
if (!line) {
callback(undefined);
@@ -52,12 +58,14 @@ export class OscLinkProvider implements ILinkProvider {
if (finishLink || (currentStart !== -1 && x === lineLength - 1)) {
const text = this._oscLinkService.getLinkData(currentLinkId)?.uri;
if (text) {
const linkHandler = this._optionsService.rawOptions.linkHandler;
// OSC links always use underline and pointer decorations
result.push({
text,
// These ranges are 1-based
range: {
start: {
// TODO: Adjacent links aren't working correctly
x: currentStart + 1,
y
},
@@ -67,10 +75,9 @@ export class OscLinkProvider implements ILinkProvider {
y
}
},
activate(e, text) {
console.log('activate!', text);
}
// TODO: Embedder API to handle hover
activate: linkHandler?.activate || defaultActivate,
hover: linkHandler?.hover,
leave: linkHandler?.leave
});
}
currentStart = -1;
@@ -82,3 +89,20 @@ export class OscLinkProvider implements ILinkProvider {
callback(result);
}
}
function defaultActivate(e: MouseEvent, uri: string): void {
const answer = confirm(`Do you want to navigate to ${uri}?`);
if (answer) {
const newWindow = window.open();
if (newWindow) {
try {
newWindow.opener = null;
} catch {
// no-op, Electron can throw
}
newWindow.location.href = uri;
} else {
console.warn('Opening link blocked as opener could not be cleared');
}
}
}
+1
View File
@@ -24,6 +24,7 @@ export const DEFAULT_OPTIONS: Readonly<ITerminalOptions> = {
fontWeightBold: 'bold',
lineHeight: 1.0,
letterSpacing: 0,
linkHandler: null,
logLevel: 'info',
scrollback: 1000,
scrollSensitivity: 1,
+2 -2
View File
@@ -24,7 +24,6 @@ export class OscLinkService implements IOscLinkService {
}
public registerLink(data: IOscLinkData): number {
// TODO: Extend range where appropriate
const buffer = this._bufferService.buffer;
// Links with no id will only ever be registered a single time
@@ -39,6 +38,7 @@ export class OscLinkService implements IOscLinkService {
return entry.id;
}
// Add the line to the link if it already exists
const castData = data as Required<IOscLinkData>;
const key = this._getEntryIdKey(castData);
const match = this._entriesWithId.get(key);
@@ -47,6 +47,7 @@ export class OscLinkService implements IOscLinkService {
return match.id;
}
// Create the link
const entry: IOscLinkEntryWithId = {
id: this._nextId++,
key: this._getEntryIdKey(castData),
@@ -64,7 +65,6 @@ export class OscLinkService implements IOscLinkService {
return;
}
if (link.lines.every(e => e.line !== y)) {
console.log(' add new line', y);
link.lines.push(this._bufferService.buffer.addMarker(y));
}
}
+2 -1
View File
@@ -7,7 +7,7 @@ import { IEvent, IEventEmitter } from 'common/EventEmitter';
import { IBuffer, IBufferSet } from 'common/buffer/Types';
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource, IDisposable, IColorRGB, IColor, CursorStyle, IOscLinkData } from 'common/Types';
import { createDecorator } from 'common/services/ServiceRegistry';
import { IDecorationOptions, IDecoration } from 'xterm';
import { IDecorationOptions, IDecoration, ILinkHandler } from 'xterm';
export const IBufferService = createDecorator<IBufferService>('BufferService');
export interface IBufferService {
@@ -223,6 +223,7 @@ export interface ITerminalOptions {
fontWeightBold: FontWeight;
letterSpacing: number;
lineHeight: number;
linkHandler: ILinkHandler | null;
logLevel: LogLevel;
macOptionIsMeta: boolean;
macOptionClickForcesSelection: boolean;
+36
View File
@@ -128,6 +128,14 @@ declare module 'xterm' {
*/
lineHeight?: number;
/**
* The handler for OSC 8 hyperlinks. Links will use the `confirm` browser
* API if no link handler is set. Consider the security of users when using
* this, there should be some tooltip or prompt when hovering or activating
* the link.
*/
linkHandler?: ILinkHandler | null;
/**
* What log level to use, this will log for all levels below and including
* what is set:
@@ -1101,6 +1109,34 @@ declare module 'xterm' {
y: number;
}
/**
* A link handler for OSC 8 hyperlinks.
*/
interface ILinkHandler {
/**
* Calls when the link is activated.
* @param event The mouse event triggering the callback.
* @param text The text of the link.
*/
activate(event: MouseEvent, text: string): void;
/**
* Called when the mouse hovers the link. To use this to create a DOM-based hover tooltip,
* create the hover element within `Terminal.element` and add the `xterm-hover` class to it,
* that will cause mouse events to not fall through and activate other links.
* @param event The mouse event triggering the callback.
* @param text The text of the link.
*/
hover?(event: MouseEvent, text: string): void;
/**
* Called when the mouse leaves the link.
* @param event The mouse event triggering the callback.
* @param text The text of the link.
*/
leave?(event: MouseEvent, text: string): void;
}
/**
* A custom link provider.
*/