Compare commits

...

17 Commits

Author SHA1 Message Date
Joseph T. Lyons
3fc49233cc zed 0.205.4 2025-09-22 14:06:12 -04:00
Conrad Irwin
08b3cb354f Move my keybinding fixes to the right platform (#38654)
In cffb883108 I put the fixed keybindings
on the wrong platform

Release Notes:

- Fix syntax node shortcuts
2025-09-22 10:26:22 -06:00
Finn Evers
bd784b42c1 editor: Properly layout expand toggles with git blame enabled (#38349)
Release Notes:

- Fixed an issue where expand toggles were too large with the git blame
deployed.
2025-09-22 11:24:42 -04:00
Marshall Bowers
e428167833 extension_host: Expand supported extension API range to include v0.7.0 (#38529)
This PR updates the version range for v0.6.0 of the extension API to
include v0.7.0.

Since we bumped the `zed_extension_api` crate's version to v0.7.0, we
need to expand this range in order for Zed clients to be able to install
extensions built against v0.7.0 of `zed_extension_api`.

Currently no extensions that target `zed_extension_api@0.7.0` can be
installed.

Release Notes:

- N/A
2025-09-22 10:16:58 -04:00
Cole Miller
eeab1649de zed 0.205.3 2025-09-20 14:27:28 -04:00
Cole Miller
f519fd7f90 acp: Fix spawning login task (#38567)
Reverts #38175, which is not correct, since in fact we do need to
pre-quote the command and arguments for the shell when using
`SpawnInTerminal` (although we should probably change the API so that
this isn't necessary). Then, applies the same fix as #38565 to fix the
root cause of being unable to spawn the login task on macOS, or in any
case where the command/args contain spaces.

Release Notes:

- Fixed being unable to login with Claude Code or Gemini using the
terminal.
2025-09-20 14:15:29 -04:00
Ben Kunkle
e6cae4e7ed macos: Fix panic when NSWindow::screen returns nil (#38524)
Closes #ISSUE

Release Notes:

- mac: Fixed an issue where Zed would panic if the workspace window was
previously off screen
2025-09-19 13:11:23 -06:00
Cole Miller
49f98ca4f6 zed 0.205.2 2025-09-19 10:12:06 -04:00
Derek Nguyen
6b8b1e6859 python: Fix ty binary path and required args (#38458)
Closes #38347

Release Notes:

- Fixed path and args to ty lsp binary


When attempting to use the new ty lsp integration in the preview, I
noticed issues related to accessing the binary. After deleting the
downloaded archive and adding the following changes that:

- downloads the archive with the correct `AssetKind::TarGz`
- uses the correct path to the extracted binary
- adds the `server` argument to initialize the lsp (like ruff)

After the above changes the LSP starts correctly
```bash
2025-09-18T16:17:03-05:00 INFO  [lsp] starting language server process. binary path: "/Users/dereknguyen/Library/Application Support/Zed/languages/ty/ty-0.0.1-alpha.20/ty-aarch64-apple-darwin/ty", working directory: "/Users/dereknguyen/projects/test-project", args: ["server"]
```
<img width="206" height="98" alt="image"
src="https://github.com/user-attachments/assets/8fcf423f-40a0-4cd9-a79e-e09666323fe2"
/>

---------

Co-authored-by: Cole Miller <cole@zed.dev>
2025-09-19 10:08:35 -04:00
Cole Miller
de412e4c71 python: Install basedpyright with npm instead of pip (#38471)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-19 10:08:25 -04:00
Joseph T. Lyons
3e55f14863 zed 0.205.1 2025-09-18 16:04:27 -04:00
Cole Miller
900e1adcda acp: Fix agent servers sometimes not being registered when Zed starts (#38330)
In local projects, initialize the list of agents in the agent server
store immediately. Previously we were initializing the list only after a
delay, in an attempt to avoid sending the `ExternalAgentsUpdated`
message to the downstream client (if any) before its handlers were
initialized. But we already have a separate codepath for that situation,
in the `AgentServerStore::shared`, and we can insert the delay in that
place instead.

Release Notes:

- acp: Fixed a bug where starting an external agent thread soon after
Zed starts up would show a "not registered" error.

---------

Co-authored-by: Michael <michael@zed.dev>
Co-authored-by: Agus <agus@zed.dev>
2025-09-18 15:56:14 -04:00
Cole Miller
5229cc75ac python: Install basedpyright if the basedpyright-langserver binary is missing (#38426)
Potential fix for #38377 

Release Notes:

- N/A

---------

Co-authored-by: Peter Tripp <petertripp@gmail.com>
2025-09-18 15:27:26 -04:00
Anthony Eid
844f3eeb71 Fix chunks peek_with_bitmaps panic (#38430)
This panic only happened in debug builds because of a left shift
overflow. The slice range has bounds between 0 and 128. The 128 case
caused the overflow.

We now do an unbounded shift and a wrapped sub to get the correct
bitmask. If the slice range is 128 left, it should make 1 zero. Then the
wrapped sub would flip all bits, which is expected behavior.

Release Notes:

- N/A

Co-authored-by: Nia <nia@zed.dev>
2025-09-18 14:45:37 -04:00
Lukas Wirth
ab4687391a Partially revert "project: Fix terminal activation scripts failing on Windows for new shells (#37986)"
This reverts commit 4002602a89.
2025-09-18 11:59:08 +02:00
Ben Brandt
d5cf9be162 acp: Fix behavior of read_text_file for ACP agents (#38401)
We were incorrectly handling the line number as well as stripping out
line breaks when returning portions of files.

It also makes sure following is updated even when we load a snapshot
from cache, which wasn't the case before.

We also are able to load the text via a range in the snapshot, rather
than allocating a string for the entire file and then another after
iterating over lines in the file.

Release Notes:

- acp: Fix incorrect behavior when ACP agents requested to read portions
of files.
2025-09-18 11:58:07 +02:00
Joseph T. Lyons
3530445baf v0.205.x preview 2025-09-17 10:10:04 -04:00
21 changed files with 348 additions and 250 deletions

3
Cargo.lock generated
View File

@@ -416,6 +416,7 @@ dependencies = [
"serde_json",
"serde_json_lenient",
"settings",
"shlex",
"smol",
"streaming_diff",
"task",
@@ -21105,7 +21106,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.205.0"
version = "0.205.4"
dependencies = [
"acp_tools",
"activity_indicator",

View File

@@ -550,6 +550,8 @@
"cmd-ctrl-left": "editor::SelectSmallerSyntaxNode", // Shrink selection
"cmd-ctrl-right": "editor::SelectLargerSyntaxNode", // Expand selection
"cmd-ctrl-up": "editor::SelectPreviousSyntaxNode", // Move selection up
"ctrl-shift-right": "editor::SelectLargerSyntaxNode", // Expand selection (VSCode version)
"ctrl-shift-left": "editor::SelectSmallerSyntaxNode", // Shrink selection (VSCode version)
"cmd-ctrl-down": "editor::SelectNextSyntaxNode", // Move selection down
"cmd-d": ["editor::SelectNext", { "replace_newest": false }], // editor.action.addSelectionToNextFindMatch / find_under_expand
"cmd-shift-l": "editor::SelectAllMatches", // Select all occurrences of current selection

View File

@@ -497,8 +497,6 @@
"shift-alt-down": "editor::DuplicateLineDown",
"shift-alt-right": "editor::SelectLargerSyntaxNode", // Expand selection
"shift-alt-left": "editor::SelectSmallerSyntaxNode", // Shrink selection
"ctrl-shift-right": "editor::SelectLargerSyntaxNode", // Expand selection (VSCode version)
"ctrl-shift-left": "editor::SelectSmallerSyntaxNode", // Shrink selection (VSCode version)
"ctrl-shift-l": "editor::SelectAllMatches", // Select all occurrences of current selection
"ctrl-f2": "editor::SelectAllMatches", // Select all occurrences of current word
"ctrl-d": ["editor::SelectNext", { "replace_newest": false }], // editor.action.addSelectionToNextFindMatch / find_under_expand

View File

@@ -1781,6 +1781,9 @@ impl AcpThread {
reuse_shared_snapshot: bool,
cx: &mut Context<Self>,
) -> Task<Result<String>> {
// Args are 1-based, move to 0-based
let line = line.unwrap_or_default().saturating_sub(1);
let limit = limit.unwrap_or(u32::MAX);
let project = self.project.clone();
let action_log = self.action_log.clone();
cx.spawn(async move |this, cx| {
@@ -1808,44 +1811,37 @@ impl AcpThread {
action_log.update(cx, |action_log, cx| {
action_log.buffer_read(buffer.clone(), cx);
})?;
project.update(cx, |project, cx| {
let position = buffer
.read(cx)
.snapshot()
.anchor_before(Point::new(line.unwrap_or_default(), 0));
project.set_agent_location(
Some(AgentLocation {
buffer: buffer.downgrade(),
position,
}),
cx,
);
})?;
buffer.update(cx, |buffer, _| buffer.snapshot())?
let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot())?;
this.update(cx, |this, _| {
this.shared_buffers.insert(buffer.clone(), snapshot.clone());
})?;
snapshot
};
this.update(cx, |this, _| {
let text = snapshot.text();
this.shared_buffers.insert(buffer.clone(), snapshot);
if line.is_none() && limit.is_none() {
return Ok(text);
}
let limit = limit.unwrap_or(u32::MAX) as usize;
let Some(line) = line else {
return Ok(text.lines().take(limit).collect::<String>());
};
let max_point = snapshot.max_point();
if line >= max_point.row {
anyhow::bail!(
"Attempting to read beyond the end of the file, line {}:{}",
max_point.row + 1,
max_point.column
);
}
let count = text.lines().count();
if count < line as usize {
anyhow::bail!("There are only {} lines", count);
}
Ok(text
.lines()
.skip(line as usize + 1)
.take(limit)
.collect::<String>())
})?
let start = snapshot.anchor_before(Point::new(line, 0));
let end = snapshot.anchor_before(Point::new(line.saturating_add(limit), 0));
project.update(cx, |project, cx| {
project.set_agent_location(
Some(AgentLocation {
buffer: buffer.downgrade(),
position: start,
}),
cx,
);
})?;
Ok(snapshot.text_for_range(start..end).collect::<String>())
})
}
@@ -2391,6 +2387,82 @@ mod tests {
request.await.unwrap();
}
#[gpui::test]
async fn test_reading_from_line(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(path!("/tmp"), json!({"foo": "one\ntwo\nthree\nfour\n"}))
.await;
let project = Project::test(fs.clone(), [], cx).await;
project
.update(cx, |project, cx| {
project.find_or_create_worktree(path!("/tmp/foo"), true, cx)
})
.await
.unwrap();
let connection = Rc::new(FakeAgentConnection::new());
let thread = cx
.update(|cx| connection.new_thread(project, Path::new(path!("/tmp")), cx))
.await
.unwrap();
// Whole file
let content = thread
.update(cx, |thread, cx| {
thread.read_text_file(path!("/tmp/foo").into(), None, None, false, cx)
})
.await
.unwrap();
assert_eq!(content, "one\ntwo\nthree\nfour\n");
// Only start line
let content = thread
.update(cx, |thread, cx| {
thread.read_text_file(path!("/tmp/foo").into(), Some(3), None, false, cx)
})
.await
.unwrap();
assert_eq!(content, "three\nfour\n");
// Only limit
let content = thread
.update(cx, |thread, cx| {
thread.read_text_file(path!("/tmp/foo").into(), None, Some(2), false, cx)
})
.await
.unwrap();
assert_eq!(content, "one\ntwo\n");
// Range
let content = thread
.update(cx, |thread, cx| {
thread.read_text_file(path!("/tmp/foo").into(), Some(2), Some(2), false, cx)
})
.await
.unwrap();
assert_eq!(content, "two\nthree\n");
// Invalid
let err = thread
.update(cx, |thread, cx| {
thread.read_text_file(path!("/tmp/foo").into(), Some(5), Some(2), false, cx)
})
.await
.unwrap_err();
assert_eq!(
err.to_string(),
"Attempting to read beyond the end of the file, line 5:0"
);
}
#[gpui::test]
async fn test_succeeding_canceled_toolcall(cx: &mut TestAppContext) {
init_test(cx);

View File

@@ -80,6 +80,7 @@ serde.workspace = true
serde_json.workspace = true
serde_json_lenient.workspace = true
settings.workspace = true
shlex.workspace = true
smol.workspace = true
streaming_diff.workspace = true
task.workspace = true

View File

@@ -9,7 +9,7 @@ use agent_client_protocol::{self as acp, PromptCapabilities};
use agent_servers::{AgentServer, AgentServerDelegate};
use agent_settings::{AgentProfileId, AgentSettings, CompletionMode, NotifyWhenAgentWaiting};
use agent2::{DbThreadMetadata, HistoryEntry, HistoryEntryId, HistoryStore, NativeAgentServer};
use anyhow::{Result, anyhow, bail};
use anyhow::{Context as _, Result, anyhow, bail};
use arrayvec::ArrayVec;
use audio::{Audio, Sound};
use buffer_diff::BufferDiff;
@@ -1582,6 +1582,19 @@ impl AcpThreadView {
window.spawn(cx, async move |cx| {
let mut task = login.clone();
task.command = task
.command
.map(|command| anyhow::Ok(shlex::try_quote(&command)?.to_string()))
.transpose()?;
task.args = task
.args
.iter()
.map(|arg| {
Ok(shlex::try_quote(arg)
.context("Failed to quote argument")?
.to_string())
})
.collect::<Result<Vec<_>>>()?;
task.full_label = task.label.clone();
task.id = task::TaskId(format!("external-agent-{}-login", task.label));
task.command_label = task.label.clone();
@@ -1591,7 +1604,7 @@ impl AcpThreadView {
task.shell = shell;
let terminal = terminal_panel.update_in(cx, |terminal_panel, window, cx| {
terminal_panel.spawn_task(login.clone(), window, cx)
terminal_panel.spawn_task(&task, window, cx)
})?;
let terminal = terminal.await?;
@@ -5669,23 +5682,6 @@ pub(crate) mod tests {
});
}
#[gpui::test]
async fn test_spawn_external_agent_login_handles_spaces(cx: &mut TestAppContext) {
init_test(cx);
// Verify paths with spaces aren't pre-quoted
let path_with_spaces = "/Users/test/Library/Application Support/Zed/cli.js";
let login_task = task::SpawnInTerminal {
command: Some("node".to_string()),
args: vec![path_with_spaces.to_string(), "/login".to_string()],
..Default::default()
};
// Args should be passed as-is, not pre-quoted
assert!(!login_task.args[0].starts_with('"'));
assert!(!login_task.args[0].starts_with('\''));
}
#[gpui::test]
async fn test_notification_for_tool_authorization(cx: &mut TestAppContext) {
init_test(cx);

View File

@@ -506,7 +506,6 @@ mod tests {
use super::*;
use std::{path::Path, sync::Arc};
use futures::channel::oneshot;
use gpui::TestAppContext;
use indoc::indoc;
use language::{Language, LanguageConfig, LanguageId, LanguageMatcher, tree_sitter_rust};
@@ -655,17 +654,10 @@ mod tests {
expect_file_decl("a.rs", &decls[1], &project, cx);
});
// Drop the buffer and wait for release
let (release_tx, release_rx) = oneshot::channel();
cx.update(|cx| {
cx.observe_release(&buffer, |_, _| {
release_tx.send(()).ok();
})
.detach();
// Need to trigger flush_effects so that the observe_release handler will run.
cx.update(|_cx| {
drop(buffer);
});
drop(buffer);
cx.run_until_parked();
release_rx.await.ok();
cx.run_until_parked();
index.read_with(cx, |index, cx| {

View File

@@ -3015,6 +3015,12 @@ impl EditorElement {
.ilog10()
+ 1;
let git_gutter_width = Self::gutter_strip_width(line_height)
+ gutter_dimensions
.git_blame_entries_width
.unwrap_or_default();
let available_width = gutter_dimensions.left_padding - git_gutter_width;
buffer_rows
.iter()
.enumerate()
@@ -3030,9 +3036,6 @@ impl EditorElement {
ExpandExcerptDirection::UpAndDown => IconName::ExpandVertical,
};
let git_gutter_width = Self::gutter_strip_width(line_height);
let available_width = gutter_dimensions.left_padding - git_gutter_width;
let editor = self.editor.clone();
let is_wide = max_line_number_length
>= EditorSettings::get_global(cx).gutter.min_line_number_digits as u32

View File

@@ -35,7 +35,7 @@ use util::{archive::extract_zip, fs::make_file_executable, maybe};
use wasmtime::component::{Linker, Resource};
pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 6, 0);
pub const MAX_VERSION: SemanticVersion = SemanticVersion::new(0, 6, 0);
pub const MAX_VERSION: SemanticVersion = SemanticVersion::new(0, 7, 0);
wasmtime::component::bindgen!({
async: true,

View File

@@ -513,10 +513,11 @@ impl MacWindowState {
fn bounds(&self) -> Bounds<Pixels> {
let mut window_frame = unsafe { NSWindow::frame(self.native_window) };
let screen_frame = unsafe {
let screen = NSWindow::screen(self.native_window);
NSScreen::frame(screen)
};
let screen = unsafe { NSWindow::screen(self.native_window) };
if screen == nil {
return Bounds::new(point(px(0.), px(0.)), crate::DEFAULT_WINDOW_SIZE);
}
let screen_frame = unsafe { NSScreen::frame(screen) };
// Flip the y coordinate to be top-left origin
window_frame.origin.y =

View File

@@ -55,7 +55,7 @@ use std::{
str,
sync::{
Arc, LazyLock,
atomic::{AtomicU64, AtomicUsize, Ordering::SeqCst},
atomic::{AtomicUsize, Ordering::SeqCst},
},
};
use syntax_map::{QueryCursorHandle, SyntaxSnapshot};
@@ -168,7 +168,6 @@ pub struct CachedLspAdapter {
pub disk_based_diagnostics_progress_token: Option<String>,
language_ids: HashMap<LanguageName, String>,
pub adapter: Arc<dyn LspAdapter>,
pub reinstall_attempt_count: AtomicU64,
cached_binary: ServerBinaryCache,
}
@@ -185,7 +184,6 @@ impl Debug for CachedLspAdapter {
&self.disk_based_diagnostics_progress_token,
)
.field("language_ids", &self.language_ids)
.field("reinstall_attempt_count", &self.reinstall_attempt_count)
.finish_non_exhaustive()
}
}
@@ -204,7 +202,6 @@ impl CachedLspAdapter {
language_ids,
adapter,
cached_binary: Default::default(),
reinstall_attempt_count: AtomicU64::new(0),
})
}

View File

@@ -94,7 +94,7 @@ pub fn init(languages: Arc<LanguageRegistry>, fs: Arc<dyn Fs>, node: NodeRuntime
let ty_lsp_adapter = Arc::new(python::TyLspAdapter::new(fs.clone()));
let python_context_provider = Arc::new(python::PythonContextProvider);
let python_lsp_adapter = Arc::new(python::PyrightLspAdapter::new(node.clone()));
let basedpyright_lsp_adapter = Arc::new(BasedPyrightLspAdapter::new());
let basedpyright_lsp_adapter = Arc::new(BasedPyrightLspAdapter::new(node.clone()));
let ruff_lsp_adapter = Arc::new(RuffLspAdapter::new(fs.clone()));
let python_toolchain_provider = Arc::new(python::PythonToolchainProvider);
let rust_context_provider = Arc::new(rust::RustContextProvider);

View File

@@ -29,7 +29,6 @@ use parking_lot::Mutex;
use std::str::FromStr;
use std::{
borrow::Cow,
ffi::OsString,
fmt::Write,
path::{Path, PathBuf},
sync::Arc,
@@ -65,9 +64,6 @@ impl ManifestProvider for PyprojectTomlManifestProvider {
}
}
const SERVER_PATH: &str = "node_modules/pyright/langserver.index.js";
const NODE_MODULE_RELATIVE_SERVER_PATH: &str = "pyright/langserver.index.js";
enum TestRunner {
UNITTEST,
PYTEST,
@@ -85,10 +81,6 @@ impl FromStr for TestRunner {
}
}
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
vec![server_path.into(), "--stdio".into()]
}
/// Pyright assigns each completion item a `sortText` of the form `XX.YYYY.name`.
/// Where `XX` is the sorting category, `YYYY` is based on most recent usage,
/// and `name` is the symbol name itself.
@@ -108,7 +100,7 @@ pub struct TyLspAdapter {
#[cfg(target_os = "macos")]
impl TyLspAdapter {
const GITHUB_ASSET_KIND: AssetKind = AssetKind::Gz;
const GITHUB_ASSET_KIND: AssetKind = AssetKind::TarGz;
const ARCH_SERVER_NAME: &str = "apple-darwin";
}
@@ -224,15 +216,20 @@ impl LspInstaller for TyLspAdapter {
digest: expected_digest,
} = latest_version;
let destination_path = container_dir.join(format!("ty-{name}"));
async_fs::create_dir_all(&destination_path).await?;
let server_path = match Self::GITHUB_ASSET_KIND {
AssetKind::TarGz | AssetKind::Gz => destination_path.clone(), // Tar and gzip extract in place.
AssetKind::Zip => destination_path.clone().join("ty.exe"), // zip contains a .exe
AssetKind::TarGz | AssetKind::Gz => destination_path
.join(Self::build_asset_name()?.0)
.join("ty"),
AssetKind::Zip => destination_path.clone().join("ty.exe"),
};
let binary = LanguageServerBinary {
path: server_path.clone(),
env: None,
arguments: Default::default(),
arguments: vec!["server".into()],
};
let metadata_path = destination_path.with_extension("metadata");
@@ -291,7 +288,7 @@ impl LspInstaller for TyLspAdapter {
Ok(LanguageServerBinary {
path: server_path,
env: None,
arguments: Default::default(),
arguments: vec!["server".into()],
})
}
@@ -313,14 +310,16 @@ impl LspInstaller for TyLspAdapter {
let path = last.context("no cached binary")?;
let path = match TyLspAdapter::GITHUB_ASSET_KIND {
AssetKind::TarGz | AssetKind::Gz => path, // Tar and gzip extract in place.
AssetKind::Zip => path.join("ty.exe"), // zip contains a .exe
AssetKind::TarGz | AssetKind::Gz => {
path.join(Self::build_asset_name()?.0).join("ty")
}
AssetKind::Zip => path.join("ty.exe"),
};
anyhow::Ok(LanguageServerBinary {
path,
env: None,
arguments: Default::default(),
arguments: vec!["server".into()],
})
})
.await
@@ -334,10 +333,29 @@ pub struct PyrightLspAdapter {
impl PyrightLspAdapter {
const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("pyright");
const SERVER_PATH: &str = "node_modules/pyright/langserver.index.js";
const NODE_MODULE_RELATIVE_SERVER_PATH: &str = "pyright/langserver.index.js";
pub fn new(node: NodeRuntime) -> Self {
PyrightLspAdapter { node }
}
async fn get_cached_server_binary(
container_dir: PathBuf,
node: &NodeRuntime,
) -> Option<LanguageServerBinary> {
let server_path = container_dir.join(Self::SERVER_PATH);
if server_path.exists() {
Some(LanguageServerBinary {
path: node.binary_path().await.log_err()?,
env: None,
arguments: vec![server_path.into(), "--stdio".into()],
})
} else {
log::error!("missing executable in directory {:?}", server_path);
None
}
}
}
#[async_trait(?Send)]
@@ -550,13 +568,13 @@ impl LspInstaller for PyrightLspAdapter {
.await
.log_err()??;
let path = node_modules_path.join(NODE_MODULE_RELATIVE_SERVER_PATH);
let path = node_modules_path.join(Self::NODE_MODULE_RELATIVE_SERVER_PATH);
let env = delegate.shell_env().await;
Some(LanguageServerBinary {
path: node,
env: Some(env),
arguments: server_binary_arguments(&path),
arguments: vec![path.into(), "--stdio".into()],
})
}
}
@@ -567,7 +585,7 @@ impl LspInstaller for PyrightLspAdapter {
container_dir: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let server_path = container_dir.join(SERVER_PATH);
let server_path = container_dir.join(Self::SERVER_PATH);
self.node
.npm_install_packages(
@@ -580,7 +598,7 @@ impl LspInstaller for PyrightLspAdapter {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: Some(env),
arguments: server_binary_arguments(&server_path),
arguments: vec![server_path.into(), "--stdio".into()],
})
}
@@ -590,7 +608,7 @@ impl LspInstaller for PyrightLspAdapter {
container_dir: &PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
let server_path = container_dir.join(SERVER_PATH);
let server_path = container_dir.join(Self::SERVER_PATH);
let should_install_language_server = self
.node
@@ -609,7 +627,7 @@ impl LspInstaller for PyrightLspAdapter {
Some(LanguageServerBinary {
path: self.node.binary_path().await.ok()?,
env: Some(env),
arguments: server_binary_arguments(&server_path),
arguments: vec![server_path.into(), "--stdio".into()],
})
}
}
@@ -619,29 +637,12 @@ impl LspInstaller for PyrightLspAdapter {
container_dir: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
let mut binary = get_cached_server_binary(container_dir, &self.node).await?;
let mut binary = Self::get_cached_server_binary(container_dir, &self.node).await?;
binary.env = Some(delegate.shell_env().await);
Some(binary)
}
}
async fn get_cached_server_binary(
container_dir: PathBuf,
node: &NodeRuntime,
) -> Option<LanguageServerBinary> {
let server_path = container_dir.join(SERVER_PATH);
if server_path.exists() {
Some(LanguageServerBinary {
path: node.binary_path().await.log_err()?,
env: None,
arguments: server_binary_arguments(&server_path),
})
} else {
log::error!("missing executable in directory {:?}", server_path);
None
}
}
pub(crate) struct PythonContextProvider;
const PYTHON_TEST_TARGET_TASK_VARIABLE: VariableName =
@@ -1559,7 +1560,7 @@ impl LspInstaller for PyLspAdapter {
util::command::new_smol_command(pip_path.as_path())
.arg("install")
.arg("python-lsp-server")
.arg("-U")
.arg("--upgrade")
.output()
.await?
.status
@@ -1570,7 +1571,7 @@ impl LspInstaller for PyLspAdapter {
util::command::new_smol_command(pip_path.as_path())
.arg("install")
.arg("python-lsp-server[all]")
.arg("-U")
.arg("--upgrade")
.output()
.await?
.status
@@ -1581,7 +1582,7 @@ impl LspInstaller for PyLspAdapter {
util::command::new_smol_command(pip_path)
.arg("install")
.arg("pylsp-mypy")
.arg("-U")
.arg("--upgrade")
.output()
.await?
.status
@@ -1589,6 +1590,10 @@ impl LspInstaller for PyLspAdapter {
"pylsp-mypy installation failed"
);
let pylsp = venv.join(BINARY_DIR).join("pylsp");
ensure!(
delegate.which(pylsp.as_os_str()).await.is_some(),
"pylsp installation was incomplete"
);
Ok(LanguageServerBinary {
path: pylsp,
env: None,
@@ -1603,6 +1608,7 @@ impl LspInstaller for PyLspAdapter {
) -> Option<LanguageServerBinary> {
let venv = self.base_venv(delegate).await.ok()?;
let pylsp = venv.join(BINARY_DIR).join("pylsp");
delegate.which(pylsp.as_os_str()).await?;
Some(LanguageServerBinary {
path: pylsp,
env: None,
@@ -1612,62 +1618,34 @@ impl LspInstaller for PyLspAdapter {
}
pub(crate) struct BasedPyrightLspAdapter {
python_venv_base: OnceCell<Result<Arc<Path>, String>>,
node: NodeRuntime,
}
impl BasedPyrightLspAdapter {
const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("basedpyright");
const BINARY_NAME: &'static str = "basedpyright-langserver";
const SERVER_PATH: &str = "node_modules/basedpyright/langserver.index.js";
const NODE_MODULE_RELATIVE_SERVER_PATH: &str = "basedpyright/langserver.index.js";
pub(crate) fn new() -> Self {
Self {
python_venv_base: OnceCell::new(),
}
pub(crate) fn new(node: NodeRuntime) -> Self {
BasedPyrightLspAdapter { node }
}
async fn ensure_venv(delegate: &dyn LspAdapterDelegate) -> Result<Arc<Path>> {
let python_path = Self::find_base_python(delegate)
.await
.context("Could not find Python installation for basedpyright")?;
let work_dir = delegate
.language_server_download_dir(&Self::SERVER_NAME)
.await
.context("Could not get working directory for basedpyright")?;
let mut path = PathBuf::from(work_dir.as_ref());
path.push("basedpyright-venv");
if !path.exists() {
util::command::new_smol_command(python_path)
.arg("-m")
.arg("venv")
.arg("basedpyright-venv")
.current_dir(work_dir)
.spawn()?
.output()
.await?;
}
Ok(path.into())
}
// Find "baseline", user python version from which we'll create our own venv.
async fn find_base_python(delegate: &dyn LspAdapterDelegate) -> Option<PathBuf> {
for path in ["python3", "python"] {
if let Some(path) = delegate.which(path.as_ref()).await {
return Some(path);
}
}
None
}
async fn base_venv(&self, delegate: &dyn LspAdapterDelegate) -> Result<Arc<Path>, String> {
self.python_venv_base
.get_or_init(move || async move {
Self::ensure_venv(delegate)
.await
.map_err(|e| format!("{e}"))
async fn get_cached_server_binary(
container_dir: PathBuf,
node: &NodeRuntime,
) -> Option<LanguageServerBinary> {
let server_path = container_dir.join(Self::SERVER_PATH);
if server_path.exists() {
Some(LanguageServerBinary {
path: node.binary_path().await.log_err()?,
env: None,
arguments: vec![server_path.into(), "--stdio".into()],
})
.await
.clone()
} else {
log::error!("missing executable in directory {:?}", server_path);
None
}
}
}
@@ -1857,81 +1835,112 @@ impl LspAdapter for BasedPyrightLspAdapter {
}
impl LspInstaller for BasedPyrightLspAdapter {
type BinaryVersion = ();
type BinaryVersion = String;
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
_: bool,
_: &mut AsyncApp,
) -> Result<()> {
Ok(())
) -> Result<String> {
self.node
.npm_package_latest_version(Self::SERVER_NAME.as_ref())
.await
}
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
toolchain: Option<Toolchain>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
if let Some(bin) = delegate.which(Self::BINARY_NAME.as_ref()).await {
if let Some(path) = delegate.which(Self::BINARY_NAME.as_ref()).await {
let env = delegate.shell_env().await;
Some(LanguageServerBinary {
path: bin,
path,
env: Some(env),
arguments: vec!["--stdio".into()],
})
} else {
let path = Path::new(toolchain?.path.as_ref())
.parent()?
.join(Self::BINARY_NAME);
path.exists().then(|| LanguageServerBinary {
path,
arguments: vec!["--stdio".into()],
env: None,
// TODO shouldn't this be self.node.binary_path()?
let node = delegate.which("node".as_ref()).await?;
let (node_modules_path, _) = delegate
.npm_package_installed_version(Self::SERVER_NAME.as_ref())
.await
.log_err()??;
let path = node_modules_path.join(Self::NODE_MODULE_RELATIVE_SERVER_PATH);
let env = delegate.shell_env().await;
Some(LanguageServerBinary {
path: node,
env: Some(env),
arguments: vec![path.into(), "--stdio".into()],
})
}
}
async fn fetch_server_binary(
&self,
_latest_version: (),
_container_dir: PathBuf,
latest_version: Self::BinaryVersion,
container_dir: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let venv = self.base_venv(delegate).await.map_err(|e| anyhow!(e))?;
let pip_path = venv.join(BINARY_DIR).join("pip3");
ensure!(
util::command::new_smol_command(pip_path.as_path())
.arg("install")
.arg("basedpyright")
.arg("-U")
.output()
.await?
.status
.success(),
"basedpyright installation failed"
);
let pylsp = venv.join(BINARY_DIR).join(Self::BINARY_NAME);
let server_path = container_dir.join(Self::SERVER_PATH);
self.node
.npm_install_packages(
&container_dir,
&[(Self::SERVER_NAME.as_ref(), latest_version.as_str())],
)
.await?;
let env = delegate.shell_env().await;
Ok(LanguageServerBinary {
path: pylsp,
env: None,
arguments: vec!["--stdio".into()],
path: self.node.binary_path().await?,
env: Some(env),
arguments: vec![server_path.into(), "--stdio".into()],
})
}
async fn check_if_version_installed(
&self,
version: &Self::BinaryVersion,
container_dir: &PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
let server_path = container_dir.join(Self::SERVER_PATH);
let should_install_language_server = self
.node
.should_install_npm_package(
Self::SERVER_NAME.as_ref(),
&server_path,
container_dir,
VersionStrategy::Latest(version),
)
.await;
if should_install_language_server {
None
} else {
let env = delegate.shell_env().await;
Some(LanguageServerBinary {
path: self.node.binary_path().await.ok()?,
env: Some(env),
arguments: vec![server_path.into(), "--stdio".into()],
})
}
}
async fn cached_server_binary(
&self,
_container_dir: PathBuf,
container_dir: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
let venv = self.base_venv(delegate).await.ok()?;
let pylsp = venv.join(BINARY_DIR).join(Self::BINARY_NAME);
Some(LanguageServerBinary {
path: pylsp,
env: None,
arguments: vec!["--stdio".into()],
})
let mut binary = Self::get_cached_server_binary(container_dir, &self.node).await?;
binary.env = Some(delegate.shell_env().await);
Some(binary)
}
}

View File

@@ -234,7 +234,7 @@ impl AgentServerStore {
let subscription = cx.observe_global::<SettingsStore>(|this, cx| {
this.agent_servers_settings_changed(cx);
});
let this = Self {
let mut this = Self {
state: AgentServerStoreState::Local {
node_runtime,
fs,
@@ -245,14 +245,7 @@ impl AgentServerStore {
},
external_agents: Default::default(),
};
cx.spawn(async move |this, cx| {
cx.background_executor().timer(Duration::from_secs(1)).await;
this.update(cx, |this, cx| {
this.agent_servers_settings_changed(cx);
})
.ok();
})
.detach();
this.agent_servers_settings_changed(cx);
this
}
@@ -305,22 +298,29 @@ impl AgentServerStore {
}
}
pub fn shared(&mut self, project_id: u64, client: AnyProtoClient) {
pub fn shared(&mut self, project_id: u64, client: AnyProtoClient, cx: &mut Context<Self>) {
match &mut self.state {
AgentServerStoreState::Local {
downstream_client, ..
} => {
client
.send(proto::ExternalAgentsUpdated {
project_id,
names: self
.external_agents
*downstream_client = Some((project_id, client.clone()));
// Send the current list of external agents downstream, but only after a delay,
// to avoid having the message arrive before the downstream project's agent server store
// sets up its handlers.
cx.spawn(async move |this, cx| {
cx.background_executor().timer(Duration::from_secs(1)).await;
let names = this.update(cx, |this, _| {
this.external_agents
.keys()
.map(|name| name.to_string())
.collect(),
})
.log_err();
*downstream_client = Some((project_id, client));
.collect()
})?;
client
.send(proto::ExternalAgentsUpdated { project_id, names })
.log_err();
anyhow::Ok(())
})
.detach();
}
AgentServerStoreState::Remote { .. } => {
debug_panic!(
@@ -721,11 +721,6 @@ struct RemoteExternalAgentServer {
new_version_available_tx: Option<watch::Sender<Option<String>>>,
}
// new method: status_updated
// does nothing in the all-local case
// for RemoteExternalAgentServer, sends on the stored tx
// etc.
impl ExternalAgentServer for RemoteExternalAgentServer {
fn get_command(
&mut self,

View File

@@ -257,7 +257,7 @@ impl EventEmitter<ConflictSetUpdate> for ConflictSet {}
mod tests {
use std::{path::Path, sync::mpsc};
use crate::{Project, project_settings::ProjectSettings};
use crate::Project;
use super::*;
use fs::FakeFs;
@@ -484,7 +484,7 @@ mod tests {
cx.update(|cx| {
settings::init(cx);
WorktreeSettings::register(cx);
ProjectSettings::register(cx);
Project::init_settings(cx);
AllLanguageSettings::register(cx);
});
let initial_text = "
@@ -585,7 +585,7 @@ mod tests {
cx.update(|cx| {
settings::init(cx);
WorktreeSettings::register(cx);
ProjectSettings::register(cx);
Project::init_settings(cx);
AllLanguageSettings::register(cx);
});

View File

@@ -197,7 +197,7 @@ impl HeadlessProject {
let agent_server_store = cx.new(|cx| {
let mut agent_server_store =
AgentServerStore::local(node_runtime.clone(), fs.clone(), environment, cx);
agent_server_store.shared(REMOTE_SERVER_PROJECT_ID, session.clone());
agent_server_store.shared(REMOTE_SERVER_PROJECT_ID, session.clone(), cx);
agent_server_store
});

View File

@@ -784,7 +784,10 @@ impl<'a> Chunks<'a> {
slice_start..slice_end
};
let bitmask = (1u128 << slice_range.end as u128).saturating_sub(1);
// slice range has a bounds between 0 and 128 in non test builds
// We use a non wrapping sub because we want to overflow in the case where slice_range.end == 128
// because that represents a full chunk and the bitmask shouldn't remove anything
let bitmask = (1u128.unbounded_shl(slice_range.end as u32)).wrapping_sub(1);
let chars = (chunk.chars() & bitmask) >> slice_range.start;
let tabs = (chunk.tabs & bitmask) >> slice_range.start;

View File

@@ -532,14 +532,10 @@ impl TerminalBuilder {
child_exited: None,
};
if !activation_script.is_empty() && no_task {
if cfg!(not(target_os = "windows")) && !activation_script.is_empty() && no_task {
for activation_script in activation_script {
terminal.input(activation_script.into_bytes());
terminal.write_to_pty(if cfg!(windows) {
&b"\r\n"[..]
} else {
&b"\n"[..]
});
terminal.write_to_pty(b"\n");
}
terminal.clear();
}

View File

@@ -19,7 +19,7 @@ use itertools::Itertools;
use project::{Fs, Project, ProjectEntryId};
use search::{BufferSearchBar, buffer_search::DivRegistrar};
use settings::Settings;
use task::{RevealStrategy, RevealTarget, SpawnInTerminal, TaskId};
use task::{RevealStrategy, RevealTarget, ShellBuilder, SpawnInTerminal, TaskId};
use terminal::{
Terminal,
terminal_settings::{TerminalDockPosition, TerminalSettings},
@@ -521,10 +521,42 @@ impl TerminalPanel {
pub fn spawn_task(
&mut self,
task: SpawnInTerminal,
task: &SpawnInTerminal,
window: &mut Window,
cx: &mut Context<Self>,
) -> Task<Result<WeakEntity<Terminal>>> {
let remote_client = self
.workspace
.update(cx, |workspace, cx| {
let project = workspace.project().read(cx);
if project.is_via_collab() {
Err(anyhow!("cannot spawn tasks as a guest"))
} else {
Ok(project.remote_client())
}
})
.flatten();
let remote_client = match remote_client {
Ok(remote_client) => remote_client,
Err(e) => return Task::ready(Err(e)),
};
let remote_shell = remote_client
.as_ref()
.and_then(|remote_client| remote_client.read(cx).shell());
let builder = ShellBuilder::new(remote_shell.as_deref(), &task.shell);
let command_label = builder.command_label(&task.command_label);
let (command, args) = builder.build(task.command.clone(), &task.args);
let task = SpawnInTerminal {
command_label,
command: Some(command),
args,
..task.clone()
};
if task.allow_concurrent_runs && task.use_new_terminal {
return self.spawn_in_new_terminal(task, window, cx);
}
@@ -1558,7 +1590,7 @@ impl workspace::TerminalProvider for TerminalProvider {
window.spawn(cx, async move |cx| {
let terminal = terminal_panel
.update_in(cx, |terminal_panel, window, cx| {
terminal_panel.spawn_task(task, window, cx)
terminal_panel.spawn_task(&task, window, cx)
})
.ok()?
.await;

View File

@@ -2,7 +2,7 @@
description = "The fast, collaborative code editor."
edition.workspace = true
name = "zed"
version = "0.205.0"
version = "0.205.4"
publish.workspace = true
license = "GPL-3.0-or-later"
authors = ["Zed Team <hi@zed.dev>"]

View File

@@ -1 +1 @@
dev
preview