Compare commits
42 Commits
devcontain
...
v0.205.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d6de5677d | ||
|
|
e923694a03 | ||
|
|
22f1ad6433 | ||
|
|
6099d02539 | ||
|
|
6821efc4a8 | ||
|
|
5e464fcbb7 | ||
|
|
e059b6a31d | ||
|
|
7e837b43d2 | ||
|
|
767968aa36 | ||
|
|
e74d6a9bd6 | ||
|
|
c4873bf8dc | ||
|
|
39de188584 | ||
|
|
d3c3ef9481 | ||
|
|
79468c119e | ||
|
|
f4ee55e4bf | ||
|
|
8214adc728 | ||
|
|
386bce81c6 | ||
|
|
cc161b36b4 | ||
|
|
0369737fcb | ||
|
|
1348dbbd46 | ||
|
|
a588f85a41 | ||
|
|
d4112ffa0e | ||
|
|
22cb2d46c1 | ||
|
|
9e519ac47a | ||
|
|
1755b67937 | ||
|
|
3fc49233cc | ||
|
|
08b3cb354f | ||
|
|
bd784b42c1 | ||
|
|
e428167833 | ||
|
|
eeab1649de | ||
|
|
f519fd7f90 | ||
|
|
e6cae4e7ed | ||
|
|
49f98ca4f6 | ||
|
|
6b8b1e6859 | ||
|
|
de412e4c71 | ||
|
|
3e55f14863 | ||
|
|
900e1adcda | ||
|
|
5229cc75ac | ||
|
|
844f3eeb71 | ||
|
|
ab4687391a | ||
|
|
d5cf9be162 | ||
|
|
3530445baf |
18
Cargo.lock
generated
18
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -239,7 +239,7 @@
|
||||
"hint": {
|
||||
"color": "#628b80ff",
|
||||
"font_style": null,
|
||||
"font_weight": 700
|
||||
"font_weight": null
|
||||
},
|
||||
"keyword": {
|
||||
"color": "#ff8f3fff",
|
||||
|
||||
@@ -248,7 +248,7 @@
|
||||
"hint": {
|
||||
"color": "#8c957dff",
|
||||
"font_style": null,
|
||||
"font_weight": 700
|
||||
"font_weight": null
|
||||
},
|
||||
"keyword": {
|
||||
"color": "#fb4833ff",
|
||||
|
||||
@@ -244,7 +244,7 @@
|
||||
"hint": {
|
||||
"color": "#788ca6ff",
|
||||
"font_style": null,
|
||||
"font_weight": 700
|
||||
"font_weight": null
|
||||
},
|
||||
"keyword": {
|
||||
"color": "#b477cfff",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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"))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
});
|
||||
|
||||
@@ -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)>),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
0
crates/settings/src/settings_content.rs
Normal file
0
crates/settings/src/settings_content.rs
Normal 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"]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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>"]
|
||||
|
||||
@@ -1 +1 @@
|
||||
dev
|
||||
stable
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" }
|
||||
|
||||
Reference in New Issue
Block a user