@jacksontian/mwt 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,353 @@
1
+ import { FitAddon } from '/vendor/xterm/addon-fit.mjs';
2
+ import { WebLinksAddon } from '/vendor/xterm/addon-web-links.mjs';
3
+
4
+ // xterm.js is loaded as UMD via <script> tag, access from global
5
+ const { Terminal } = globalThis;
6
+
7
+ export class TerminalManager {
8
+ constructor(wsClient, container, themeManager) {
9
+ this.wsClient = wsClient;
10
+ this.container = container;
11
+ this.themeManager = themeManager;
12
+ this.terminals = new Map(); // id -> { term, fitAddon, element, resizeObserver }
13
+ this.counter = 0;
14
+ this.activeId = null;
15
+ this.changeCallbacks = [];
16
+ this.activityCallbacks = [];
17
+ this.activitySet = new Set(); // terminal IDs with unread activity
18
+
19
+ // Update all terminals when theme changes
20
+ if (themeManager) {
21
+ themeManager.onChange(() => {
22
+ const theme = themeManager.getTerminalTheme();
23
+ for (const [, entry] of this.terminals) {
24
+ entry.term.options.theme = theme;
25
+ }
26
+ });
27
+ }
28
+ }
29
+
30
+ _buildTerminal(id) {
31
+ const num = id.replace('term-', '');
32
+
33
+ // Create DOM
34
+ const pane = document.createElement('div');
35
+ pane.className = 'terminal-pane';
36
+ pane.dataset.id = id;
37
+
38
+ const header = document.createElement('div');
39
+ header.className = 'pane-header';
40
+
41
+ const title = document.createElement('span');
42
+ title.className = 'pane-title';
43
+ title.textContent = `Terminal ${num}`;
44
+
45
+ const headerActions = document.createElement('div');
46
+ headerActions.className = 'pane-actions';
47
+
48
+ const maximizeBtn = document.createElement('button');
49
+ maximizeBtn.className = 'pane-maximize';
50
+ maximizeBtn.title = 'Maximize terminal';
51
+ maximizeBtn.innerHTML = '<svg class="icon-maximize" width="12" height="12" viewBox="0 0 16 16" fill="none"><rect x="2" y="2" width="12" height="12" rx="1.5" stroke="currentColor" stroke-width="1.5"/></svg>'
52
+ + '<svg class="icon-restore" width="12" height="12" viewBox="0 0 16 16" fill="none"><rect x="4" y="1" width="11" height="11" rx="1.5" stroke="currentColor" stroke-width="1.5"/><path d="M4 5H2.5A1.5 1.5 0 001 6.5v8A1.5 1.5 0 002.5 16h8a1.5 1.5 0 001.5-1.5V13" stroke="currentColor" stroke-width="1.5"/></svg>';
53
+ maximizeBtn.addEventListener('click', (e) => {
54
+ e.stopPropagation();
55
+ this.toggleMaximize(id);
56
+ });
57
+
58
+ const closeBtn = document.createElement('button');
59
+ closeBtn.className = 'pane-close';
60
+ closeBtn.textContent = '\u00d7';
61
+ closeBtn.title = 'Close terminal';
62
+ closeBtn.addEventListener('click', (e) => {
63
+ e.stopPropagation();
64
+ this.closeTerminal(id);
65
+ });
66
+
67
+ headerActions.appendChild(maximizeBtn);
68
+ headerActions.appendChild(closeBtn);
69
+
70
+ header.appendChild(title);
71
+ header.appendChild(headerActions);
72
+
73
+ const body = document.createElement('div');
74
+ body.className = 'terminal-body';
75
+
76
+ pane.appendChild(header);
77
+ pane.appendChild(body);
78
+ this.container.appendChild(pane);
79
+
80
+ // Create xterm instance
81
+ const term = new Terminal({
82
+ cursorBlink: true,
83
+ fontSize: 14,
84
+ fontFamily: "'Cascadia Code', 'Fira Code', 'JetBrains Mono', 'Menlo', monospace",
85
+ lineHeight: 1.15,
86
+ theme: this.themeManager ? this.themeManager.getTerminalTheme() : {},
87
+ allowProposedApi: true,
88
+ });
89
+
90
+ const fitAddon = new FitAddon();
91
+ const webLinksAddon = new WebLinksAddon();
92
+ term.loadAddon(fitAddon);
93
+ term.loadAddon(webLinksAddon);
94
+ term.open(body);
95
+
96
+ // Fit after layout settles
97
+ requestAnimationFrame(() => {
98
+ try { fitAddon.fit(); term.scrollToBottom(); } catch { /* ignore if not visible */ }
99
+ });
100
+
101
+ // ResizeObserver with debounce
102
+ let fitTimeout = null;
103
+ const resizeObserver = new ResizeObserver(() => {
104
+ clearTimeout(fitTimeout);
105
+ fitTimeout = setTimeout(() => {
106
+ try { fitAddon.fit(); term.scrollToBottom(); } catch { /* ignore */ }
107
+ }, 50);
108
+ });
109
+ resizeObserver.observe(body);
110
+
111
+ // Wire xterm -> server (muted during buffer replay to suppress escape sequence responses)
112
+ let muted = false;
113
+ term.onData((data) => {
114
+ if (!muted) this.wsClient.sendData(id, data);
115
+ });
116
+
117
+ term.onResize(({ cols, rows }) => {
118
+ this.wsClient.resizeTerminal(id, cols, rows);
119
+ });
120
+
121
+ // Wire server -> xterm
122
+ this.wsClient.onTerminalData(id, (data) => {
123
+ term.write(data);
124
+ if (id !== this.activeId && !this.activitySet.has(id)) {
125
+ this.activitySet.add(id);
126
+ this._notifyActivity(id);
127
+ }
128
+ });
129
+
130
+ this.wsClient.onTerminalExit(id, () => {
131
+ this.closeTerminal(id);
132
+ });
133
+
134
+ // Update pane/tab title when shell reports a new title (e.g. cwd via OSC)
135
+ term.onTitleChange((newTitle) => {
136
+ title.textContent = newTitle;
137
+ title.title = newTitle;
138
+ this._notifyChange({ type: 'title', id, title: newTitle });
139
+ });
140
+
141
+ // Track active terminal on focus and clear activity
142
+ pane.addEventListener('mousedown', () => { this.activeId = id; this.clearActivity(id); });
143
+ term.textarea?.addEventListener('focus', () => { this.activeId = id; this.clearActivity(id); });
144
+
145
+ const setMuted = (v) => { muted = v; };
146
+ this.terminals.set(id, { term, fitAddon, element: pane, resizeObserver, setMuted });
147
+ this._notifyChange({ type: 'add', id });
148
+ this.activeId = id;
149
+ term.focus();
150
+
151
+ return { term, fitAddon, element: pane, resizeObserver, setMuted };
152
+ }
153
+
154
+ createTerminal() {
155
+ const id = `term-${++this.counter}`;
156
+ const { fitAddon } = this._buildTerminal(id);
157
+
158
+ // Get initial dimensions and create server-side PTY
159
+ const dims = fitAddon.proposeDimensions();
160
+ this.wsClient.createTerminal(id, dims?.cols || 80, dims?.rows || 24);
161
+
162
+ return id;
163
+ }
164
+
165
+ restoreTerminal(id) {
166
+ // Update counter to avoid future ID collisions
167
+ const num = parseInt(id.replace('term-', ''), 10);
168
+ if (num > this.counter) this.counter = num;
169
+
170
+ const { fitAddon, setMuted } = this._buildTerminal(id);
171
+
172
+ // Mute xterm -> server during buffer replay to prevent escape sequence
173
+ // responses (cursor position reports, device attributes) from being
174
+ // interpreted as shell commands
175
+ setMuted(true);
176
+
177
+ // PTY already exists server-side, just sync the size
178
+ const dims = fitAddon.proposeDimensions();
179
+ this.wsClient.resizeTerminal(id, dims?.cols || 80, dims?.rows || 24);
180
+
181
+ return id;
182
+ }
183
+
184
+ unmuteTerminal(id) {
185
+ const entry = this.terminals.get(id);
186
+ if (entry) entry.setMuted(false);
187
+ }
188
+
189
+ unmuteAll() {
190
+ for (const [, entry] of this.terminals) {
191
+ entry.setMuted(false);
192
+ }
193
+ }
194
+
195
+ clearAll() {
196
+ for (const [id, entry] of this.terminals) {
197
+ this.wsClient.removeListeners(id);
198
+ entry.resizeObserver.disconnect();
199
+ entry.term.dispose();
200
+ entry.element.remove();
201
+ }
202
+ this.terminals.clear();
203
+ this.counter = 0;
204
+ }
205
+
206
+ closeTerminal(id) {
207
+ const entry = this.terminals.get(id);
208
+ if (!entry) return;
209
+
210
+ // If closing a maximized terminal, restore others first
211
+ if (entry.element.classList.contains('maximized')) {
212
+ for (const [, e] of this.terminals) {
213
+ e.element.classList.remove('hidden-by-maximize');
214
+ }
215
+ }
216
+
217
+ this.wsClient.closeTerminal(id);
218
+ this.wsClient.removeListeners(id);
219
+ entry.resizeObserver.disconnect();
220
+ entry.term.dispose();
221
+ entry.element.remove();
222
+ this.terminals.delete(id);
223
+
224
+ this._notifyChange({ type: 'remove', id });
225
+ }
226
+
227
+ focusTerminal(id) {
228
+ const entry = this.terminals.get(id);
229
+ if (entry) entry.term.focus();
230
+ }
231
+
232
+ fitAll() {
233
+ for (const [, entry] of this.terminals) {
234
+ requestAnimationFrame(() => {
235
+ try { entry.fitAddon.fit(); entry.term.scrollToBottom(); } catch { /* ignore */ }
236
+ });
237
+ }
238
+ }
239
+
240
+ fitTerminal(id) {
241
+ const entry = this.terminals.get(id);
242
+ if (entry) {
243
+ requestAnimationFrame(() => {
244
+ try { entry.fitAddon.fit(); entry.term.scrollToBottom(); } catch { /* ignore */ }
245
+ });
246
+ }
247
+ }
248
+
249
+ toggleMaximize(id) {
250
+ const entry = this.terminals.get(id);
251
+ if (!entry) return;
252
+
253
+ const pane = entry.element;
254
+ const isMaximized = pane.classList.contains('maximized');
255
+
256
+ if (isMaximized) {
257
+ // Restore: show all panes
258
+ pane.classList.remove('maximized');
259
+ for (const [, e] of this.terminals) {
260
+ e.element.classList.remove('hidden-by-maximize');
261
+ }
262
+ this._notifyChange({ type: 'restore', id });
263
+ } else {
264
+ // Maximize: hide others, expand this one
265
+ for (const [otherId, e] of this.terminals) {
266
+ if (otherId !== id) {
267
+ e.element.classList.add('hidden-by-maximize');
268
+ }
269
+ }
270
+ pane.classList.add('maximized');
271
+ this._notifyChange({ type: 'maximize', id });
272
+ }
273
+
274
+ requestAnimationFrame(() => this.fitAll());
275
+ }
276
+
277
+ getMaximizedId() {
278
+ for (const [id, entry] of this.terminals) {
279
+ if (entry.element.classList.contains('maximized')) return id;
280
+ }
281
+ return null;
282
+ }
283
+
284
+ getCount() {
285
+ return this.terminals.size;
286
+ }
287
+
288
+ getIds() {
289
+ return [...this.terminals.keys()];
290
+ }
291
+
292
+ getTitle(id) {
293
+ const entry = this.terminals.get(id);
294
+ if (entry) {
295
+ const paneTitle = entry.element.querySelector('.pane-title');
296
+ if (paneTitle) return paneTitle.textContent;
297
+ }
298
+ const idx = id.replace('term-', '');
299
+ return `Terminal ${idx}`;
300
+ }
301
+
302
+ focusNext() {
303
+ const ids = this.getIds();
304
+ if (ids.length <= 1) return;
305
+ const idx = ids.indexOf(this.activeId);
306
+ const nextId = ids[(idx + 1) % ids.length];
307
+ this.activeId = nextId;
308
+ this.focusTerminal(nextId);
309
+ return nextId;
310
+ }
311
+
312
+ focusPrev() {
313
+ const ids = this.getIds();
314
+ if (ids.length <= 1) return;
315
+ const idx = ids.indexOf(this.activeId);
316
+ const prevId = ids[(idx - 1 + ids.length) % ids.length];
317
+ this.activeId = prevId;
318
+ this.focusTerminal(prevId);
319
+ return prevId;
320
+ }
321
+
322
+ closeActiveTerminal() {
323
+ const id = this.activeId || this.getIds()[0];
324
+ if (id) this.closeTerminal(id);
325
+ }
326
+
327
+ toggleMaximizeActive() {
328
+ const id = this.activeId || this.getIds()[0];
329
+ if (id) this.toggleMaximize(id);
330
+ }
331
+
332
+ onChange(callback) {
333
+ this.changeCallbacks.push(callback);
334
+ }
335
+
336
+ onActivity(callback) {
337
+ this.activityCallbacks.push(callback);
338
+ }
339
+
340
+ clearActivity(id) {
341
+ if (this.activitySet.delete(id)) {
342
+ this._notifyActivity(id, true);
343
+ }
344
+ }
345
+
346
+ _notifyChange(event) {
347
+ for (const cb of this.changeCallbacks) cb(event);
348
+ }
349
+
350
+ _notifyActivity(id, cleared = false) {
351
+ for (const cb of this.activityCallbacks) cb(id, cleared);
352
+ }
353
+ }
@@ -0,0 +1,96 @@
1
+ const STORAGE_KEY = 'myterminal-theme';
2
+
3
+ const DARK_TERMINAL_THEME = {
4
+ background: '#0a0c12',
5
+ foreground: '#d4d8e8',
6
+ cursor: '#6c8cff',
7
+ cursorAccent: '#0a0c12',
8
+ selectionBackground: '#264f78',
9
+ selectionForeground: '#ffffff',
10
+ black: '#1c2035',
11
+ red: '#e05560',
12
+ green: '#7ec699',
13
+ yellow: '#e6c07b',
14
+ blue: '#6c8cff',
15
+ magenta: '#c678dd',
16
+ cyan: '#56b6c2',
17
+ white: '#d4d8e8',
18
+ brightBlack: '#555b72',
19
+ brightRed: '#ff6b76',
20
+ brightGreen: '#98e6b3',
21
+ brightYellow: '#ffd68a',
22
+ brightBlue: '#8aa4ff',
23
+ brightMagenta: '#e0a0ff',
24
+ brightCyan: '#7fd4e0',
25
+ brightWhite: '#ffffff',
26
+ };
27
+
28
+ const LIGHT_TERMINAL_THEME = {
29
+ background: '#ffffff',
30
+ foreground: '#1a1d2e',
31
+ cursor: '#4a6cf7',
32
+ cursorAccent: '#ffffff',
33
+ selectionBackground: '#b4d5fe',
34
+ selectionForeground: '#1a1d2e',
35
+ black: '#1a1d2e',
36
+ red: '#d43d4e',
37
+ green: '#2a9d4e',
38
+ yellow: '#b8860b',
39
+ blue: '#4a6cf7',
40
+ magenta: '#a626a4',
41
+ cyan: '#0e7490',
42
+ white: '#d0d5e0',
43
+ brightBlack: '#9098b0',
44
+ brightRed: '#e55966',
45
+ brightGreen: '#3bb563',
46
+ brightYellow: '#d49e1a',
47
+ brightBlue: '#6b8af9',
48
+ brightMagenta: '#c45bcf',
49
+ brightCyan: '#1a9bb5',
50
+ brightWhite: '#f0f2f5',
51
+ };
52
+
53
+ export class ThemeManager {
54
+ constructor() {
55
+ this._listeners = [];
56
+ this._mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
57
+
58
+ // Listen for OS theme changes
59
+ this._mediaQuery.addEventListener('change', () => {
60
+ if (!localStorage.getItem(STORAGE_KEY)) {
61
+ this._apply(this._systemTheme());
62
+ }
63
+ });
64
+
65
+ // Apply initial theme
66
+ const saved = localStorage.getItem(STORAGE_KEY);
67
+ this._apply(saved || this._systemTheme());
68
+ }
69
+
70
+ get current() {
71
+ return document.documentElement.dataset.theme || 'dark';
72
+ }
73
+
74
+ toggle() {
75
+ const next = this.current === 'dark' ? 'light' : 'dark';
76
+ localStorage.setItem(STORAGE_KEY, next);
77
+ this._apply(next);
78
+ }
79
+
80
+ getTerminalTheme() {
81
+ return this.current === 'dark' ? DARK_TERMINAL_THEME : LIGHT_TERMINAL_THEME;
82
+ }
83
+
84
+ onChange(callback) {
85
+ this._listeners.push(callback);
86
+ }
87
+
88
+ _systemTheme() {
89
+ return this._mediaQuery.matches ? 'dark' : 'light';
90
+ }
91
+
92
+ _apply(theme) {
93
+ document.documentElement.dataset.theme = theme;
94
+ for (const cb of this._listeners) cb(theme);
95
+ }
96
+ }
@@ -0,0 +1,136 @@
1
+ export class WSClient {
2
+ constructor() {
3
+ this.ws = null;
4
+ this.listeners = new Map(); // id -> { onData, onExit }
5
+ this.pendingMessages = [];
6
+ this.reconnectDelay = 1000;
7
+ this.onReconnectCallbacks = [];
8
+ this.onSessionRestoreCallback = null;
9
+ this.onRestoreCompleteCallback = null;
10
+ this.sessionId = this._getOrCreateSessionId();
11
+ this.connect();
12
+ }
13
+
14
+ _getOrCreateSessionId() {
15
+ const key = 'myterminal-session-id';
16
+ let id = localStorage.getItem(key);
17
+ if (!id) {
18
+ id = crypto.randomUUID();
19
+ localStorage.setItem(key, id);
20
+ }
21
+ return id;
22
+ }
23
+
24
+ connect() {
25
+ const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
26
+ this.ws = new WebSocket(`${protocol}//${location.host}?sessionId=${this.sessionId}`);
27
+
28
+ this.ws.onopen = () => {
29
+ this.reconnectDelay = 1000;
30
+ // flush pending messages
31
+ for (const msg of this.pendingMessages) {
32
+ this.ws.send(msg);
33
+ }
34
+ this.pendingMessages = [];
35
+ };
36
+
37
+ this.ws.onmessage = (event) => {
38
+ let msg;
39
+ try {
40
+ msg = JSON.parse(event.data);
41
+ } catch {
42
+ return;
43
+ }
44
+
45
+ if (msg.type === 'session-restore') {
46
+ if (this.onSessionRestoreCallback) {
47
+ this.onSessionRestoreCallback(msg.terminals);
48
+ }
49
+ return;
50
+ }
51
+
52
+ if (msg.type === 'buffer') {
53
+ const listener = this.listeners.get(msg.id);
54
+ if (listener && listener.onData) {
55
+ listener.onData(msg.data);
56
+ }
57
+ return;
58
+ }
59
+
60
+ if (msg.type === 'restore-complete') {
61
+ if (this.onRestoreCompleteCallback) {
62
+ this.onRestoreCompleteCallback();
63
+ }
64
+ return;
65
+ }
66
+
67
+ const listener = this.listeners.get(msg.id);
68
+ if (!listener) return;
69
+
70
+ if (msg.type === 'data' && listener.onData) {
71
+ listener.onData(msg.data);
72
+ } else if (msg.type === 'exit' && listener.onExit) {
73
+ listener.onExit(msg.exitCode);
74
+ }
75
+ };
76
+
77
+ this.ws.onclose = () => {
78
+ setTimeout(() => {
79
+ this.reconnectDelay = Math.min(this.reconnectDelay * 1.5, 10000);
80
+ this.connect();
81
+ for (const cb of this.onReconnectCallbacks) cb();
82
+ }, this.reconnectDelay);
83
+ };
84
+ }
85
+
86
+ send(obj) {
87
+ const data = JSON.stringify(obj);
88
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
89
+ this.ws.send(data);
90
+ } else {
91
+ this.pendingMessages.push(data);
92
+ }
93
+ }
94
+
95
+ createTerminal(id, cols, rows) {
96
+ this.send({ type: 'create', id, cols, rows });
97
+ }
98
+
99
+ sendData(id, data) {
100
+ this.send({ type: 'data', id, data });
101
+ }
102
+
103
+ resizeTerminal(id, cols, rows) {
104
+ this.send({ type: 'resize', id, cols, rows });
105
+ }
106
+
107
+ closeTerminal(id) {
108
+ this.send({ type: 'close', id });
109
+ }
110
+
111
+ onTerminalData(id, callback) {
112
+ if (!this.listeners.has(id)) this.listeners.set(id, {});
113
+ this.listeners.get(id).onData = callback;
114
+ }
115
+
116
+ onTerminalExit(id, callback) {
117
+ if (!this.listeners.has(id)) this.listeners.set(id, {});
118
+ this.listeners.get(id).onExit = callback;
119
+ }
120
+
121
+ removeListeners(id) {
122
+ this.listeners.delete(id);
123
+ }
124
+
125
+ onReconnect(callback) {
126
+ this.onReconnectCallbacks.push(callback);
127
+ }
128
+
129
+ onSessionRestore(callback) {
130
+ this.onSessionRestoreCallback = callback;
131
+ }
132
+
133
+ onRestoreComplete(callback) {
134
+ this.onRestoreCompleteCallback = callback;
135
+ }
136
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Copyright (c) 2014-2024 The xterm.js authors. All rights reserved.
3
+ * @license MIT
4
+ *
5
+ * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
6
+ * @license MIT
7
+ *
8
+ * Originally forked from (with the author's permission):
9
+ * Fabrice Bellard's javascript vt100 for jslinux:
10
+ * http://bellard.org/jslinux/
11
+ * Copyright (c) 2011 Fabrice Bellard
12
+ */
13
+ /*---------------------------------------------------------------------------------------------
14
+ * Copyright (c) Microsoft Corporation. All rights reserved.
15
+ * Licensed under the MIT License. See License.txt in the project root for license information.
16
+ *--------------------------------------------------------------------------------------------*/
17
+ var h=2,_=1,o=class{activate(e){this._terminal=e}dispose(){}fit(){let e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;let t=this._terminal._core;(this._terminal.rows!==e.rows||this._terminal.cols!==e.cols)&&(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal||!this._terminal.element||!this._terminal.element.parentElement)return;let t=this._terminal._core._renderService.dimensions;if(t.css.cell.width===0||t.css.cell.height===0)return;let s=this._terminal.options.scrollback===0?0:this._terminal.options.overviewRuler?.width||14,r=window.getComputedStyle(this._terminal.element.parentElement),l=parseInt(r.getPropertyValue("height")),a=Math.max(0,parseInt(r.getPropertyValue("width"))),i=window.getComputedStyle(this._terminal.element),n={top:parseInt(i.getPropertyValue("padding-top")),bottom:parseInt(i.getPropertyValue("padding-bottom")),right:parseInt(i.getPropertyValue("padding-right")),left:parseInt(i.getPropertyValue("padding-left"))},m=n.top+n.bottom,d=n.right+n.left,c=l-m,p=a-d-s;return{cols:Math.max(h,Math.floor(p/t.css.cell.width)),rows:Math.max(_,Math.floor(c/t.css.cell.height))}}};export{o as FitAddon};
18
+ //# sourceMappingURL=addon-fit.mjs.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Copyright (c) 2014-2024 The xterm.js authors. All rights reserved.
3
+ * @license MIT
4
+ *
5
+ * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
6
+ * @license MIT
7
+ *
8
+ * Originally forked from (with the author's permission):
9
+ * Fabrice Bellard's javascript vt100 for jslinux:
10
+ * http://bellard.org/jslinux/
11
+ * Copyright (c) 2011 Fabrice Bellard
12
+ */
13
+ /*---------------------------------------------------------------------------------------------
14
+ * Copyright (c) Microsoft Corporation. All rights reserved.
15
+ * Licensed under the MIT License. See License.txt in the project root for license information.
16
+ *--------------------------------------------------------------------------------------------*/
17
+ var v=class{constructor(e,t,n,o={}){this._terminal=e;this._regex=t;this._handler=n;this._options=o}provideLinks(e,t){let n=g.computeLink(e,this._regex,this._terminal,this._handler);t(this._addCallbacks(n))}_addCallbacks(e){return e.map(t=>(t.leave=this._options.leave,t.hover=(n,o)=>{if(this._options.hover){let{range:p}=t;this._options.hover(n,o,p)}},t))}};function k(l){try{let e=new URL(l),t=e.password&&e.username?`${e.protocol}//${e.username}:${e.password}@${e.host}`:e.username?`${e.protocol}//${e.username}@${e.host}`:`${e.protocol}//${e.host}`;return l.toLocaleLowerCase().startsWith(t.toLocaleLowerCase())}catch{return!1}}var g=class l{static computeLink(e,t,n,o){let p=new RegExp(t.source,(t.flags||"")+"g"),[i,r]=l._getWindowedLineStrings(e-1,n),s=i.join(""),a,d=[];for(;a=p.exec(s);){let u=a[0];if(!k(u))continue;let[c,h]=l._mapStrIdx(n,r,0,a.index),[m,f]=l._mapStrIdx(n,c,h,u.length);if(c===-1||h===-1||m===-1||f===-1)continue;let b={start:{x:h+1,y:c+1},end:{x:f,y:m+1}};d.push({range:b,text:u,activate:o})}return d}static _getWindowedLineStrings(e,t){let n,o=e,p=e,i=0,r="",s=[];if(n=t.buffer.active.getLine(e)){let a=n.translateToString(!0);if(n.isWrapped&&a[0]!==" "){for(i=0;(n=t.buffer.active.getLine(--o))&&i<2048&&(r=n.translateToString(!0),i+=r.length,s.push(r),!(!n.isWrapped||r.indexOf(" ")!==-1)););s.reverse()}for(s.push(a),i=0;(n=t.buffer.active.getLine(++p))&&n.isWrapped&&i<2048&&(r=n.translateToString(!0),i+=r.length,s.push(r),r.indexOf(" ")===-1););}return[s,o]}static _mapStrIdx(e,t,n,o){let p=e.buffer.active,i=p.getNullCell(),r=n;for(;o;){let s=p.getLine(t);if(!s)return[-1,-1];for(let a=r;a<s.length;++a){s.getCell(a,i);let d=i.getChars();if(i.getWidth()&&(o-=d.length||1,a===s.length-1&&d==="")){let c=p.getLine(t+1);c&&c.isWrapped&&(c.getCell(0,i),i.getWidth()===2&&(o+=1))}if(o<0)return[t,a]}t++,r=0}return[t,r]}};var _=/(https?|HTTPS?):[/]{2}[^\s"'!*(){}|\\\^<>`]*[^\s"':,.!?{}|\\\^~\[\]`()<>]/;function w(l,e){let t=window.open();if(t){try{t.opener=null}catch{}t.location.href=e}else console.warn("Opening link blocked as opener could not be cleared")}var L=class{constructor(e=w,t={}){this._handler=e;this._options=t}activate(e){this._terminal=e;let t=this._options,n=t.urlRegex||_;this._linkProvider=this._terminal.registerLinkProvider(new v(this._terminal,n,this._handler,t))}dispose(){this._linkProvider?.dispose()}};export{L as WebLinksAddon};
18
+ //# sourceMappingURL=addon-web-links.mjs.map