Compare commits

...

42 Commits

Author SHA1 Message Date
Zed Bot
5d6de5677d Bump to 0.205.7 for @ConradIrwin 2025-09-29 17:54:20 +00:00
Lukas Wirth
e923694a03 themes: Set font_weight to null for syntax.hint (#39105)
Since https://github.com/zed-industries/zed/pull/36219 we now render
inlay hints as bold due to this.

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-29 15:01:48 +02:00
Lukas Wirth
22f1ad6433 themes: Set font_weight to null for syntax.hint (#39105)
Since https://github.com/zed-industries/zed/pull/36219 we now render
inlay hints as bold due to this.

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-29 15:01:24 +02:00
Ben Brandt
6099d02539 acp: Add NO_PROXY if not set otherwise to not proxy localhost urls (#39100)
Since we might run MCP servers locally for an agent, we don't want to
use the proxy for those.
We set this if the user has set a proxy, but not a custom NO_PROXY env
var.

Closes #38839

Release Notes:

- acp: Don't run local mcp servers through proxy, if set
2025-09-29 13:47:40 +02:00
Lukas Wirth
6821efc4a8 search: Fix panic in project search due to workspace double lease (#39049)
Fixes ZED-1K1

Release Notes:

- Fixed panic when spawning a new project search with include file only
filtering
2025-09-29 13:13:14 +02:00
Lukas Wirth
5e464fcbb7 ui: Fix panic in highlight_ranges when given an oob index (#39051)
Fixes ZED-1QW

Release Notes:

- Fixed a panic when highlighting labels
2025-09-29 13:11:05 +02:00
Lukas Wirth
e059b6a31d acp_thread: Fix terminal tool incorrectly redirecting stdin to /dev/null 2025-09-29 11:39:12 +02:00
Lukas Wirth
7e837b43d2 auto_update: Unmount update disk image in the background (#38867)
Release Notes:

- Fixed potentially temporarily hanging on macOS when updating the app
2025-09-27 19:56:09 +02:00
Joseph T. Lyons
767968aa36 zed 0.205.6 2025-09-26 14:24:41 -04:00
Smit Barmase
e74d6a9bd6 editor: Fix predict edit at cursor action when show_edit_predictions is false (#38821)
Closes #37601 

Regressed in https://github.com/zed-industries/zed/pull/36469. 

Edit: Original issue https://github.com/zed-industries/zed/issues/25744
is fixed for Zeta in this PR. For Copilot, it will be covered in a
follow-up. In the case of Copilot, even after discarding, we still get a
prediction on suggest, which is a bug.

Release Notes:

- Fixed issue where predict edit at cursor didn't work when
`show_edit_predictions` is `false`.
2025-09-26 06:23:10 -04:00
Derek Nguyen
c4873bf8dc python: Fix ty archive extraction on Linux (#38917)
Closes #38553 
Release Notes:

- Fixed wrong AssetKind specified on linux for ty 


As discussed in the linked issue. All of the non windows assets for ty
are `tar.gz` files. This change applies that fix.
2025-09-25 19:31:15 -04:00
Ben Brandt
39de188584 acp: Use ACP error types in read_text_file (#38863)
- Map path lookup and internal failures to acp::Error
- Return INVALID_PARAMS for reads beyond EOF

Release Notes:

- acp: Return more informative error types from `read_text_file` to
agents
2025-09-25 14:09:25 +02:00
Ben Brandt
d3c3ef9481 acp: update to v0.4 of Rust library (#38336)
Release Notes:

- N/A
2025-09-25 14:06:09 +02:00
Julia Ryan
79468c119e Respect user's font-smoothing setting (#38467)
#37622 was incorrectly forcing font smoothing to be enabled on macos
even when the user had disabled that setting at the OS level. See [this
comment](https://github.com/zed-industries/zed/pull/37622#issuecomment-3310030659)
for an example of the difference that font smoothing makes.

Release Notes:

- N/A
2025-09-25 13:41:42 +03:00
Ben Brandt
f4ee55e4bf acp: Fix read_text_file erroring on empty files (#38856)
The previous validation was too strict and didn't permit reading empty
files.

Addresses: https://github.com/google-gemini/gemini-cli/issues/9280

Release Notes:

- acp: Fix `read_text_file` returning errors for empty files
2025-09-25 12:13:10 +02:00
Zed Bot
8214adc728 Bump to 0.205.5 for @ConradIrwin 2025-09-25 03:12:46 +00:00
Lukas Wirth
386bce81c6 editor: Fix invalid anchors in hover_links::surrounding_filename (#38766)
Fixes ZED-1K3

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-24 13:09:29 +02:00
Lukas Wirth
cc161b36b4 editor: Prevent panics in BlockChunks if the block spans more than 128 lines (#38763)
Not an ideal fix, but a proper one will require restructuring the
iterator state (which would be easier if Rust had first class
generators)
Fixes ZED-1MB

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-24 13:09:09 +02:00
Conrad Irwin
0369737fcb Revert "Add setting to show/hide title bar (#37428)" (#38756)
Closes https://github.com/zed-industries/zed/issues/38547

Release Notes:

- Reverted the ability to show/hide the titlebar. This caused rendering
bugs on
macOS, and we're preparing for the redesign which requires the toolbar
being present.

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2025-09-24 07:26:11 +03:00
Kirill Bulatov
1348dbbd46 Clear buffer colors on empty LSP response (#38742)
Follow-up of https://github.com/zed-industries/zed/pull/32816
Closes https://github.com/zed-industries/zed/issues/38602


https://github.com/user-attachments/assets/26058c91-4ffd-4c6f-a41d-17da0c3d7220

Release Notes:

- Fixed buffer colors not cleared on empty LSP responses
2025-09-24 00:01:21 +03:00
Joseph T. Lyons
a588f85a41 v0.205.x stable 2025-09-23 16:14:49 -04:00
Marshall Bowers
d4112ffa0e Update plan text (#38731)
Release Notes:

- N/A

---------

Co-authored-by: David Kleingeld <davidsk@zed.dev>
2025-09-23 13:50:18 -04:00
Alvaro Parker
22cb2d46c1 git: Fix git amend on panel (#38681)
Closes #38651 

`git_panel.set_amend_pending(false, cx);` was being called before
`git_panel.commit_changes(...)` which was causing the commit buffer to
be cleared/reset before actually sending the commit request to git.

Introduced by #35268 which added clear buffer functionality to the
`set_amend_pending` function.

Release Notes:

- Fix git amend on panel sending "Update ..." instead of the original
commit message
- FIx git amend button not working
2025-09-23 09:23:50 -06:00
Ben Brandt
9e519ac47a agent: Fix Gemini refusing all requests with file-based tool calls (#38705)
Solves an issue where Google APIs refuse all requests with file-based
tool calls attached.
This seems to get triggered in the case where:

- copy_path + another file-based tool call is enabled
- default terminal is `/bin/bash` or something similar

It is unclear why this is happening, but removing the terminal commands
in those tool calls seems to have solved the issue.

Closes #37180 and #37414

Release Notes:

- agent: Fix Gemini refusing requests with certain profiles/systems.
2025-09-23 14:30:03 +02:00
Marshall Bowers
1755b67937 language_models: Treat a block_reason from Gemini as a refusal (#38670)
This PR updates the Gemini provider to treat a
`prompt_feedback.block_reason` as a refusal, as Gemini does not seem to
return a `stop_reason` to use in this case.

<img width="639" height="162" alt="Screenshot 2025-09-22 at 4 23 15 PM"
src="https://github.com/user-attachments/assets/7a86d67e-06c1-49ea-b58f-fa80666f0f8c"
/>

Previously this would just result in no feedback to the user.

Release Notes:

- Added an error message when a Gemini response contains a
`block_reason`.
2025-09-22 17:15:53 -04:00
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
62 changed files with 866 additions and 595 deletions

18
Cargo.lock generated
View File

@@ -195,12 +195,13 @@ dependencies = [
[[package]]
name = "agent-client-protocol"
version = "0.2.1"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "003fb91bf1b8d6e15f72c45fb9171839af8241e81e3839fbb73536af113b7a79"
checksum = "3aaa2bd05a2401887945f8bfd70026e90bc3cf96c62ab9eba2779835bf21dc60"
dependencies = [
"anyhow",
"async-broadcast",
"async-trait",
"futures 0.3.31",
"log",
"parking_lot",
@@ -293,6 +294,7 @@ dependencies = [
"agent-client-protocol",
"agent_settings",
"anyhow",
"async-trait",
"client",
"collections",
"env_logger 0.11.8",
@@ -416,6 +418,7 @@ dependencies = [
"serde_json",
"serde_json_lenient",
"settings",
"shlex",
"smol",
"streaming_diff",
"task",
@@ -2190,7 +2193,7 @@ dependencies = [
"bitflags 2.9.0",
"cexpr",
"clang-sys",
"itertools 0.11.0",
"itertools 0.12.1",
"lazy_static",
"lazycell",
"log",
@@ -9559,6 +9562,7 @@ dependencies = [
"credentials_provider",
"deepseek",
"editor",
"feature_flags",
"fs",
"futures 0.3.31",
"google_ai",
@@ -13225,7 +13229,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4"
dependencies = [
"bytes 1.10.1",
"heck 0.5.0",
"itertools 0.11.0",
"itertools 0.12.1",
"log",
"multimap",
"once_cell",
@@ -13258,7 +13262,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1"
dependencies = [
"anyhow",
"itertools 0.11.0",
"itertools 0.12.1",
"proc-macro2",
"quote",
"syn 2.0.101",
@@ -20621,7 +20625,7 @@ dependencies = [
"idna",
"indexmap",
"inout",
"itertools 0.11.0",
"itertools 0.12.1",
"itertools 0.13.0",
"jiff",
"lazy_static",
@@ -21105,7 +21109,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.205.0"
version = "0.205.7"
dependencies = [
"acp_tools",
"activity_indicator",

View File

@@ -437,7 +437,7 @@ zlog_settings = { path = "crates/zlog_settings" }
# External crates
#
agent-client-protocol = { version = "0.2.1", features = ["unstable"] }
agent-client-protocol = { version = "0.4.3", features = ["unstable"] }
aho-corasick = "1.1"
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" }
any_vec = "0.14"

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

@@ -391,8 +391,6 @@
"use_system_window_tabs": false,
// Titlebar related settings
"title_bar": {
// When to show the title bar: "always" | "never" | "hide_in_full_screen".
"show": "always",
// Whether to show the branch icon beside branch switcher in the titlebar.
"show_branch_icon": false,
// Whether to show the branch name button in the titlebar.

View File

@@ -239,7 +239,7 @@
"hint": {
"color": "#628b80ff",
"font_style": null,
"font_weight": 700
"font_weight": null
},
"keyword": {
"color": "#ff8f3fff",

View File

@@ -248,7 +248,7 @@
"hint": {
"color": "#8c957dff",
"font_style": null,
"font_weight": 700
"font_weight": null
},
"keyword": {
"color": "#fb4833ff",

View File

@@ -244,7 +244,7 @@
"hint": {
"color": "#788ca6ff",
"font_style": null,
"font_weight": 700
"font_weight": null
},
"keyword": {
"color": "#b477cfff",

View File

@@ -1780,17 +1780,26 @@ impl AcpThread {
limit: Option<u32>,
reuse_shared_snapshot: bool,
cx: &mut Context<Self>,
) -> Task<Result<String>> {
) -> Task<Result<String, acp::Error>> {
// 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| {
let load = project.update(cx, |project, cx| {
let path = project
.project_path_for_absolute_path(&path, cx)
.context("invalid path")?;
anyhow::Ok(project.open_buffer(path, cx))
});
let buffer = load??.await?;
let load = project
.update(cx, |project, cx| {
let path = project
.project_path_for_absolute_path(&path, cx)
.ok_or_else(|| {
acp::Error::resource_not_found(Some(path.display().to_string()))
})?;
Ok(project.open_buffer(path, cx))
})
.map_err(|e| acp::Error::internal_error().with_data(e.to_string()))
.flatten()?;
let buffer = load.await?;
let snapshot = if reuse_shared_snapshot {
this.read_with(cx, |this, _| {
@@ -1808,44 +1817,39 @@ 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();
let start_position = Point::new(line, 0);
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>())
})?
if start_position > max_point {
return Err(acp::Error::invalid_params().with_data(format!(
"Attempting to read beyond the end of the file, line {}:{}",
max_point.row + 1,
max_point.column
)));
}
let start = snapshot.anchor_before(start_position);
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>())
})
}
@@ -1981,7 +1985,7 @@ impl AcpThread {
let terminal_id = terminal_id.clone();
async move |_this, cx| {
let env = env.await;
let (command, args) = ShellBuilder::new(
let (task_command, task_args) = ShellBuilder::new(
project
.update(cx, |project, cx| {
project
@@ -1992,13 +1996,13 @@ impl AcpThread {
&Shell::Program(get_default_system_shell()),
)
.redirect_stdin_to_dev_null()
.build(Some(command), &args);
.build(Some(command.clone()), &args);
let terminal = project
.update(cx, |project, cx| {
project.create_terminal_task(
task::SpawnInTerminal {
command: Some(command.clone()),
args: args.clone(),
command: Some(task_command),
args: task_args,
cwd: cwd.clone(),
env,
..Default::default()
@@ -2391,6 +2395,188 @@ 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(6), Some(2), false, cx)
})
.await
.unwrap_err();
assert_eq!(
err.to_string(),
"Invalid params: \"Attempting to read beyond the end of the file, line 5:0\""
);
}
#[gpui::test]
async fn test_reading_empty_file(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(path!("/tmp"), json!({"foo": ""})).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, "");
// Only start line
let content = thread
.update(cx, |thread, cx| {
thread.read_text_file(path!("/tmp/foo").into(), Some(1), None, false, cx)
})
.await
.unwrap();
assert_eq!(content, "");
// 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, "");
// Range
let content = thread
.update(cx, |thread, cx| {
thread.read_text_file(path!("/tmp/foo").into(), Some(1), Some(1), false, cx)
})
.await
.unwrap();
assert_eq!(content, "");
// 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(),
"Invalid params: \"Attempting to read beyond the end of the file, line 1:0\""
);
}
#[gpui::test]
async fn test_reading_non_existing_file(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(path!("/tmp"), json!({})).await;
let project = Project::test(fs.clone(), [], cx).await;
project
.update(cx, |project, cx| {
project.find_or_create_worktree(path!("/tmp"), 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();
// Out of project file
let err = thread
.update(cx, |thread, cx| {
thread.read_text_file(path!("/foo").into(), None, None, false, cx)
})
.await
.unwrap_err();
assert_eq!(err.code, acp::ErrorCode::RESOURCE_NOT_FOUND.code);
}
#[gpui::test]
async fn test_succeeding_canceled_toolcall(cx: &mut TestAppContext) {
init_test(cx);

View File

@@ -48,16 +48,15 @@ The one exception to this is if the user references something you don't know abo
## Code Block Formatting
Whenever you mention a code block, you MUST use ONLY use the following format:
```path/to/Something.blah#L123-456
(code goes here)
```
The `#L123-456` means the line number range 123 through 456, and the path/to/Something.blah
is a path in the project. (If there is no valid path in the project, then you can use
/dev/null/path.extension for its path.) This is the ONLY valid way to format code blocks, because the Markdown parser
does not understand the more common ```language syntax, or bare ``` blocks. It only
understands this path-based syntax, and if the path is missing, then it will error and you will have to do it over again.
The `#L123-456` means the line number range 123 through 456, and the path/to/Something.blah is a path in the project. (If there is no valid path in the project, then you can use /dev/null/path.extension for its path.) This is the ONLY valid way to format code blocks, because the Markdown parser does not understand the more common ```language syntax, or bare ``` blocks. It only understands this path-based syntax, and if the path is missing, then it will error and you will have to do it over again.
Just to be really clear about this, if you ever find yourself writing three backticks followed by a language name, STOP!
You have made a mistake. You can only ever put paths after triple backticks!
<example>
Based on all the information I've gathered, here's a summary of how this system works:
1. The README file is loaded into the system.
@@ -74,6 +73,7 @@ This is the last header in the README.
```
4. Finally, it passes this information on to the next process.
</example>
<example>
In Markdown, hash marks signify headings. For example:
```/dev/null/example.md#L1-3
@@ -82,6 +82,7 @@ In Markdown, hash marks signify headings. For example:
### Level 3 heading
```
</example>
Here are examples of ways you must never render code blocks:
<bad_example_do_not_do_this>
In Markdown, hash marks signify headings. For example:
@@ -91,7 +92,9 @@ In Markdown, hash marks signify headings. For example:
### Level 3 heading
```
</bad_example_do_not_do_this>
This example is unacceptable because it does not include the path.
<bad_example_do_not_do_this>
In Markdown, hash marks signify headings. For example:
```markdown
@@ -101,14 +104,15 @@ In Markdown, hash marks signify headings. For example:
```
</bad_example_do_not_do_this>
This example is unacceptable because it has the language instead of the path.
<bad_example_do_not_do_this>
In Markdown, hash marks signify headings. For example:
# Level 1 heading
## Level 2 heading
### Level 3 heading
</bad_example_do_not_do_this>
This example is unacceptable because it uses indentation to mark the code block
instead of backticks with a path.
This example is unacceptable because it uses indentation to mark the code block instead of backticks with a path.
<bad_example_do_not_do_this>
In Markdown, hash marks signify headings. For example:
```markdown

View File

@@ -9,14 +9,14 @@ use std::sync::Arc;
use util::markdown::MarkdownInlineCode;
/// Copies a file or directory in the project, and returns confirmation that the copy succeeded.
/// Directory contents will be copied recursively (like `cp -r`).
/// Directory contents will be copied recursively.
///
/// This tool should be used when it's desirable to create a copy of a file or directory without modifying the original.
/// It's much more efficient than doing this by separately reading and then writing the file or directory's contents, so this tool should be preferred over that approach whenever copying is the goal.
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct CopyPathToolInput {
/// The source path of the file or directory to copy.
/// If a directory is specified, its contents will be copied recursively (like `cp -r`).
/// If a directory is specified, its contents will be copied recursively.
///
/// <example>
/// If the project has the following files:

View File

@@ -11,7 +11,7 @@ use crate::{AgentTool, ToolCallEventStream};
/// Creates a new directory at the specified path within the project. Returns confirmation that the directory was created.
///
/// This tool creates a directory and all necessary parent directories (similar to `mkdir -p`). It should be used whenever you need to create new directories within the project.
/// This tool creates a directory and all necessary parent directories. It should be used whenever you need to create new directories within the project.
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct CreateDirectoryToolInput {
/// The path of the new directory.

View File

@@ -23,6 +23,7 @@ action_log.workspace = true
agent-client-protocol.workspace = true
agent_settings.workspace = true
anyhow.workspace = true
async-trait.workspace = true
client.workspace = true
collections.workspace = true
env_logger = { workspace = true, optional = true }

View File

@@ -13,7 +13,7 @@ use util::ResultExt as _;
use std::path::PathBuf;
use std::{any::Any, cell::RefCell};
use std::{path::Path, rc::Rc, sync::Arc};
use std::{path::Path, rc::Rc};
use thiserror::Error;
use anyhow::{Context as _, Result};
@@ -505,6 +505,7 @@ struct ClientDelegate {
cx: AsyncApp,
}
#[async_trait::async_trait(?Send)]
impl acp::Client for ClientDelegate {
async fn request_permission(
&self,
@@ -638,19 +639,11 @@ impl acp::Client for ClientDelegate {
Ok(Default::default())
}
async fn ext_method(
&self,
_name: Arc<str>,
_params: Arc<serde_json::value::RawValue>,
) -> Result<Arc<serde_json::value::RawValue>, acp::Error> {
async fn ext_method(&self, _args: acp::ExtRequest) -> Result<acp::ExtResponse, acp::Error> {
Err(acp::Error::method_not_found())
}
async fn ext_notification(
&self,
_name: Arc<str>,
_params: Arc<serde_json::value::RawValue>,
) -> Result<(), acp::Error> {
async fn ext_notification(&self, _args: acp::ExtNotification) -> Result<(), acp::Error> {
Err(acp::Error::method_not_found())
}

View File

@@ -99,6 +99,9 @@ pub fn load_proxy_env(cx: &mut App) -> HashMap<String, String> {
if let Some(no_proxy) = read_no_proxy_from_env() {
env.insert("NO_PROXY".to_owned(), no_proxy);
} else if proxy_url.is_some() {
// We sometimes need local MCP servers that we don't want to proxy
env.insert("NO_PROXY".to_owned(), "localhost,127.0.0.1".to_owned());
}
env

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

@@ -2,7 +2,7 @@ use std::sync::Arc;
use ai_onboarding::{AgentPanelOnboardingCard, PlanDefinitions};
use client::zed_urls;
use cloud_llm_client::{Plan, PlanV1};
use cloud_llm_client::{Plan, PlanV2};
use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
use ui::{Divider, Tooltip, prelude::*};
@@ -112,7 +112,7 @@ impl Component for EndTrialUpsell {
Some(
v_flex()
.child(EndTrialUpsell {
plan: Plan::V1(PlanV1::ZedFree),
plan: Plan::V2(PlanV2::ZedFree),
dismiss_upsell: Arc::new(|_, _| {}),
})
.into_any_element(),

View File

@@ -120,7 +120,7 @@ impl ZedAiOnboarding {
.max_w_full()
.gap_1()
.child(Headline::new("Welcome to Zed AI"))
.child(YoungAccountBanner)
.child(YoungAccountBanner::new(is_v2))
.child(
v_flex()
.mt_2()
@@ -372,7 +372,7 @@ impl Component for ZedAiOnboarding {
"Free Plan",
onboarding(
SignInStatus::SignedIn,
Some(Plan::V1(PlanV1::ZedFree)),
Some(Plan::V2(PlanV2::ZedFree)),
false,
),
),
@@ -380,7 +380,7 @@ impl Component for ZedAiOnboarding {
"Pro Trial",
onboarding(
SignInStatus::SignedIn,
Some(Plan::V1(PlanV1::ZedProTrial)),
Some(Plan::V2(PlanV2::ZedProTrial)),
false,
),
),
@@ -388,7 +388,7 @@ impl Component for ZedAiOnboarding {
"Pro Plan",
onboarding(
SignInStatus::SignedIn,
Some(Plan::V1(PlanV1::ZedPro)),
Some(Plan::V2(PlanV2::ZedPro)),
false,
),
),

View File

@@ -175,7 +175,7 @@ impl RenderOnce for AiUpsellCard {
.child(Label::new("Try Zed AI").size(LabelSize::Large))
.map(|this| {
if self.account_too_young {
this.child(YoungAccountBanner).child(
this.child(YoungAccountBanner::new(is_v2_plan)).child(
v_flex()
.mt_2()
.gap_1()
@@ -215,7 +215,7 @@ impl RenderOnce for AiUpsellCard {
.child(
footer_container
.child(
Button::new("start_trial", "Start 14-day Free Pro Trial")
Button::new("start_trial", "Start Pro Trial")
.full_width()
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
.when_some(self.tab_index, |this, tab_index| {
@@ -230,7 +230,7 @@ impl RenderOnce for AiUpsellCard {
}),
)
.child(
Label::new("No credit card required")
Label::new("14 days, no credit card required")
.size(LabelSize::Small)
.color(Color::Muted),
),
@@ -327,7 +327,7 @@ impl Component for AiUpsellCard {
sign_in_status: SignInStatus::SignedIn,
sign_in: Arc::new(|_, _| {}),
account_too_young: false,
user_plan: Some(Plan::V1(PlanV1::ZedFree)),
user_plan: Some(Plan::V2(PlanV2::ZedFree)),
tab_index: Some(1),
}
.into_any_element(),
@@ -338,7 +338,7 @@ impl Component for AiUpsellCard {
sign_in_status: SignInStatus::SignedIn,
sign_in: Arc::new(|_, _| {}),
account_too_young: true,
user_plan: Some(Plan::V1(PlanV1::ZedFree)),
user_plan: Some(Plan::V2(PlanV2::ZedFree)),
tab_index: Some(1),
}
.into_any_element(),
@@ -349,7 +349,7 @@ impl Component for AiUpsellCard {
sign_in_status: SignInStatus::SignedIn,
sign_in: Arc::new(|_, _| {}),
account_too_young: false,
user_plan: Some(Plan::V1(PlanV1::ZedProTrial)),
user_plan: Some(Plan::V2(PlanV2::ZedProTrial)),
tab_index: Some(1),
}
.into_any_element(),
@@ -360,7 +360,7 @@ impl Component for AiUpsellCard {
sign_in_status: SignInStatus::SignedIn,
sign_in: Arc::new(|_, _| {}),
account_too_young: false,
user_plan: Some(Plan::V1(PlanV1::ZedPro)),
user_plan: Some(Plan::V2(PlanV2::ZedPro)),
tab_index: Some(1),
}
.into_any_element(),

View File

@@ -7,33 +7,62 @@ pub struct PlanDefinitions;
impl PlanDefinitions {
pub const AI_DESCRIPTION: &'static str = "Zed offers a complete agentic experience, with robust editing and reviewing features to collaborate with AI.";
pub fn free_plan(&self, _is_v2: bool) -> impl IntoElement {
List::new()
.child(ListBulletItem::new("50 prompts with Claude models"))
.child(ListBulletItem::new("2,000 accepted edit predictions"))
}
pub fn pro_trial(&self, _is_v2: bool, period: bool) -> impl IntoElement {
List::new()
.child(ListBulletItem::new("150 prompts with Claude models"))
.child(ListBulletItem::new(
"Unlimited edit predictions with Zeta, our open-source model",
))
.when(period, |this| {
this.child(ListBulletItem::new(
"Try it out for 14 days for free, no credit card required",
pub fn free_plan(&self, is_v2: bool) -> impl IntoElement {
if is_v2 {
List::new()
.child(ListBulletItem::new("2,000 accepted edit predictions"))
.child(ListBulletItem::new(
"Unlimited prompts with your AI API keys",
))
})
.child(ListBulletItem::new(
"Unlimited use of external agents like Claude Code",
))
} else {
List::new()
.child(ListBulletItem::new("50 prompts with Claude models"))
.child(ListBulletItem::new("2,000 accepted edit predictions"))
}
}
pub fn pro_plan(&self, _is_v2: bool, price: bool) -> impl IntoElement {
List::new()
.child(ListBulletItem::new("500 prompts with Claude models"))
.child(ListBulletItem::new(
"Unlimited edit predictions with Zeta, our open-source model",
))
.when(price, |this| {
this.child(ListBulletItem::new("$20 USD per month"))
})
pub fn pro_trial(&self, is_v2: bool, period: bool) -> impl IntoElement {
if is_v2 {
List::new()
.child(ListBulletItem::new("Unlimited edit predictions"))
.child(ListBulletItem::new("$20 of tokens"))
.when(period, |this| {
this.child(ListBulletItem::new(
"Try it out for 14 days, no credit card required",
))
})
} else {
List::new()
.child(ListBulletItem::new("150 prompts with Claude models"))
.child(ListBulletItem::new(
"Unlimited edit predictions with Zeta, our open-source model",
))
.when(period, |this| {
this.child(ListBulletItem::new(
"Try it out for 14 days, no credit card required",
))
})
}
}
pub fn pro_plan(&self, is_v2: bool, price: bool) -> impl IntoElement {
if is_v2 {
List::new()
.child(ListBulletItem::new("Unlimited edit predictions"))
.child(ListBulletItem::new("$5 of tokens"))
.child(ListBulletItem::new("Usage-based billing beyond $5"))
} else {
List::new()
.child(ListBulletItem::new("500 prompts with Claude models"))
.child(ListBulletItem::new(
"Unlimited edit predictions with Zeta, our open-source model",
))
.when(price, |this| {
this.child(ListBulletItem::new("$20 USD per month"))
})
}
}
}

View File

@@ -2,17 +2,30 @@ use gpui::{IntoElement, ParentElement};
use ui::{Banner, prelude::*};
#[derive(IntoElement)]
pub struct YoungAccountBanner;
pub struct YoungAccountBanner {
is_v2: bool,
}
impl YoungAccountBanner {
pub fn new(is_v2: bool) -> Self {
Self { is_v2 }
}
}
impl RenderOnce for YoungAccountBanner {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
const YOUNG_ACCOUNT_DISCLAIMER: &str = "To prevent abuse of our service, GitHub accounts created fewer than 30 days ago are not eligible for free plan usage or Pro plan free trial. To request an exception, reach out to billing-support@zed.dev.";
const YOUNG_ACCOUNT_DISCLAIMER: &str = "To prevent abuse of our service, GitHub accounts created fewer than 30 days ago are not eligible for free plan usage or Pro plan free trial. You can request an exception by reaching out to billing-support@zed.dev";
const YOUNG_ACCOUNT_DISCLAIMER_V2: &str = "To prevent abuse of our service, GitHub accounts created fewer than 30 days ago are not eligible for the Pro trial. You can request an exception by reaching out to billing-support@zed.dev";
let label = div()
.w_full()
.text_sm()
.text_color(cx.theme().colors().text_muted)
.child(YOUNG_ACCOUNT_DISCLAIMER);
.child(if self.is_v2 {
YOUNG_ACCOUNT_DISCLAIMER_V2
} else {
YOUNG_ACCOUNT_DISCLAIMER
});
div()
.max_w_full()

View File

@@ -139,18 +139,25 @@ impl Tool for TerminalTool {
env
});
let build_cmd = {
let input_command = input.command.clone();
move || {
ShellBuilder::new(
remote_shell.as_deref(),
&Shell::Program(get_default_system_shell()),
)
.redirect_stdin_to_dev_null()
.build(Some(input_command.clone()), &[])
}
};
let Some(window) = window else {
// Headless setup, a test or eval. Our terminal subsystem requires a workspace,
// so bypass it and provide a convincing imitation using a pty.
let task = cx.background_spawn(async move {
let env = env.await;
let pty_system = native_pty_system();
let (command, args) = ShellBuilder::new(
remote_shell.as_deref(),
&Shell::Program(get_default_system_shell()),
)
.redirect_stdin_to_dev_null()
.build(Some(input.command.clone()), &[]);
let (command, args) = build_cmd();
let mut cmd = CommandBuilder::new(command);
cmd.args(args);
for (k, v) in env {
@@ -187,16 +194,10 @@ impl Tool for TerminalTool {
};
};
let command = input.command.clone();
let terminal = cx.spawn({
let project = project.downgrade();
async move |cx| {
let (command, args) = ShellBuilder::new(
remote_shell.as_deref(),
&Shell::Program(get_default_system_shell()),
)
.redirect_stdin_to_dev_null()
.build(Some(input.command), &[]);
let (command, args) = build_cmd();
let env = env.await;
project
.update(cx, |project, cx| {
@@ -215,18 +216,18 @@ impl Tool for TerminalTool {
}
});
let command_markdown =
cx.new(|cx| Markdown::new(format!("```bash\n{}\n```", command).into(), None, None, cx));
let card = cx.new(|cx| {
TerminalToolCard::new(
command_markdown.clone(),
working_dir.clone(),
cx.entity_id(),
let command_markdown = cx.new(|cx| {
Markdown::new(
format!("```bash\n{}\n```", input.command).into(),
None,
None,
cx,
)
});
let card =
cx.new(|cx| TerminalToolCard::new(command_markdown, working_dir, cx.entity_id(), cx));
let output = cx.spawn({
let card = card.clone();
async move |cx| {
@@ -267,7 +268,7 @@ impl Tool for TerminalTool {
let previous_len = content.len();
let (processed_content, finished_with_empty_output) = process_content(
&content,
&command,
&input.command,
exit_status.map(portable_pty::ExitStatus::from),
);

View File

@@ -3,7 +3,8 @@ use client::{Client, TelemetrySettings};
use db::RELEASE_CHANNEL;
use db::kvp::KEY_VALUE_STORE;
use gpui::{
App, AppContext as _, AsyncApp, Context, Entity, Global, SemanticVersion, Task, Window, actions,
App, AppContext as _, AsyncApp, BackgroundExecutor, Context, Entity, Global, SemanticVersion,
Task, Window, actions,
};
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use paths::remote_servers_dir;
@@ -13,6 +14,7 @@ use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsKey, SettingsSources, SettingsStore, SettingsUi};
use smol::{fs, io::AsyncReadExt};
use smol::{fs::File, process::Command};
use std::mem;
use std::{
env::{
self,
@@ -85,31 +87,37 @@ pub struct JsonRelease {
pub url: String,
}
struct MacOsUnmounter {
struct MacOsUnmounter<'a> {
mount_path: PathBuf,
background_executor: &'a BackgroundExecutor,
}
impl Drop for MacOsUnmounter {
impl Drop for MacOsUnmounter<'_> {
fn drop(&mut self) {
let unmount_output = std::process::Command::new("hdiutil")
.args(["detach", "-force"])
.arg(&self.mount_path)
.output();
match unmount_output {
Ok(output) if output.status.success() => {
log::info!("Successfully unmounted the disk image");
}
Ok(output) => {
log::error!(
"Failed to unmount disk image: {:?}",
String::from_utf8_lossy(&output.stderr)
);
}
Err(error) => {
log::error!("Error while trying to unmount disk image: {:?}", error);
}
}
let mount_path = mem::take(&mut self.mount_path);
self.background_executor
.spawn(async move {
let unmount_output = Command::new("hdiutil")
.args(["detach", "-force"])
.arg(&mount_path)
.output()
.await;
match unmount_output {
Ok(output) if output.status.success() => {
log::info!("Successfully unmounted the disk image");
}
Ok(output) => {
log::error!(
"Failed to unmount disk image: {:?}",
String::from_utf8_lossy(&output.stderr)
);
}
Err(error) => {
log::error!("Error while trying to unmount disk image: {:?}", error);
}
}
})
.detach();
}
}
@@ -929,6 +937,7 @@ async fn install_release_macos(
// Create an MacOsUnmounter that will be dropped (and thus unmount the disk) when this function exits
let _unmounter = MacOsUnmounter {
mount_path: mount_path.clone(),
background_executor: cx.background_executor(),
};
let output = Command::new("rsync")

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

@@ -26,7 +26,7 @@ use sum_tree::{Bias, Dimensions, SumTree, Summary, TreeMap};
use text::{BufferId, Edit};
use ui::ElementId;
const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize];
const NEWLINES: &[u8] = &[b'\n'; u128::BITS as usize];
const BULLETS: &str = "********************************************************************************************************************************";
/// Tracks custom blocks such as diagnostics that should be displayed within buffer.
@@ -1726,12 +1726,13 @@ impl<'a> Iterator for BlockChunks<'a> {
let start_in_block = self.output_row - block_start;
let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
let line_count = end_in_block - start_in_block;
// todo: We need to split the chunk here?
let line_count = cmp::min(end_in_block - start_in_block, u128::BITS);
self.output_row += line_count;
return Some(Chunk {
text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) },
chars: (1 << line_count) - 1,
chars: 1u128.unbounded_shl(line_count) - 1,
..Default::default()
});
}
@@ -1746,6 +1747,7 @@ impl<'a> Iterator for BlockChunks<'a> {
if self.transforms.item().is_some() {
return Some(Chunk {
text: "\n",
chars: 1,
..Default::default()
});
}
@@ -1773,7 +1775,7 @@ impl<'a> Iterator for BlockChunks<'a> {
let chars_count = prefix.chars().count();
let bullet_len = chars_count;
prefix = &BULLETS[..bullet_len];
chars = (1 << bullet_len) - 1;
chars = 1u128.unbounded_shl(bullet_len as u32) - 1;
tabs = 0;
}

View File

@@ -551,7 +551,7 @@ impl TabChunks<'_> {
self.chunk = Chunk {
text: &SPACES[0..(to_next_stop as usize)],
is_tab: true,
chars: (1u128 << to_next_stop) - 1,
chars: 1u128.unbounded_shl(to_next_stop) - 1,
..Default::default()
};
self.inside_leading_tab = to_next_stop > 0;
@@ -623,7 +623,7 @@ impl<'a> Iterator for TabChunks<'a> {
return Some(Chunk {
text: &SPACES[..len as usize],
is_tab: true,
chars: (1 << len) - 1,
chars: 1u128.unbounded_shl(len) - 1,
tabs: 0,
..self.chunk.clone()
});

View File

@@ -7,9 +7,7 @@ use std::ops::Range;
use text::{Point, ToOffset};
use crate::{
EditPrediction,
editor_tests::{init_test, update_test_language_settings},
test::editor_test_context::EditorTestContext,
EditPrediction, editor_tests::init_test, test::editor_test_context::EditorTestContext,
};
#[gpui::test]
@@ -273,44 +271,6 @@ async fn test_edit_prediction_jump_disabled_for_non_zed_providers(cx: &mut gpui:
});
}
#[gpui::test]
async fn test_edit_predictions_disabled_in_scope(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
update_test_language_settings(cx, |settings| {
settings.defaults.edit_predictions_disabled_in = Some(vec!["string".to_string()]);
});
let mut cx = EditorTestContext::new(cx).await;
let provider = cx.new(|_| FakeEditPredictionProvider::default());
assign_editor_completion_provider(provider.clone(), &mut cx);
let language = languages::language("javascript", tree_sitter_typescript::LANGUAGE_TSX.into());
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
// Test disabled inside of string
cx.set_state("const x = \"hello ˇworld\";");
propose_edits(&provider, vec![(17..17, "beautiful ")], &mut cx);
cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
cx.editor(|editor, _, _| {
assert!(
editor.active_edit_prediction.is_none(),
"Edit predictions should be disabled in string scopes when configured in edit_predictions_disabled_in"
);
});
// Test enabled outside of string
cx.set_state("const x = \"hello world\"; ˇ");
propose_edits(&provider, vec![(24..24, "// comment")], &mut cx);
cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
cx.editor(|editor, _, _| {
assert!(
editor.active_edit_prediction.is_some(),
"Edit predictions should work outside of disabled scopes"
);
});
}
fn assert_editor_active_edit_completion(
cx: &mut EditorTestContext,
assert: impl FnOnce(MultiBufferSnapshot, &Vec<(Range<Anchor>, String)>),

View File

@@ -7149,6 +7149,8 @@ impl Editor {
return None;
}
self.update_visible_edit_prediction(window, cx);
if !user_requested
&& (!self.should_show_edit_predictions()
|| !self.is_focused(window)
@@ -7158,7 +7160,6 @@ impl Editor {
return None;
}
self.update_visible_edit_prediction(window, cx);
provider.refresh(
self.project.clone(),
buffer,
@@ -7851,11 +7852,6 @@ impl Editor {
self.edit_prediction_settings =
self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
self.discard_edit_prediction(false, cx);
return None;
};
self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
if self.edit_prediction_indent_conflict {

View File

@@ -25630,7 +25630,7 @@ async fn test_document_colors(cx: &mut TestAppContext) {
workspace
.update(cx, |workspace, window, cx| {
workspace.active_pane().update(cx, |pane, cx| {
pane.navigate_backward(&Default::default(), window, cx);
pane.navigate_backward(&workspace::GoBack, window, cx);
})
})
.unwrap();
@@ -25658,6 +25658,50 @@ async fn test_document_colors(cx: &mut TestAppContext) {
"Should have an initial inlay"
);
});
drop(color_request_handle);
let closure_requests_made = Arc::clone(&requests_made);
let mut empty_color_request_handle = fake_language_server
.set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
let requests_made = Arc::clone(&closure_requests_made);
async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
);
requests_made.fetch_add(1, atomic::Ordering::Release);
Ok(Vec::new())
}
});
let save = editor.update_in(cx, |editor, window, cx| {
editor.move_to_end(&MoveToEnd, window, cx);
editor.handle_input("dirty_again", window, cx);
editor.save(
SaveOptions {
format: false,
autosave: true,
},
project.clone(),
window,
cx,
)
});
save.await.unwrap();
empty_color_request_handle.next().await.unwrap();
cx.run_until_parked();
assert_eq!(
4,
requests_made.load(atomic::Ordering::Acquire),
"Should query for colors once per save only, as formatting was not requested"
);
editor.update(cx, |editor, cx| {
assert_eq!(
Vec::<Rgba>::new(),
extract_color_inlays(editor, cx),
"Should clear all colors when the server returns an empty response"
);
});
}
#[gpui::test]

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

@@ -898,6 +898,7 @@ fn surrounding_filename(
} else {
// Otherwise, we skip the quote
inside_quotes = true;
token_end += ch.len_utf8();
continue;
}
}
@@ -1545,6 +1546,10 @@ mod tests {
("'fˇile.txt'", Some("file.txt")),
("ˇ'file.txt'", Some("file.txt")),
("ˇ'fi\\ le.txt'", Some("fi le.txt")),
// Quoted multibyte characters
(" ˇ\"\"", Some("")),
(" \"ˇ常\"", Some("")),
("ˇ\"\"", Some("")),
];
for (input, expected) in test_cases {

View File

@@ -228,60 +228,70 @@ impl Editor {
};
match colors {
Ok(colors) => {
for color in colors.colors {
let color_start = point_from_lsp(color.lsp_range.start);
let color_end = point_from_lsp(color.lsp_range.end);
if colors.colors.is_empty() {
let new_entry =
new_editor_colors.entry(buffer_id).or_insert_with(|| {
(Vec::<(Range<Anchor>, DocumentColor)>::new(), None)
});
new_entry.0.clear();
new_entry.1 = colors.cache_version;
} else {
for color in colors.colors {
let color_start = point_from_lsp(color.lsp_range.start);
let color_end = point_from_lsp(color.lsp_range.end);
for (excerpt_id, buffer_snapshot, excerpt_range) in excerpts {
if !excerpt_range.contains(&color_start.0)
|| !excerpt_range.contains(&color_end.0)
{
continue;
for (excerpt_id, buffer_snapshot, excerpt_range) in excerpts {
if !excerpt_range.contains(&color_start.0)
|| !excerpt_range.contains(&color_end.0)
{
continue;
}
let Some(color_start_anchor) = multi_buffer_snapshot
.anchor_in_excerpt(
*excerpt_id,
buffer_snapshot.anchor_before(
buffer_snapshot
.clip_point_utf16(color_start, Bias::Left),
),
)
else {
continue;
};
let Some(color_end_anchor) = multi_buffer_snapshot
.anchor_in_excerpt(
*excerpt_id,
buffer_snapshot.anchor_after(
buffer_snapshot
.clip_point_utf16(color_end, Bias::Right),
),
)
else {
continue;
};
let new_entry =
new_editor_colors.entry(buffer_id).or_insert_with(|| {
(Vec::<(Range<Anchor>, DocumentColor)>::new(), None)
});
new_entry.1 = colors.cache_version;
let new_buffer_colors = &mut new_entry.0;
let (Ok(i) | Err(i)) =
new_buffer_colors.binary_search_by(|(probe, _)| {
probe
.start
.cmp(&color_start_anchor, &multi_buffer_snapshot)
.then_with(|| {
probe.end.cmp(
&color_end_anchor,
&multi_buffer_snapshot,
)
})
});
new_buffer_colors
.insert(i, (color_start_anchor..color_end_anchor, color));
break;
}
let Some(color_start_anchor) = multi_buffer_snapshot
.anchor_in_excerpt(
*excerpt_id,
buffer_snapshot.anchor_before(
buffer_snapshot
.clip_point_utf16(color_start, Bias::Left),
),
)
else {
continue;
};
let Some(color_end_anchor) = multi_buffer_snapshot
.anchor_in_excerpt(
*excerpt_id,
buffer_snapshot.anchor_after(
buffer_snapshot
.clip_point_utf16(color_end, Bias::Right),
),
)
else {
continue;
};
let new_entry =
new_editor_colors.entry(buffer_id).or_insert_with(|| {
(Vec::<(Range<Anchor>, DocumentColor)>::new(), None)
});
new_entry.1 = colors.cache_version;
let new_buffer_colors = &mut new_entry.0;
let (Ok(i) | Err(i)) =
new_buffer_colors.binary_search_by(|(probe, _)| {
probe
.start
.cmp(&color_start_anchor, &multi_buffer_snapshot)
.then_with(|| {
probe
.end
.cmp(&color_end_anchor, &multi_buffer_snapshot)
})
});
new_buffer_colors
.insert(i, (color_start_anchor..color_end_anchor, color));
break;
}
}
}

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

@@ -10,6 +10,10 @@ pub struct BillingV2FeatureFlag {}
impl FeatureFlag for BillingV2FeatureFlag {
const NAME: &'static str = "billing-v2";
fn enabled_for_all() -> bool {
true
}
}
pub struct NotebookFeatureFlag;

View File

@@ -1430,7 +1430,6 @@ impl GitPanel {
self.load_last_commit_message_if_empty(cx);
} else {
telemetry::event!("Git Amended", source = "Git Panel");
self.set_amend_pending(false, cx);
self.commit_changes(
CommitOptions {
amend: true,
@@ -1604,6 +1603,9 @@ impl GitPanel {
});
self.pending_commit = Some(task);
if options.amend {
self.set_amend_pending(false, cx);
}
}
pub(crate) fn uncommit(&mut self, window: &mut Window, cx: &mut Context<Self>) {
@@ -3453,7 +3455,6 @@ impl GitPanel {
telemetry::event!("Git Committed", source = "Git Panel");
git_panel
.update(cx, |git_panel, cx| {
git_panel.set_amend_pending(false, cx);
git_panel.commit_changes(
CommitOptions { amend, signoff },
window,

View File

@@ -397,7 +397,6 @@ impl MacTextSystemState {
.subpixel_variant
.map(|v| v as f32 / SUBPIXEL_VARIANTS as f32);
cx.set_allows_font_smoothing(true);
cx.set_should_smooth_fonts(true);
cx.set_text_drawing_mode(CGTextDrawingMode::CGTextFill);
cx.set_gray_fill_color(0.0, 1.0);
cx.set_allows_antialiasing(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

@@ -29,6 +29,7 @@ copilot.workspace = true
credentials_provider.workspace = true
deepseek = { workspace = true, features = ["schemars"] }
editor.workspace = true
feature_flags.workspace = true
fs.workspace = true
futures.workspace = true
google_ai = { workspace = true, features = ["schemars"] }

View File

@@ -11,6 +11,7 @@ use cloud_llm_client::{
SUBSCRIPTION_LIMIT_RESOURCE_HEADER_NAME, TOOL_USE_LIMIT_REACHED_HEADER_NAME,
ZED_VERSION_HEADER_NAME,
};
use feature_flags::{BillingV2FeatureFlag, FeatureFlagAppExt};
use futures::{
AsyncBufReadExt, FutureExt, Stream, StreamExt, future::BoxFuture, stream::BoxStream,
};
@@ -1010,12 +1011,13 @@ struct ZedAiConfiguration {
}
impl RenderOnce for ZedAiConfiguration {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let young_account_banner = YoungAccountBanner;
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let is_pro = self.plan.is_some_and(|plan| {
matches!(plan, Plan::V1(PlanV1::ZedPro) | Plan::V2(PlanV2::ZedPro))
});
let is_free_v2 = self
.plan
.is_some_and(|plan| plan == Plan::V2(PlanV2::ZedFree));
let subscription_text = match (self.plan, self.subscription_period) {
(Some(Plan::V1(PlanV1::ZedPro) | Plan::V2(PlanV2::ZedPro)), Some(_)) => {
"You have access to Zed's hosted models through your Pro subscription."
@@ -1023,9 +1025,16 @@ impl RenderOnce for ZedAiConfiguration {
(Some(Plan::V1(PlanV1::ZedProTrial) | Plan::V2(PlanV2::ZedProTrial)), Some(_)) => {
"You have access to Zed's hosted models through your Pro trial."
}
(Some(Plan::V1(PlanV1::ZedFree) | Plan::V2(PlanV2::ZedFree)), Some(_)) => {
(Some(Plan::V1(PlanV1::ZedFree)), Some(_)) => {
"You have basic access to Zed's hosted models through the Free plan."
}
(Some(Plan::V2(PlanV2::ZedFree)), Some(_)) => {
if self.eligible_for_trial {
"Subscribe for access to Zed's hosted models. Start with a 14 day free trial."
} else {
"Subscribe for access to Zed's hosted models."
}
}
_ => {
if self.eligible_for_trial {
"Subscribe for access to Zed's hosted models. Start with a 14 day free trial."
@@ -1075,7 +1084,10 @@ impl RenderOnce for ZedAiConfiguration {
v_flex().gap_2().w_full().map(|this| {
if self.account_too_young {
this.child(young_account_banner).child(
this.child(YoungAccountBanner::new(
is_free_v2 || cx.has_flag::<BillingV2FeatureFlag>(),
))
.child(
Button::new("upgrade", "Upgrade to Pro")
.style(ui::ButtonStyle::Tinted(ui::TintColor::Accent))
.full_width()

View File

@@ -637,6 +637,24 @@ impl GoogleEventMapper {
convert_usage(&self.usage),
)))
}
if let Some(prompt_feedback) = event.prompt_feedback
&& let Some(block_reason) = prompt_feedback.block_reason.as_deref()
{
self.stop_reason = match block_reason {
"SAFETY" | "OTHER" | "BLOCKLIST" | "PROHIBITED_CONTENT" | "IMAGE_SAFETY" => {
StopReason::Refusal
}
_ => {
log::error!("Unexpected Google block_reason: {block_reason}");
StopReason::Refusal
}
};
events.push(Ok(LanguageModelCompletionEvent::Stop(self.stop_reason)));
return events;
}
if let Some(candidates) = event.candidates {
for candidate in candidates {
if let Some(finish_reason) = candidate.finish_reason.as_deref() {

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,19 +100,19 @@ 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";
}
#[cfg(target_os = "linux")]
impl TyLspAdapter {
const GITHUB_ASSET_KIND: AssetKind = AssetKind::Gz;
const GITHUB_ASSET_KIND: AssetKind = AssetKind::TarGz;
const ARCH_SERVER_NAME: &str = "unknown-linux-gnu";
}
#[cfg(target_os = "freebsd")]
impl TyLspAdapter {
const GITHUB_ASSET_KIND: AssetKind = AssetKind::Gz;
const GITHUB_ASSET_KIND: AssetKind = AssetKind::TarGz;
const ARCH_SERVER_NAME: &str = "unknown-freebsd";
}
@@ -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

@@ -958,7 +958,12 @@ impl ProjectSearchView {
.and_then(|item| item.downcast::<ProjectSearchView>())
{
let new_query = search_view.update(cx, |search_view, cx| {
let new_query = search_view.build_search_query(cx);
let open_buffers = if search_view.included_opened_only {
Some(search_view.open_buffers(cx, workspace))
} else {
None
};
let new_query = search_view.build_search_query(cx, open_buffers);
if new_query.is_some()
&& let Some(old_query) = search_view.entity.read(cx).active_query.clone()
{
@@ -1134,7 +1139,14 @@ impl ProjectSearchView {
}
fn search(&mut self, cx: &mut Context<Self>) {
if let Some(query) = self.build_search_query(cx) {
let open_buffers = if self.included_opened_only {
self.workspace
.update(cx, |workspace, cx| self.open_buffers(cx, workspace))
.ok()
} else {
None
};
if let Some(query) = self.build_search_query(cx, open_buffers) {
self.entity.update(cx, |model, cx| model.search(query, cx));
}
}
@@ -1143,14 +1155,13 @@ impl ProjectSearchView {
self.query_editor.read(cx).text(cx)
}
fn build_search_query(&mut self, cx: &mut Context<Self>) -> Option<SearchQuery> {
fn build_search_query(
&mut self,
cx: &mut Context<Self>,
open_buffers: Option<Vec<Entity<Buffer>>>,
) -> Option<SearchQuery> {
// Do not bail early in this function, as we want to fill out `self.panels_with_errors`.
let text = self.search_query_text(cx);
let open_buffers = if self.included_opened_only {
Some(self.open_buffers(cx))
} else {
None
};
let included_files = self
.filters_enabled
.then(|| {
@@ -1286,17 +1297,13 @@ impl ProjectSearchView {
query
}
fn open_buffers(&self, cx: &mut Context<Self>) -> Vec<Entity<Buffer>> {
fn open_buffers(&self, cx: &App, workspace: &Workspace) -> Vec<Entity<Buffer>> {
let mut buffers = Vec::new();
self.workspace
.update(cx, |workspace, cx| {
for editor in workspace.items_of_type::<Editor>(cx) {
if let Some(buffer) = editor.read(cx).buffer().read(cx).as_singleton() {
buffers.push(buffer);
}
}
})
.ok();
for editor in workspace.items_of_type::<Editor>(cx) {
if let Some(buffer) = editor.read(cx).buffer().read(cx).as_singleton() {
buffers.push(buffer);
}
}
buffers
}

View File

View File

@@ -210,6 +210,7 @@ pub struct ShellBuilder {
program: String,
args: Vec<String>,
interactive: bool,
/// Whether to redirect stdin to /dev/null for the spawned command as a subshell.
redirect_stdin: bool,
kind: ShellKind,
}
@@ -279,10 +280,12 @@ impl ShellBuilder {
if self.redirect_stdin {
match self.kind {
ShellKind::Posix | ShellKind::Nushell | ShellKind::Fish | ShellKind::Csh => {
combined_command.push_str(" </dev/null");
combined_command.insert(0, '(');
combined_command.push_str(") </dev/null");
}
ShellKind::PowerShell => {
combined_command.insert_str(0, "$null | ");
combined_command.insert_str(0, "$null | {");
combined_command.push_str("}");
}
ShellKind::Cmd => {
combined_command.push_str("< NUL");
@@ -329,4 +332,17 @@ mod test {
]
);
}
#[test]
fn redirect_stdin_to_dev_null_precedence() {
let shell = Shell::Program("nu".to_owned());
let shell_builder = ShellBuilder::new(None, &shell);
let (program, args) = shell_builder
.redirect_stdin_to_dev_null()
.build(Some("echo".into()), &["nothing".to_string()]);
assert_eq!(program, "nu");
assert_eq!(args, vec!["-i", "-c", "(echo nothing) </dev/null"]);
}
}

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

@@ -4,7 +4,7 @@ mod onboarding_banner;
pub mod platform_title_bar;
mod platforms;
mod system_window_tabs;
pub mod title_bar_settings;
mod title_bar_settings;
#[cfg(feature = "stories")]
mod stories;
@@ -35,7 +35,7 @@ use remote::RemoteConnectionOptions;
use settings::{Settings, SettingsLocation};
use std::{path::Path, sync::Arc};
use theme::ActiveTheme;
use title_bar_settings::{TitleBarSettings, TitleBarVisibility};
use title_bar_settings::TitleBarSettings;
use ui::{
Avatar, Button, ButtonLike, ButtonStyle, Chip, ContextMenu, Icon, IconName, IconSize,
IconWithIndicator, Indicator, PopoverMenu, PopoverMenuHandle, Tooltip, h_flex, prelude::*,
@@ -73,49 +73,8 @@ pub fn init(cx: &mut App) {
let Some(window) = window else {
return;
};
let should_show = match TitleBarSettings::get_global(cx).show {
TitleBarVisibility::Always => true,
TitleBarVisibility::Never => false,
TitleBarVisibility::HideInFullScreen => !window.is_fullscreen(),
};
if should_show {
let item = cx.new(|cx| TitleBar::new("title-bar", workspace, window, cx));
workspace.set_titlebar_item(item.into(), window, cx);
}
cx.observe_global_in::<settings::SettingsStore>(window, |workspace, window, cx| {
let should_show = match TitleBarSettings::get_global(cx).show {
TitleBarVisibility::Always => true,
TitleBarVisibility::Never => false,
TitleBarVisibility::HideInFullScreen => !window.is_fullscreen(),
};
if should_show {
if workspace.titlebar_item().is_none() {
let item = cx.new(|cx| TitleBar::new("title-bar", workspace, window, cx));
workspace.set_titlebar_item(item.into(), window, cx);
}
} else {
workspace.clear_titlebar_item(window, cx);
}
})
.detach();
cx.observe_window_bounds(window, |workspace, window, cx| {
let should_show = match TitleBarSettings::get_global(cx).show {
TitleBarVisibility::Always => true,
TitleBarVisibility::Never => false,
TitleBarVisibility::HideInFullScreen => !window.is_fullscreen(),
};
if should_show {
if workspace.titlebar_item().is_none() {
let item = cx.new(|cx| TitleBar::new("title-bar", workspace, window, cx));
workspace.set_titlebar_item(item.into(), window, cx);
}
} else {
workspace.clear_titlebar_item(window, cx);
}
})
.detach();
let item = cx.new(|cx| TitleBar::new("title-bar", workspace, window, cx));
workspace.set_titlebar_item(item.into(), window, cx);
#[cfg(not(target_os = "macos"))]
workspace.register_action(|workspace, action: &OpenApplicationMenu, window, cx| {

View File

@@ -3,17 +3,8 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
#[derive(Copy, Clone, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
#[serde(rename_all = "snake_case")]
pub enum TitleBarVisibility {
Always,
Never,
HideInFullScreen,
}
#[derive(Copy, Clone, Deserialize, Debug)]
pub struct TitleBarSettings {
pub show: TitleBarVisibility,
pub show_branch_icon: bool,
pub show_onboarding_banner: bool,
pub show_user_picture: bool,
@@ -29,10 +20,6 @@ pub struct TitleBarSettings {
#[settings_ui(group = "Title Bar")]
#[settings_key(key = "title_bar")]
pub struct TitleBarSettingsContent {
/// Controls when the title bar is visible: "always" | "never" | "hide_in_full_screen".
///
/// Default: "always"
pub show: Option<TitleBarVisibility>,
/// Whether to show the branch icon beside branch switcher in the title bar.
///
/// Default: false

View File

@@ -97,15 +97,10 @@ pub fn highlight_ranges(
let mut end_ix = start_ix;
loop {
end_ix = end_ix + text[end_ix..].chars().next().unwrap().len_utf8();
if let Some(&next_ix) = highlight_indices.peek()
&& next_ix == end_ix
{
end_ix = next_ix;
highlight_indices.next();
continue;
end_ix += text[end_ix..].chars().next().map_or(0, |c| c.len_utf8());
if highlight_indices.next_if(|&ix| ix == end_ix).is_none() {
break;
}
break;
}
highlights.push((start_ix..end_ix, style));

View File

@@ -2043,11 +2043,6 @@ impl Workspace {
cx.notify();
}
pub fn clear_titlebar_item(&mut self, _: &mut Window, cx: &mut Context<Self>) {
self.titlebar_item = None;
cx.notify();
}
pub fn set_prompt_for_new_path(&mut self, prompt: PromptForNewPath) {
self.on_prompt_for_new_path = Some(prompt)
}

View File

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

View File

@@ -1 +1 @@
dev
stable

View File

@@ -4125,7 +4125,6 @@ Run the {#action theme_selector::Toggle} action in the command palette to see a
```json
"title_bar": {
"show": "always",
"show_branch_icon": false,
"show_branch_name": true,
"show_project_items": true,

View File

@@ -108,7 +108,6 @@ To disable this behavior use:
```json
// Control which items are shown/hidden in the title bar
"title_bar": {
"show": "always", // When to show: always | never | hide_in_full_screen
"show_branch_icon": false, // Show/hide branch icon beside branch switcher
"show_branch_name": true, // Show/hide branch name
"show_project_items": true, // Show/hide project host and name

View File

@@ -75,6 +75,7 @@ hmac = { version = "0.12", default-features = false, features = ["reset"] }
hyper = { version = "0.14", features = ["client", "http1", "http2", "runtime", "server", "stream"] }
idna = { version = "1" }
indexmap = { version = "2", features = ["serde"] }
itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
jiff = { version = "0.2" }
lazy_static = { version = "1", default-features = false, features = ["spin_no_std"] }
libc = { version = "0.2", features = ["extra_traits"] }
@@ -213,6 +214,7 @@ hyper = { version = "0.14", features = ["client", "http1", "http2", "runtime", "
idna = { version = "1" }
indexmap = { version = "2", features = ["serde"] }
itertools-594e8ee84c453af0 = { package = "itertools", version = "0.13" }
itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
jiff = { version = "0.2" }
lazy_static = { version = "1", default-features = false, features = ["spin_no_std"] }
libc = { version = "0.2", features = ["extra_traits"] }
@@ -333,7 +335,6 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] }
getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" }
livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" }
naga = { version = "25", features = ["msl-out", "wgsl-in"] }
nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] }
@@ -395,7 +396,6 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] }
getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" }
livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" }
naga = { version = "25", features = ["msl-out", "wgsl-in"] }
nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] }
@@ -474,7 +474,6 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f
gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
inout = { version = "0.1", default-features = false, features = ["block-padding"] }
itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" }
linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] }
linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" }
@@ -555,7 +554,6 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f
gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
inout = { version = "0.1", default-features = false, features = ["block-padding"] }
itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" }
linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] }
linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" }
@@ -614,7 +612,6 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] }
getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-features = false, features = ["js", "rdrand"] }
hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" }
livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" }
num = { version = "0.4" }
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
@@ -690,7 +687,6 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f
gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
inout = { version = "0.1", default-features = false, features = ["block-padding"] }
itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" }
linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] }
linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" }