The model-independent half of the ssh:// open scheme. WorkspaceRef is the value type for "where a workspace lives" — a local path or ssh://[user@]host[:port]/abs/path, with parse/uri round-tripping and a host:path display form. RecentProject carries host/port/user (back-compatible JSON: absent keys deserialize as local) so remote recents survive restarts and render with their host badge. The remaining T-332 scope — ProjectManager.current off bare Directory, open() branching, remote resolveProject — is gated on the execution layer (T-336), which is itself blocked on the T-330 footprint pick; the epic's blocker graph now encodes that gating. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
65 lines
2.4 KiB
Dart
65 lines
2.4 KiB
Dart
/// WorkspaceRef (T-332): where a workspace lives — a local repo root or
|
|
/// a repo on a remote host reached over SSH (T-329).
|
|
///
|
|
/// The remote form is written `ssh://[user@]host[:port]/abs/remote/path`
|
|
/// (host may be a `~/.ssh/config` alias — resolution happens at connect
|
|
/// time, not here). A bare string with no scheme is a local path.
|
|
library;
|
|
|
|
/// A reference to a workspace root. Immutable value type.
|
|
class WorkspaceRef {
|
|
const WorkspaceRef.local(this.path) : host = null, port = null, user = null;
|
|
|
|
const WorkspaceRef.remote({required String this.host, required this.path, this.port, this.user});
|
|
|
|
/// Remote host (or `~/.ssh/config` alias). Null means local.
|
|
final String? host;
|
|
|
|
/// SSH port; null means the ssh default / config-resolved port.
|
|
final int? port;
|
|
|
|
/// SSH user; null means the local username / config-resolved user.
|
|
final String? user;
|
|
|
|
/// Absolute workspace path — on [host] when remote, locally otherwise.
|
|
final String path;
|
|
|
|
bool get isRemote => host != null;
|
|
|
|
/// Parse either a plain local path or an `ssh://` URI. Returns null
|
|
/// for a malformed `ssh://` form (no host, or no absolute path).
|
|
static WorkspaceRef? parse(String input) {
|
|
if (!input.startsWith('ssh://')) return WorkspaceRef.local(input);
|
|
final Uri uri;
|
|
try {
|
|
uri = Uri.parse(input);
|
|
} on FormatException {
|
|
return null;
|
|
}
|
|
if (uri.host.isEmpty || uri.path.isEmpty || uri.path == '/') return null;
|
|
return WorkspaceRef.remote(host: uri.host, path: uri.path, port: uri.hasPort ? uri.port : null, user: uri.userInfo.isEmpty ? null : uri.userInfo);
|
|
}
|
|
|
|
/// The canonical string form: the bare path locally, the full
|
|
/// `ssh://` URI remotely. `parse(uri) == ref` round-trips.
|
|
String get uri {
|
|
if (!isRemote) return path;
|
|
final auth = user == null ? host! : '$user@$host';
|
|
final p = port == null ? '' : ':$port';
|
|
return 'ssh://$auth$p$path';
|
|
}
|
|
|
|
/// Compact human form for recents/switcher rows: `host:path` remotely
|
|
/// (e.g. `buildbox:/srv/repo`), the bare path locally.
|
|
String get display => isRemote ? '$host:$path' : path;
|
|
|
|
@override
|
|
bool operator ==(Object other) => other is WorkspaceRef && other.host == host && other.port == port && other.user == user && other.path == path;
|
|
|
|
@override
|
|
int get hashCode => Object.hash(host, port, user, path);
|
|
|
|
@override
|
|
String toString() => 'WorkspaceRef($uri)';
|
|
}
|