linkHandler option and default handler
This commit is contained in:
+2
-2
@@ -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('║ ║');
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Vendored
+36
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user