Better conpty (#39090)

Jakub Konka and Cole Miller created

Closes #22657
Closes #37863

# Background

Several users have noted that the terminal shipped with Zed on Windows
is either misbehaving or missing several features including lack of
consistent clearing behaviour. After some investigation which included
digging into the Microsoft Terminal project and VSCode editor, it turns
out that the pseudoconsole provided by Windows OS is severely outdated
which manifests itself in problems such as lack of clearing behaviour,
etc. Interestingly however, neither MS Terminal nor VSCode exhibit this
limitation so the question was why. Enter custom `conpty.dll` and
`OpenConsole.exe` runtime. These are updated, developed in MS Terminal
tree subprojects that aim to replace native Windows API as well as
augment the `conhost.exe` process that runs by default in Windows. They
also fix all the woes we had with the terminal on Windows (there is a
chance that ctrl-c behaviour is also fixed with these, but still need to
double check that this is indeed the case). This PR ensures that Zed
also benefits from the update pseudoconsole API.

# Proposed approach

It is possible to fork MS Terminal and instrument the necessary
subprojects for Rust-awareness (using `cc-rs` or otherwise to compile
the C++ code and then embed it in Rust-produced binaries for easier
inclusion in projects) but it comes at a cost of added complexity,
maintenance burden, etc. An alternative approach was proposed by
@reflectronic to download the binary from the official Nuget repo and
bundle it for release/local use. This PR aims to do just that.

There are two bits to this PR:
1. ~~when building Zed locally, and more specifically, when the `zed`
crate is being built, we will strive to download and unpack the binaries
into `OUT_DIR` provided by `cargo`. We will then set
`ZED_CONPTY_INSTALL_PATH=${OUT_DIR}/conpty` and use it at runtime in Zed
binary to tweak the loader's search path with that additional path. This
effectively ensures that Zed built from source on Windows has full
terminal support.~~ EDIT: after several discussions offline, we've
decided that keeping it minimal will serve us best, meaning: when
developing locally it is up to the developer of Zed to install
`conpty.dll` and put it in the loader's search path.
2. when bundling Windows release, we will download and unpack the nuget
package into Zed's bundle which will ensure it is installed in the same
directory as Zed by the installer.

**Note** I realise that 1. may actually not be needed - instead we could
leave that bit for the user who wants to run Zed from source to ensure
that they have `conpty.dll` in the loader's search path. I'd love to
hear opinions on this!

Release Notes:

- N/A

---------

Co-authored-by: Cole Miller <cole@zed.dev>

Change summary

Cargo.lock                |  9 +++++----
Cargo.toml                |  1 +
crates/zed/Cargo.toml     |  1 +
crates/zed/src/main.rs    | 12 ++++++++++++
script/bundle-windows.ps1 | 11 +++++++++++
5 files changed, 30 insertions(+), 4 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -4111,9 +4111,9 @@ dependencies = [
 
 [[package]]
 name = "crc"
-version = "3.2.1"
+version = "3.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
+checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675"
 dependencies = [
  "crc-catalog",
 ]
@@ -14573,9 +14573,9 @@ checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
 
 [[package]]
 name = "sha2"
-version = "0.10.8"
+version = "0.10.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
 dependencies = [
  "cfg-if",
  "cpufeatures",
@@ -20347,6 +20347,7 @@ dependencies = [
  "web_search",
  "web_search_providers",
  "windows 0.61.1",
+ "windows-sys 0.61.0",
  "winresource",
  "workspace",
  "workspace-hack",

Cargo.toml 🔗

@@ -714,6 +714,7 @@ wasmtime = { version = "29", default-features = false, features = [
 wasmtime-wasi = "29"
 which = "6.0.0"
 windows-core = "0.61"
+windows-sys = "0.61"
 wit-component = "0.221"
 workspace-hack = "0.1.0"
 yawc = "0.2.5"

crates/zed/Cargo.toml 🔗

@@ -170,6 +170,7 @@ zlog_settings.workspace = true
 
 [target.'cfg(target_os = "windows")'.dependencies]
 windows.workspace = true
+windows-sys.workspace = true
 
 [target.'cfg(target_os = "windows")'.build-dependencies]
 winresource = "0.1"

crates/zed/src/main.rs 🔗

@@ -259,6 +259,9 @@ pub fn main() {
             .unwrap_or("unknown"),
     );
 
+    #[cfg(windows)]
+    check_for_conpty_dll();
+
     let app = Application::new().with_assets(Assets);
 
     let system_id = app.background_executor().block(system_id()).ok();
@@ -1514,3 +1517,12 @@ fn dump_all_gpui_actions() {
     )
     .unwrap();
 }
+
+#[cfg(windows)]
+fn check_for_conpty_dll() {
+    use windows_sys::{Win32::System::LibraryLoader::LoadLibraryW, w};
+    let hmodule = unsafe { LoadLibraryW(w!("conpty.dll")) };
+    if hmodule.is_null() {
+        log::warn!("Failed to load conpty.dll. Terminal will work with reduced functionality.");
+    }
+}

script/bundle-windows.ps1 🔗

@@ -146,6 +146,14 @@ function DownloadAMDGpuServices {
     Expand-Archive -Path $zipPath -DestinationPath "." -Force
 }
 
+function DownloadConpty {
+    # If you update the Conpty version, please also update the version in `crates/zed/build.rs`.
+    $url = "https://www.nuget.org/api/v2/package/CI.Microsoft.Windows.Console.ConPTY/1.22.250314001"
+    $zipPath = ".\conpty.zip"
+    Invoke-WebRequest -Uri $url -OutFile $zipPath
+    Expand-Archive -Path $zipPath -DestinationPath ".\conpty" -Force
+}
+
 function CollectFiles {
     Move-Item -Path "$innoDir\zed_explorer_command_injector.appx" -Destination "$innoDir\appx\zed_explorer_command_injector.appx" -Force
     Move-Item -Path "$innoDir\zed_explorer_command_injector.dll" -Destination "$innoDir\appx\zed_explorer_command_injector.dll" -Force
@@ -153,6 +161,8 @@ function CollectFiles {
     Move-Item -Path "$innoDir\zed.sh" -Destination "$innoDir\bin\zed" -Force
     Move-Item -Path "$innoDir\auto_update_helper.exe" -Destination "$innoDir\tools\auto_update_helper.exe" -Force
     Move-Item -Path ".\AGS_SDK-6.3.0\ags_lib\lib\amd_ags_x64.dll" -Destination "$innoDir\amd_ags_x64.dll" -Force
+    Move-Item -Path ".\conpty\build\native\runtimes\*" -Destination "$innoDir\bin" -Force
+    Move-Item -Path ".\conpty\runtimes\win10-x64\native\conpty.dll" -Destination "$innoDir\bin\conpty.dll" -Force
 }
 
 function BuildInstaller {
@@ -280,6 +290,7 @@ MakeAppx
 SignZedAndItsFriends
 ZipZedAndItsFriendsDebug
 DownloadAMDGpuServices
+DownloadConpty
 CollectFiles
 BuildInstaller