46 lines
1.8 KiB
JavaScript
46 lines
1.8 KiB
JavaScript
// File paths in the terminal become links (devdesk#21): click one in Claude's
|
|||
|
|
// output (web/src/files.js, ./notes.md:12, /root/code/x/y.py:3:9) to open it in
|
||
|
|
// the Files pane, at that line if there is one. Only paths that are files of
|
||
|
|
// the session become links, so words and URLs are left alone.
|
||
|
|
|
||
|
|
// A path: optional ./ ../ or /, segments, and either a slash or an extension;
|
||
|
|
// then an optional :line[:col].
|
||
|
|
const PATH_RE = /(?<![\w/.@~+-])((?:\.{1,2}\/|\/)?(?:[\w@+~-][\w.@+~-]*\/)*[\w@+~-][\w.@+~-]*(?:\.[A-Za-z0-9]{1,10})?)(?::(\d+)(?::(\d+))?)?/g;
|
||
|
|
const URL_RE = /\b[a-z][a-z0-9+.-]*:\/\/\S+/gi;
|
||
|
|
|
||
|
|
export function findPaths(text) {
|
||
|
|
const urls = [...text.matchAll(URL_RE)].map((m) => [m.index, m.index + m[0].length]);
|
||
|
|
const out = [];
|
||
|
|
for (const m of text.matchAll(PATH_RE)) {
|
||
|
|
const candidate = m[1];
|
||
|
|
if (!candidate.includes('/') && !/\.[A-Za-z0-9]{1,10}$/.test(candidate)) continue;
|
||
|
|
if (urls.some(([a, b]) => m.index >= a && m.index < b)) continue;
|
||
|
|
out.push({ index: m.index, length: m[0].length, path: candidate, line: m[2] ? Number(m[2]) : undefined });
|
||
|
|
}
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function registerPathLinks(term, { resolve, open }) {
|
||
|
|
term.registerLinkProvider({
|
||
|
|
provideLinks(y, callback) {
|
||
|
|
const line = term.buffer.active.getLine(y - 1);
|
||
|
|
if (!line) {
|
||
|
|
callback(undefined);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const links = [];
|
||
|
|
for (const found of findPaths(line.translateToString(true))) {
|
||
|
|
const path = resolve(found.path);
|
||
|
|
if (!path) continue;
|
||
|
|
links.push({
|
||
|
|
range: { start: { x: found.index + 1, y }, end: { x: found.index + found.length, y } },
|
||
|
|
text: path,
|
||
|
|
decorations: { underline: true, pointerCursor: true },
|
||
|
|
activate: () => open(path, found.line),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
callback(links.length ? links : undefined);
|
||
|
|
},
|
||
|
|
});
|
||
|
|
}
|