Add the ability to edit remote directories over SSH (#14530)
This is a first step towards allowing you to edit remote projects directly over SSH. We'll start with a pretty bare-bones feature set, and incrementally add further features. ### Todo Distribution * [x] Build nightly releases of `zed-remote-server` binaries * [x] linux (arm + x86) * [x] mac (arm + x86) * [x] Build stable + preview releases of `zed-remote-server` * [x] download and cache remote server binaries as needed when opening ssh project * [x] ensure server has the latest version of the binary Auth * [x] allow specifying password at the command line * [x] auth via ssh keys * [x] UI password prompt Features * [x] upload remote server binary to server automatically * [x] opening directories * [x] tracking file system updates * [x] opening, editing, saving buffers * [ ] file operations (rename, delete, create) * [ ] git diffs * [ ] project search Release Notes: - N/A --------- Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
This commit is contained in:
parent
7733bf686b
commit
b9a53ffa0b
50 changed files with 2194 additions and 250 deletions
|
@ -112,7 +112,18 @@ impl App {
|
|||
log::info!("GPUI was compiled in test mode");
|
||||
|
||||
Self(AppContext::new(
|
||||
current_platform(),
|
||||
current_platform(false),
|
||||
Arc::new(()),
|
||||
http::client(None),
|
||||
))
|
||||
}
|
||||
|
||||
/// Build an app in headless mode. This prevents opening windows,
|
||||
/// but makes it possible to run an application in an context like
|
||||
/// SSH, where GUI applications are not allowed.
|
||||
pub fn headless() -> Self {
|
||||
Self(AppContext::new(
|
||||
current_platform(true),
|
||||
Arc::new(()),
|
||||
http::client(None),
|
||||
))
|
||||
|
|
|
@ -64,11 +64,16 @@ pub(crate) use test::*;
|
|||
pub(crate) use windows::*;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(crate) fn current_platform() -> Rc<dyn Platform> {
|
||||
Rc::new(MacPlatform::new())
|
||||
pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
|
||||
Rc::new(MacPlatform::new(headless))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub(crate) fn current_platform() -> Rc<dyn Platform> {
|
||||
pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
|
||||
if headless {
|
||||
return Rc::new(HeadlessClient::new());
|
||||
}
|
||||
|
||||
match guess_compositor() {
|
||||
"Wayland" => Rc::new(WaylandClient::new()),
|
||||
"X11" => Rc::new(X11Client::new()),
|
||||
|
@ -99,7 +104,7 @@ pub fn guess_compositor() -> &'static str {
|
|||
|
||||
// todo("windows")
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) fn current_platform() -> Rc<dyn Platform> {
|
||||
pub(crate) fn current_platform(_headless: bool) -> Rc<dyn Platform> {
|
||||
Rc::new(WindowsPlatform::new())
|
||||
}
|
||||
|
||||
|
|
|
@ -12,10 +12,9 @@ use core_graphics::{
|
|||
event::{CGEvent, CGEventFlags, CGKeyCode},
|
||||
event_source::{CGEventSource, CGEventSourceStateID},
|
||||
};
|
||||
use ctor::ctor;
|
||||
use metal::foreign_types::ForeignType as _;
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
use std::{borrow::Cow, mem, ptr};
|
||||
use std::{borrow::Cow, mem, ptr, sync::Once};
|
||||
|
||||
const BACKSPACE_KEY: u16 = 0x7f;
|
||||
const SPACE_KEY: u16 = b' ' as u16;
|
||||
|
@ -25,13 +24,22 @@ const ESCAPE_KEY: u16 = 0x1b;
|
|||
const TAB_KEY: u16 = 0x09;
|
||||
const SHIFT_TAB_KEY: u16 = 0x19;
|
||||
|
||||
static mut EVENT_SOURCE: core_graphics::sys::CGEventSourceRef = ptr::null_mut();
|
||||
fn synthesize_keyboard_event(code: CGKeyCode) -> CGEvent {
|
||||
static mut EVENT_SOURCE: core_graphics::sys::CGEventSourceRef = ptr::null_mut();
|
||||
static INIT_EVENT_SOURCE: Once = Once::new();
|
||||
|
||||
#[ctor]
|
||||
unsafe fn build_event_source() {
|
||||
let source = CGEventSource::new(CGEventSourceStateID::Private).unwrap();
|
||||
EVENT_SOURCE = source.as_ptr();
|
||||
INIT_EVENT_SOURCE.call_once(|| {
|
||||
let source = CGEventSource::new(CGEventSourceStateID::Private).unwrap();
|
||||
unsafe {
|
||||
EVENT_SOURCE = source.as_ptr();
|
||||
};
|
||||
mem::forget(source);
|
||||
});
|
||||
|
||||
let source = unsafe { core_graphics::event_source::CGEventSource::from_ptr(EVENT_SOURCE) };
|
||||
let event = CGEvent::new_keyboard_event(source.clone(), code, true).unwrap();
|
||||
mem::forget(source);
|
||||
event
|
||||
}
|
||||
|
||||
pub fn key_to_native(key: &str) -> Cow<str> {
|
||||
|
@ -335,9 +343,7 @@ fn chars_for_modified_key(code: CGKeyCode, cmd: bool, shift: bool) -> String {
|
|||
// always returns an empty string with certain keyboards, e.g. Japanese. Synthesizing
|
||||
// an event with the given flags instead lets us access `characters`, which always
|
||||
// returns a valid string.
|
||||
let source = unsafe { core_graphics::event_source::CGEventSource::from_ptr(EVENT_SOURCE) };
|
||||
let event = CGEvent::new_keyboard_event(source.clone(), code, true).unwrap();
|
||||
mem::forget(source);
|
||||
let event = synthesize_keyboard_event(code);
|
||||
|
||||
let mut flags = CGEventFlags::empty();
|
||||
if cmd {
|
||||
|
|
|
@ -24,6 +24,7 @@ use core_foundation::{
|
|||
boolean::CFBoolean,
|
||||
data::CFData,
|
||||
dictionary::{CFDictionary, CFDictionaryRef, CFMutableDictionary},
|
||||
runloop::CFRunLoopRun,
|
||||
string::{CFString, CFStringRef},
|
||||
};
|
||||
use ctor::ctor;
|
||||
|
@ -139,6 +140,7 @@ pub(crate) struct MacPlatformState {
|
|||
foreground_executor: ForegroundExecutor,
|
||||
text_system: Arc<MacTextSystem>,
|
||||
renderer_context: renderer::Context,
|
||||
headless: bool,
|
||||
pasteboard: id,
|
||||
text_hash_pasteboard_type: id,
|
||||
metadata_pasteboard_type: id,
|
||||
|
@ -155,15 +157,16 @@ pub(crate) struct MacPlatformState {
|
|||
|
||||
impl Default for MacPlatform {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
Self::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl MacPlatform {
|
||||
pub(crate) fn new() -> Self {
|
||||
pub(crate) fn new(headless: bool) -> Self {
|
||||
let dispatcher = Arc::new(MacDispatcher::new());
|
||||
Self(Mutex::new(MacPlatformState {
|
||||
background_executor: BackgroundExecutor::new(dispatcher.clone()),
|
||||
headless,
|
||||
foreground_executor: ForegroundExecutor::new(dispatcher),
|
||||
text_system: Arc::new(MacTextSystem::new()),
|
||||
renderer_context: renderer::Context::default(),
|
||||
|
@ -394,7 +397,15 @@ impl Platform for MacPlatform {
|
|||
}
|
||||
|
||||
fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
|
||||
self.0.lock().finish_launching = Some(on_finish_launching);
|
||||
let mut state = self.0.lock();
|
||||
if state.headless {
|
||||
drop(state);
|
||||
on_finish_launching();
|
||||
unsafe { CFRunLoopRun() };
|
||||
} else {
|
||||
state.finish_launching = Some(on_finish_launching);
|
||||
drop(state);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let app: id = msg_send![APP_CLASS, sharedApplication];
|
||||
|
@ -1238,7 +1249,7 @@ mod tests {
|
|||
}
|
||||
|
||||
fn build_platform() -> MacPlatform {
|
||||
let platform = MacPlatform::new();
|
||||
let platform = MacPlatform::new(false);
|
||||
platform.0.lock().pasteboard = unsafe { NSPasteboard::pasteboardWithUniqueName(nil) };
|
||||
platform
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue