@aurigax-ai/ghostty-web 0.0.0-stage → 0.2.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,150 @@
1
+ /**
2
+ * URL Regex Link Provider
3
+ *
4
+ * Detects plain text URLs using regex pattern matching.
5
+ * Supports common protocols but excludes file paths.
6
+ *
7
+ * This provider runs after OSC8LinkProvider, so explicit hyperlinks
8
+ * take precedence over regex-detected URLs.
9
+ */
10
+
11
+ import type { ILink, ILinkProvider } from '../types';
12
+
13
+ /**
14
+ * URL Regex Provider
15
+ *
16
+ * Detects plain text URLs on a single line using regex.
17
+ * Does not support multi-line URLs or file paths.
18
+ *
19
+ * Supported protocols:
20
+ * - https://, http://
21
+ * - mailto:
22
+ * - ftp://, ssh://, git://
23
+ * - tel:, magnet:
24
+ * - gemini://, gopher://, news:
25
+ */
26
+ export class UrlRegexProvider implements ILinkProvider {
27
+ /**
28
+ * URL regex pattern
29
+ * Matches common protocols followed by valid URL characters
30
+ * Excludes file paths (no ./ or ../ or bare /)
31
+ */
32
+ private static readonly URL_REGEX =
33
+ /(?:https?:\/\/|mailto:|ftp:\/\/|ssh:\/\/|git:\/\/|tel:|magnet:|gemini:\/\/|gopher:\/\/|news:)[\w\-.~:\/?#@!$&*+,;=%]+/gi;
34
+
35
+ /**
36
+ * Characters to strip from end of URLs
37
+ * Common punctuation that's unlikely to be part of the URL
38
+ */
39
+ private static readonly TRAILING_PUNCTUATION = /[.,;!?)\]]+$/;
40
+
41
+ constructor(private terminal: ITerminalForUrlProvider) {}
42
+
43
+ /**
44
+ * Provide all regex-detected URLs on the given row
45
+ */
46
+ provideLinks(y: number, callback: (links: ILink[] | undefined) => void): void {
47
+ const links: ILink[] = [];
48
+
49
+ const line = this.terminal.buffer.active.getLine(y);
50
+ if (!line) {
51
+ callback(undefined);
52
+ return;
53
+ }
54
+
55
+ // Convert line cells to text
56
+ const lineText = this.lineToText(line);
57
+
58
+ // Reset regex state (global flag maintains state)
59
+ UrlRegexProvider.URL_REGEX.lastIndex = 0;
60
+
61
+ // Find all URL matches in the line
62
+ let match: RegExpExecArray | null = UrlRegexProvider.URL_REGEX.exec(lineText);
63
+ while (match !== null) {
64
+ let url = match[0];
65
+ const startX = match.index;
66
+ let endX = match.index + url.length - 1; // Inclusive end
67
+
68
+ // Strip trailing punctuation
69
+ const stripped = url.replace(UrlRegexProvider.TRAILING_PUNCTUATION, '');
70
+ if (stripped.length < url.length) {
71
+ url = stripped;
72
+ endX = startX + url.length - 1;
73
+ }
74
+
75
+ // Skip if URL is too short (e.g., just "http://")
76
+ if (url.length > 8) {
77
+ links.push({
78
+ text: url,
79
+ range: {
80
+ start: { x: startX, y },
81
+ end: { x: endX, y },
82
+ },
83
+ activate: (event) => {
84
+ // Open link if Ctrl/Cmd is pressed
85
+ if (event.ctrlKey || event.metaKey) {
86
+ window.open(url, '_blank', 'noopener,noreferrer');
87
+ }
88
+ },
89
+ });
90
+ }
91
+
92
+ // Get next match
93
+ match = UrlRegexProvider.URL_REGEX.exec(lineText);
94
+ }
95
+
96
+ callback(links.length > 0 ? links : undefined);
97
+ }
98
+
99
+ /**
100
+ * Convert a buffer line to plain text string
101
+ */
102
+ private lineToText(line: IBufferLineForUrlProvider): string {
103
+ const chars: string[] = [];
104
+
105
+ for (let x = 0; x < line.length; x++) {
106
+ const cell = line.getCell(x);
107
+ if (!cell) {
108
+ chars.push(' ');
109
+ continue;
110
+ }
111
+
112
+ const codepoint = cell.getCodepoint();
113
+ // Skip null characters and control characters
114
+ if (codepoint === 0 || codepoint < 32) {
115
+ chars.push(' ');
116
+ } else {
117
+ chars.push(String.fromCodePoint(codepoint));
118
+ }
119
+ }
120
+
121
+ return chars.join('');
122
+ }
123
+
124
+ dispose(): void {
125
+ // No resources to clean up
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Minimal terminal interface required by UrlRegexProvider
131
+ */
132
+ export interface ITerminalForUrlProvider {
133
+ buffer: {
134
+ active: {
135
+ getLine(y: number): IBufferLineForUrlProvider | undefined;
136
+ };
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Minimal buffer line interface for URL detection
142
+ */
143
+ interface IBufferLineForUrlProvider {
144
+ length: number;
145
+ getCell(x: number):
146
+ | {
147
+ getCodepoint(): number;
148
+ }
149
+ | undefined;
150
+ }