本地终端运行的程序可以写路径或命令,带参数;输入框右端的下拉列出装了的常用 shell (zsh、bash、sh、fish、nu、pwsh,从 /etc/shells、PATH 和 Homebrew 目录找),旁边 「选择文件…」用系统的文件选择框。留空是自动检测,新开和重新启动的本地终端生效。 只写一个 shell 时和默认一样按登录 shell 启动,带参数或不是 shell 的照写执行。 Windows 的默认 shell 原来是 cmd.exe,现在先用 PowerShell 7,其次 Windows PowerShell, 都没有才用 cmd.exe。 工作区改为每次打开、重启本地终端时向 LocalTerminalTransportProvider 要 factory, UI 测试注入的 FixedLocalTerminalTransportProvider 记下每次要的 shell。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
183 lines
5.9 KiB
Rust
183 lines
5.9 KiB
Rust
//! Fake port forwards, and the workspace that runs them.
|
|
|
|
use super::*;
|
|
|
|
/// What a fake forward does once it is started.
|
|
#[derive(Clone, Default)]
|
|
pub enum ForwardScript {
|
|
/// Logs in and listens.
|
|
#[default]
|
|
Listens,
|
|
/// Asks whether to trust the host before it listens.
|
|
AsksTrust,
|
|
/// Cannot be brought up.
|
|
Fails(&'static str),
|
|
}
|
|
|
|
/// One run of a fake forward, as the tests see it.
|
|
#[derive(Clone)]
|
|
pub struct FakeForwardRun {
|
|
pub rule: ForwardRule,
|
|
pub host: HostId,
|
|
/// The worker's end of the event channel: a test sends what a real
|
|
/// forward would report later (a dropped connection, a failure).
|
|
pub events: async_channel::Sender<ForwardEvent>,
|
|
pub stopped: Arc<AtomicUsize>,
|
|
}
|
|
|
|
/// Stands in for the SSH forward worker: opens no socket, records each run
|
|
/// and answers `Stop` with `Stopped` like the real one.
|
|
#[derive(Default)]
|
|
pub struct FakeForwardProvider {
|
|
pub script: ForwardScript,
|
|
pub runs: Arc<Mutex<Vec<FakeForwardRun>>>,
|
|
}
|
|
|
|
impl FakeForwardProvider {
|
|
pub fn with_script(script: ForwardScript) -> Self {
|
|
Self {
|
|
script,
|
|
..Self::default()
|
|
}
|
|
}
|
|
|
|
pub fn runs(&self) -> Vec<FakeForwardRun> {
|
|
self.runs
|
|
.lock()
|
|
.unwrap_or_else(|error| error.into_inner())
|
|
.clone()
|
|
}
|
|
|
|
/// How many of the runs have been told to stop and did.
|
|
pub fn stopped(&self) -> usize {
|
|
self.runs()
|
|
.iter()
|
|
.map(|run| run.stopped.load(Ordering::SeqCst))
|
|
.sum()
|
|
}
|
|
}
|
|
|
|
impl ForwardTransportProvider for FakeForwardProvider {
|
|
fn create(&self, rule: &ForwardRule, _: &HostLogin) -> Box<dyn ForwardTransport> {
|
|
Box::new(FakeForwardTransport {
|
|
script: self.script.clone(),
|
|
rule: rule.clone(),
|
|
host: rule.host,
|
|
runs: self.runs.clone(),
|
|
})
|
|
}
|
|
}
|
|
|
|
pub struct FakeForwardTransport {
|
|
pub script: ForwardScript,
|
|
pub rule: ForwardRule,
|
|
pub host: HostId,
|
|
pub runs: Arc<Mutex<Vec<FakeForwardRun>>>,
|
|
}
|
|
|
|
impl ForwardTransport for FakeForwardTransport {
|
|
fn run(
|
|
self: Box<Self>,
|
|
commands: async_channel::Receiver<ForwardCommand>,
|
|
events: async_channel::Sender<ForwardEvent>,
|
|
) {
|
|
let stopped = Arc::new(AtomicUsize::new(0));
|
|
self.runs
|
|
.lock()
|
|
.unwrap_or_else(|error| error.into_inner())
|
|
.push(FakeForwardRun {
|
|
rule: self.rule.clone(),
|
|
host: self.host,
|
|
events: events.clone(),
|
|
stopped: stopped.clone(),
|
|
});
|
|
let _ = events.send_blocking(ForwardEvent::Connecting);
|
|
match self.script {
|
|
ForwardScript::Listens => {
|
|
let _ = events.send_blocking(ForwardEvent::Listening);
|
|
}
|
|
ForwardScript::AsksTrust => {
|
|
let _ = events.send_blocking(ForwardEvent::Prompt(ConnectionPrompt::new(
|
|
7,
|
|
ConnectionPromptKind::unknown_host(
|
|
"10.0.2.5",
|
|
22,
|
|
"ssh-ed25519",
|
|
"SHA256:forward-fingerprint",
|
|
),
|
|
)));
|
|
}
|
|
ForwardScript::Fails(reason) => {
|
|
let _ = events.send_blocking(ForwardEvent::Failed(reason.to_string()));
|
|
return;
|
|
}
|
|
}
|
|
while let Ok(command) = commands.recv_blocking() {
|
|
match command {
|
|
ForwardCommand::PromptReply { reply, .. } => {
|
|
if matches!(reply, ConnectionPromptReply::TrustAndSave) {
|
|
let _ = events.send_blocking(ForwardEvent::Listening);
|
|
}
|
|
}
|
|
ForwardCommand::Stop => break,
|
|
}
|
|
}
|
|
stopped.fetch_add(1, Ordering::SeqCst);
|
|
let _ = events.send_blocking(ForwardEvent::Stopped);
|
|
}
|
|
}
|
|
|
|
/// The workspace with a fake forward provider the test keeps hold of.
|
|
pub fn open_workspace_with_forwards(
|
|
cx: &mut TestAppContext,
|
|
store: HostStore,
|
|
provider: Arc<FakeForwardProvider>,
|
|
) -> (WindowHandle<Root>, Entity<Workspace>) {
|
|
open_sized_workspace_with_forwards(cx, store, provider, size(px(1280.), px(800.)))
|
|
}
|
|
|
|
pub fn open_sized_workspace_with_forwards(
|
|
cx: &mut TestAppContext,
|
|
store: HostStore,
|
|
provider: Arc<FakeForwardProvider>,
|
|
window_size: gpui_kit::Size<gpui_kit::Pixels>,
|
|
) -> (WindowHandle<Root>, Entity<Workspace>) {
|
|
init_app(cx);
|
|
// The forward dialog's diagram plays a flow over real time, and dialogs
|
|
// slide in: both would move targets between locating and clicking them.
|
|
cx.update(|cx| cx.set_reduce_motion(true));
|
|
let mut workspace = None;
|
|
let handle = cx.open_window(window_size, |window, cx| {
|
|
let store = cx.new(|_| store);
|
|
let remote = Arc::new(FixedRemoteTerminalTransportProvider::new(Arc::new(
|
|
FakeTerminalFactory::default(),
|
|
)));
|
|
let view = cx.new(|cx| {
|
|
Workspace::new_with_services(
|
|
store,
|
|
cx.new(|_| settings_store()),
|
|
remote,
|
|
fake_local_terminals(),
|
|
Arc::new(FakeSftpProvider::default()),
|
|
Arc::new(FakeLocalDirectory::default()),
|
|
Arc::new(FakeConnectionTester::default()),
|
|
provider,
|
|
window,
|
|
cx,
|
|
)
|
|
});
|
|
workspace = Some(view.clone());
|
|
Root::new(view, window, cx)
|
|
});
|
|
(handle, workspace.expect("workspace created"))
|
|
}
|
|
|
|
/// Show the forward list and wait for it to be up.
|
|
pub async fn show_forwards(cx: &mut TestAppContext, handle: WindowHandle<Root>) {
|
|
in_frame(cx, handle, |window, cx| window.click("show-forwards", cx));
|
|
cx.wait_for(handle.into(), Duration::from_secs(2), |window, _| {
|
|
window.try_find("forward-search").is_some()
|
|
})
|
|
.await;
|
|
}
|