forked from xtermjs/xterm.js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathOscLinkProvider.ts
128 lines (117 loc) · 4.05 KB
/
OscLinkProvider.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/**
* Copyright (c) 2022 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IBufferRange, ILink, ILinkProvider } from 'browser/Types';
import { CellData } from 'common/buffer/CellData';
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 {
const line = this._bufferService.buffer.lines.get(y - 1);
if (!line) {
callback(undefined);
return;
}
const result: ILink[] = [];
const linkHandler = this._optionsService.rawOptions.linkHandler;
const cell = new CellData();
const lineLength = line.getTrimmedLength();
let currentLinkId = -1;
let currentStart = -1;
let finishLink = false;
for (let x = 0; x < lineLength; x++) {
// Minor optimization, only check for content if there isn't a link in case the link ends with
// a null cell
if (currentStart === -1 && !line.hasContent(x)) {
continue;
}
line.loadCell(x, cell);
if (cell.hasExtendedAttrs() && cell.extended.urlId) {
if (currentStart === -1) {
currentStart = x;
currentLinkId = cell.extended.urlId;
continue;
} else {
finishLink = cell.extended.urlId !== currentLinkId;
}
} else {
if (currentStart !== -1) {
finishLink = true;
}
}
if (finishLink || (currentStart !== -1 && x === lineLength - 1)) {
const text = this._oscLinkService.getLinkData(currentLinkId)?.uri;
if (text) {
// These ranges are 1-based
const range: IBufferRange = {
start: {
x: currentStart + 1,
y
},
end: {
// Offset end x if it's a link that ends on the last cell in the line
x: x + (!finishLink && x === lineLength - 1 ? 1 : 0),
y
}
};
let ignoreLink = false;
if (!linkHandler?.allowNonHttpProtocols) {
try {
const parsed = new URL(text);
if (!['http:', 'https:'].includes(parsed.protocol)) {
ignoreLink = true;
}
} catch (e) {
// Ignore invalid URLs to prevent unexpected behaviors
ignoreLink = true;
}
}
if (!ignoreLink) {
// OSC links always use underline and pointer decorations
result.push({
text,
range,
activate: (e, text) => (linkHandler ? linkHandler.activate(e, text, range) : defaultActivate(e, text)),
hover: (e, text) => linkHandler?.hover?.(e, text, range),
leave: (e, text) => linkHandler?.leave?.(e, text, range)
});
}
}
finishLink = false;
// Clear link or start a new link if one starts immediately
if (cell.hasExtendedAttrs() && cell.extended.urlId) {
currentStart = x;
currentLinkId = cell.extended.urlId;
} else {
currentStart = -1;
currentLinkId = -1;
}
}
}
// TODO: Handle fetching and returning other link ranges to underline other links with the same
// id
callback(result);
}
}
function defaultActivate(e: MouseEvent, uri: string): void {
const answer = confirm(`Do you want to navigate to ${uri}?\n\nWARNING: This link could potentially be dangerous`);
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');
}
}
}