pi-tab-focus 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.
- package/LICENSE +7 -0
- package/README.md +85 -0
- package/package.json +46 -0
- package/src/config.ts +96 -0
- package/src/fullscreen-layout.ts +459 -0
- package/src/fullscreen-ui.ts +101 -0
- package/src/index.ts +1393 -0
- package/src/transcript-links.ts +132 -0
- package/src/vim-navigation.ts +1056 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getOsc8LinkAtColumn,
|
|
3
|
+
sliceByColumn,
|
|
4
|
+
stripTerminalSequences,
|
|
5
|
+
visibleWidth,
|
|
6
|
+
} from "@earendil-works/pi-tui";
|
|
7
|
+
|
|
8
|
+
const LITERAL_URL_PATTERN = /(?:https?:\/\/|file:\/\/|mailto:)[^\s<>()]+/gu;
|
|
9
|
+
const PARENTHESIZED_URL_PATTERN =
|
|
10
|
+
/^\s+\(((?:https?:\/\/|file:\/\/|mailto:)[^\s<>()]+)\)/u;
|
|
11
|
+
const SGR_PATTERN = /\x1b\[([0-9:;]*)m/gu;
|
|
12
|
+
const OSC8_PATTERN = /\x1b\]8;[^;]*;([^\x07\x1b]*)(?:\x07|\x1b\\)/gu;
|
|
13
|
+
|
|
14
|
+
function literalUrlAtColumn(line: string, col: number): string | undefined {
|
|
15
|
+
const stripped = stripTerminalSequences(line);
|
|
16
|
+
for (const match of stripped.matchAll(LITERAL_URL_PATTERN)) {
|
|
17
|
+
const text = match[0];
|
|
18
|
+
const start = visibleWidth(stripped.slice(0, match.index));
|
|
19
|
+
const end = start + visibleWidth(text);
|
|
20
|
+
if (col >= start && col < end) return text;
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function underlineRanges(line: string): Array<{ start: number; end: number }> {
|
|
26
|
+
const ranges: Array<{ start: number; end: number }> = [];
|
|
27
|
+
let underlined = false;
|
|
28
|
+
let start = 0;
|
|
29
|
+
|
|
30
|
+
for (const match of line.matchAll(SGR_PATTERN)) {
|
|
31
|
+
const column = visibleWidth(stripTerminalSequences(line.slice(0, match.index)));
|
|
32
|
+
const rawParams = match[1];
|
|
33
|
+
const params = rawParams === "" ? ["0"] : rawParams.split(";");
|
|
34
|
+
let next: boolean = underlined;
|
|
35
|
+
|
|
36
|
+
for (const param of params) {
|
|
37
|
+
if (param === "" || param === "0" || param === "24" || param === "4:0") {
|
|
38
|
+
next = false;
|
|
39
|
+
} else if (param === "4" || param.startsWith("4:")) {
|
|
40
|
+
next = true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!underlined && next) start = column;
|
|
45
|
+
if (underlined && !next && column > start) ranges.push({ start, end: column });
|
|
46
|
+
underlined = next;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (underlined) {
|
|
50
|
+
const end = visibleWidth(stripTerminalSequences(line));
|
|
51
|
+
if (end > start) ranges.push({ start, end });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return ranges;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function markdownFallbackUrlAtColumn(
|
|
58
|
+
line: string,
|
|
59
|
+
col: number,
|
|
60
|
+
): string | undefined {
|
|
61
|
+
const range = underlineRanges(line).find(
|
|
62
|
+
(candidate) => col >= candidate.start && col < candidate.end,
|
|
63
|
+
);
|
|
64
|
+
if (!range) return undefined;
|
|
65
|
+
|
|
66
|
+
const stripped = stripTerminalSequences(line);
|
|
67
|
+
const suffix = sliceByColumn(
|
|
68
|
+
stripped,
|
|
69
|
+
range.end,
|
|
70
|
+
Math.max(0, visibleWidth(stripped) - range.end),
|
|
71
|
+
true,
|
|
72
|
+
);
|
|
73
|
+
return PARENTHESIZED_URL_PATTERN.exec(suffix)?.[1];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function transcriptLinkAtColumn(
|
|
77
|
+
line: string,
|
|
78
|
+
col: number,
|
|
79
|
+
): string | undefined {
|
|
80
|
+
return (
|
|
81
|
+
getOsc8LinkAtColumn(line, col) ??
|
|
82
|
+
literalUrlAtColumn(line, col) ??
|
|
83
|
+
markdownFallbackUrlAtColumn(line, col)
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function firstTranscriptLink(lines: string[]): string | undefined {
|
|
88
|
+
for (const line of lines) {
|
|
89
|
+
const candidates: Array<{ col: number; url: string }> = [];
|
|
90
|
+
const stripped = stripTerminalSequences(line);
|
|
91
|
+
|
|
92
|
+
for (const match of line.matchAll(OSC8_PATTERN)) {
|
|
93
|
+
const url = match[1];
|
|
94
|
+
if (!url) continue;
|
|
95
|
+
candidates.push({
|
|
96
|
+
col: visibleWidth(stripTerminalSequences(line.slice(0, match.index))),
|
|
97
|
+
url,
|
|
98
|
+
});
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
for (const match of stripped.matchAll(LITERAL_URL_PATTERN)) {
|
|
103
|
+
candidates.push({
|
|
104
|
+
col: visibleWidth(stripped.slice(0, match.index)),
|
|
105
|
+
url: match[0],
|
|
106
|
+
});
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const lineWidth = visibleWidth(stripped);
|
|
111
|
+
for (const range of underlineRanges(line)) {
|
|
112
|
+
const suffix = sliceByColumn(
|
|
113
|
+
stripped,
|
|
114
|
+
range.end,
|
|
115
|
+
Math.max(0, lineWidth - range.end),
|
|
116
|
+
true,
|
|
117
|
+
);
|
|
118
|
+
const url = PARENTHESIZED_URL_PATTERN.exec(suffix)?.[1];
|
|
119
|
+
if (!url) continue;
|
|
120
|
+
candidates.push({ col: range.start, url });
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let first: { col: number; url: string } | undefined;
|
|
125
|
+
for (const candidate of candidates) {
|
|
126
|
+
if (!first || candidate.col < first.col) first = candidate;
|
|
127
|
+
}
|
|
128
|
+
if (first) return first.url;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|