1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
//! One admitted prompt moment's completion job: Directory, hosts or
//! already-owned SSH connection/cache. No I/O crosses the input path.
use super::*;
pub(super) fn run_completion(
job: CompletionJob,
cancel: worker::CancelToken,
) -> Outcome<RemoteCompletionResult> {
let CompletionJob {
query,
directory,
fallback,
sources,
history,
client,
worker,
remote,
container_worker,
} = job;
if cancel.is_cancelled() {
return Outcome::Cancelled(CancelReason::OwnerClosed);
}
match query {
RemoteCompletionQuery::Directory {
location,
segment,
container,
} => directory::run(
location,
&segment,
directory::Sources {
client: &client,
worker: &worker,
remote: &remote,
container: container.as_ref(),
container_worker: container_worker.as_deref(),
},
&cancel,
),
RemoteCompletionQuery::Hosts { partial } => {
let enumeration = strop_remote::enumerate_hosts(&sources, &history);
let items = enumeration
.complete(&partial)
.into_iter()
.map(|token| RemoteCandidate {
uri: format!("ssh://{token}"),
directory: false,
})
.collect();
Outcome::Success(RemoteCompletionResult::Candidates {
items,
source: CandidateSource::Config,
notes: enumeration.notes().to_vec(),
listed_directory: None,
})
}
RemoteCompletionQuery::Path { segment, .. } => {
let Some(dir) = directory else {
return Outcome::failed(
FailureKind::Protocol,
"path completion without a listing target",
);
};
let prefix = lenient_percent_decode(&segment);
match client.list_connected(&dir, &cancel) {
Ok(entries) => {
let mut items: Vec<RemoteCandidate> = entries
.into_iter()
.filter_map(|entry| {
// `.`/`..` have no file_name; browsing owns
// parent navigation, completion owns names.
let name = entry.file.path().file_name()?;
if !name.as_encoded_bytes().starts_with(&prefix) {
return None;
}
let directory = matches!(entry.kind, RemoteEntryKind::Directory);
let mut uri = entry.file.to_string();
if directory && !uri.ends_with('/') {
uri.push('/');
}
Some(RemoteCandidate { uri, directory })
})
.collect();
items.sort_by(|a, b| {
b.directory
.cmp(&a.directory)
.then_with(|| a.uri.cmp(&b.uri))
});
let listed = dir.to_string();
Outcome::Success(RemoteCompletionResult::Candidates {
items,
source: CandidateSource::Connection,
notes: Vec::new(),
listed_directory: Some(listed),
})
}
Err(_not_connected) => {
if cancel.is_cancelled() {
return Outcome::Cancelled(CancelReason::OwnerClosed);
}
if let Some(cached) = fallback {
return Outcome::Success(RemoteCompletionResult::Candidates {
items: cached,
source: CandidateSource::Cache,
notes: Vec::new(),
listed_directory: None,
});
}
Outcome::Success(RemoteCompletionResult::ConnectRequired {
endpoint: endpoint_display(&dir),
})
}
}
}
}
}