// What the new session dialog's folder field asks for, as the terminal's /cd // reads it (cli/internal/tui/folder_query.go): a word filters the recent // folders, then the hosts it starts; text with an @ can only be user@host; a // path lists its folder, filtered by its last segment; host: and host:path // list that folder on the host. import { rank } from './format'; export type FolderQuery = { kind: 'recent' | 'hosts' | 'listing'; dir: string; segment: string }; /** The host before the first colon and what follows it; text starting with / or ~, or with a slash before the colon, is local. */ export function splitHost(text: string): [string | null, string] { if (!text || text[0] === '/' || text[0] === '~') return [null, text]; const bracket = text.indexOf(']:'); if (text[0] === '[' && bracket > 0) return [text.slice(0, bracket + 1), text.slice(bracket + 2)]; const colon = text.indexOf(':'); const host = text.slice(0, colon); if (colon < 0 || !host || host.includes('/')) return [null, text]; return [host, text.slice(colon + 1)]; } /** A [user@]host without its user. */ export function bareHost(host: string): string { return host.slice(host.lastIndexOf('@') + 1); } export function parseQuery(q: string): FolderQuery { if (q === '~') return { kind: 'listing', dir: '~', segment: '' }; const [host, rest] = splitHost(q); if (host) { const slash = rest.lastIndexOf('/'); if (slash >= 0) return { kind: 'listing', dir: `${host}:${rest.slice(0, slash + 1)}`, segment: rest.slice(slash + 1) }; return { kind: 'listing', dir: `${host}:~`, segment: rest === '~' ? '' : rest }; } const slash = q.lastIndexOf('/'); if (slash >= 0) return { kind: 'listing', dir: q.slice(0, slash + 1), segment: q.slice(slash + 1) }; return { kind: q.includes('@') ? 'hosts' : 'recent', dir: '', segment: q }; } /** The location to ask the daemon to list for a typed folder: a remote path as scp reads it, under its home, and a relative local one under ~. */ export function folderRequest(typed: string): string { const [host, rest] = splitHost(typed); let path = rest === '/' ? rest : rest.replace(/\/$/, ''); if (path && path[0] !== '/' && path[0] !== '~') path = `~/${path}`; return host ? `${host}:${path || '~'}` : path || '~'; } /** The typed text with its last folder dropped: ~/proj/albedo/ goes up to ~/proj/. */ export function upQuery(q: string): string { const dir = q.replace(/\/$/, ''); return dir.slice(0, dir.lastIndexOf('/') + 1); } /** * The hosts the text completes: a word offers the hosts it starts (after the * folders it matches), and text with an @ matches every host by what follows * it, the typed user going with the completion. */ export function hostRows(query: FolderQuery, known: string[]): string[] { if (query.kind === 'listing') return []; const at = query.segment.lastIndexOf('@'); if (query.kind === 'hosts') { const user = query.segment.slice(0, at + 1); const named = rank(known, query.segment.slice(at + 1), bareHost); return named.map((host) => (user.length > 1 ? user + bareHost(host) : host)); } const word = query.segment.toLowerCase(); return word ? known.filter((host) => bareHost(host).toLowerCase().startsWith(word)) : []; }