Compare commits
26 Commits
shell-quot
...
v0.206.6-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62f2df90ed | ||
|
|
6412621855 | ||
|
|
bc42327f8e | ||
|
|
fa163e7049 | ||
|
|
5041b51d49 | ||
|
|
b801ab8c9c | ||
|
|
e2b5fbc4fd | ||
|
|
8e75ee673f | ||
|
|
3dcae67cb5 | ||
|
|
e1876a1486 | ||
|
|
c81b4c8f7e | ||
|
|
d54783bccd | ||
|
|
8baaea6ae3 | ||
|
|
3ff1eef029 | ||
|
|
2828705f39 | ||
|
|
a3f2838380 | ||
|
|
aeddd518a6 | ||
|
|
5e4d3970d1 | ||
|
|
0f0f9c93ce | ||
|
|
4ee165dd60 | ||
|
|
94d19baf4e | ||
|
|
70cb2f5d55 | ||
|
|
15baa8f5ed | ||
|
|
c68345f502 | ||
|
|
72007a075a | ||
|
|
c68602612f |
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -195,9 +195,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "agent-client-protocol"
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00e33b9f4bd34d342b6f80b7156d3a37a04aeec16313f264001e52d6a9118600"
|
||||
checksum = "3aaa2bd05a2401887945f8bfd70026e90bc3cf96c62ab9eba2779835bf21dc60"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-broadcast",
|
||||
@@ -21217,7 +21217,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.206.0"
|
||||
version = "0.206.6"
|
||||
dependencies = [
|
||||
"acp_tools",
|
||||
"activity_indicator",
|
||||
|
||||
@@ -443,7 +443,7 @@ zlog_settings = { path = "crates/zlog_settings" }
|
||||
# External crates
|
||||
#
|
||||
|
||||
agent-client-protocol = { version = "0.4.2", features = ["unstable"] }
|
||||
agent-client-protocol = { version = "0.4.3", features = ["unstable"] }
|
||||
aho-corasick = "1.1"
|
||||
alacritty_terminal = "0.25.1-rc1"
|
||||
any_vec = "0.14"
|
||||
|
||||
@@ -1780,20 +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, _| {
|
||||
@@ -1820,15 +1826,17 @@ impl AcpThread {
|
||||
};
|
||||
|
||||
let max_point = snapshot.max_point();
|
||||
if line >= max_point.row {
|
||||
anyhow::bail!(
|
||||
let start_position = Point::new(line, 0);
|
||||
|
||||
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(Point::new(line, 0));
|
||||
let start = snapshot.anchor_before(start_position);
|
||||
let end = snapshot.anchor_before(Point::new(line.saturating_add(limit), 0));
|
||||
|
||||
project.update(cx, |project, cx| {
|
||||
@@ -1977,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
|
||||
@@ -1988,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()
|
||||
@@ -2449,6 +2457,81 @@ mod tests {
|
||||
|
||||
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| {
|
||||
@@ -2459,9 +2542,40 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"Attempting to read beyond the end of the file, line 5:0"
|
||||
"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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -67,7 +67,6 @@ pub enum Model {
|
||||
alias = "claude-opus-4-1-thinking-latest"
|
||||
)]
|
||||
ClaudeOpus4_1Thinking,
|
||||
#[default]
|
||||
#[serde(rename = "claude-sonnet-4", alias = "claude-sonnet-4-latest")]
|
||||
ClaudeSonnet4,
|
||||
#[serde(
|
||||
@@ -75,6 +74,14 @@ pub enum Model {
|
||||
alias = "claude-sonnet-4-thinking-latest"
|
||||
)]
|
||||
ClaudeSonnet4Thinking,
|
||||
#[default]
|
||||
#[serde(rename = "claude-sonnet-4-5", alias = "claude-sonnet-4-5-latest")]
|
||||
ClaudeSonnet4_5,
|
||||
#[serde(
|
||||
rename = "claude-sonnet-4-5-thinking",
|
||||
alias = "claude-sonnet-4-5-thinking-latest"
|
||||
)]
|
||||
ClaudeSonnet4_5Thinking,
|
||||
#[serde(rename = "claude-3-7-sonnet", alias = "claude-3-7-sonnet-latest")]
|
||||
Claude3_7Sonnet,
|
||||
#[serde(
|
||||
@@ -133,6 +140,14 @@ impl Model {
|
||||
return Ok(Self::ClaudeOpus4);
|
||||
}
|
||||
|
||||
if id.starts_with("claude-sonnet-4-5-thinking") {
|
||||
return Ok(Self::ClaudeSonnet4_5Thinking);
|
||||
}
|
||||
|
||||
if id.starts_with("claude-sonnet-4-5") {
|
||||
return Ok(Self::ClaudeSonnet4_5);
|
||||
}
|
||||
|
||||
if id.starts_with("claude-sonnet-4-thinking") {
|
||||
return Ok(Self::ClaudeSonnet4Thinking);
|
||||
}
|
||||
@@ -180,6 +195,8 @@ impl Model {
|
||||
Self::ClaudeOpus4_1Thinking => "claude-opus-4-1-thinking-latest",
|
||||
Self::ClaudeSonnet4 => "claude-sonnet-4-latest",
|
||||
Self::ClaudeSonnet4Thinking => "claude-sonnet-4-thinking-latest",
|
||||
Self::ClaudeSonnet4_5 => "claude-sonnet-4-5-latest",
|
||||
Self::ClaudeSonnet4_5Thinking => "claude-sonnet-4-5-thinking-latest",
|
||||
Self::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
|
||||
Self::Claude3_7Sonnet => "claude-3-7-sonnet-latest",
|
||||
Self::Claude3_7SonnetThinking => "claude-3-7-sonnet-thinking-latest",
|
||||
@@ -197,6 +214,7 @@ impl Model {
|
||||
Self::ClaudeOpus4 | Self::ClaudeOpus4Thinking => "claude-opus-4-20250514",
|
||||
Self::ClaudeOpus4_1 | Self::ClaudeOpus4_1Thinking => "claude-opus-4-1-20250805",
|
||||
Self::ClaudeSonnet4 | Self::ClaudeSonnet4Thinking => "claude-sonnet-4-20250514",
|
||||
Self::ClaudeSonnet4_5 | Self::ClaudeSonnet4_5Thinking => "claude-sonnet-4-5-20250929",
|
||||
Self::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
|
||||
Self::Claude3_7Sonnet | Self::Claude3_7SonnetThinking => "claude-3-7-sonnet-latest",
|
||||
Self::Claude3_5Haiku => "claude-3-5-haiku-latest",
|
||||
@@ -215,6 +233,8 @@ impl Model {
|
||||
Self::ClaudeOpus4_1Thinking => "Claude Opus 4.1 Thinking",
|
||||
Self::ClaudeSonnet4 => "Claude Sonnet 4",
|
||||
Self::ClaudeSonnet4Thinking => "Claude Sonnet 4 Thinking",
|
||||
Self::ClaudeSonnet4_5 => "Claude Sonnet 4.5",
|
||||
Self::ClaudeSonnet4_5Thinking => "Claude Sonnet 4.5 Thinking",
|
||||
Self::Claude3_7Sonnet => "Claude 3.7 Sonnet",
|
||||
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
|
||||
Self::Claude3_7SonnetThinking => "Claude 3.7 Sonnet Thinking",
|
||||
@@ -236,6 +256,8 @@ impl Model {
|
||||
| Self::ClaudeOpus4_1Thinking
|
||||
| Self::ClaudeSonnet4
|
||||
| Self::ClaudeSonnet4Thinking
|
||||
| Self::ClaudeSonnet4_5
|
||||
| Self::ClaudeSonnet4_5Thinking
|
||||
| Self::Claude3_5Sonnet
|
||||
| Self::Claude3_5Haiku
|
||||
| Self::Claude3_7Sonnet
|
||||
@@ -261,6 +283,8 @@ impl Model {
|
||||
| Self::ClaudeOpus4_1Thinking
|
||||
| Self::ClaudeSonnet4
|
||||
| Self::ClaudeSonnet4Thinking
|
||||
| Self::ClaudeSonnet4_5
|
||||
| Self::ClaudeSonnet4_5Thinking
|
||||
| Self::Claude3_5Sonnet
|
||||
| Self::Claude3_5Haiku
|
||||
| Self::Claude3_7Sonnet
|
||||
@@ -280,6 +304,8 @@ impl Model {
|
||||
| Self::ClaudeOpus4_1Thinking
|
||||
| Self::ClaudeSonnet4
|
||||
| Self::ClaudeSonnet4Thinking
|
||||
| Self::ClaudeSonnet4_5
|
||||
| Self::ClaudeSonnet4_5Thinking
|
||||
| Self::Claude3_5Sonnet
|
||||
| Self::Claude3_7Sonnet
|
||||
| Self::Claude3_7SonnetThinking
|
||||
@@ -299,6 +325,8 @@ impl Model {
|
||||
| Self::ClaudeOpus4_1Thinking
|
||||
| Self::ClaudeSonnet4
|
||||
| Self::ClaudeSonnet4Thinking
|
||||
| Self::ClaudeSonnet4_5
|
||||
| Self::ClaudeSonnet4_5Thinking
|
||||
| Self::Claude3_5Sonnet
|
||||
| Self::Claude3_7Sonnet
|
||||
| Self::Claude3_7SonnetThinking
|
||||
@@ -318,6 +346,7 @@ impl Model {
|
||||
Self::ClaudeOpus4
|
||||
| Self::ClaudeOpus4_1
|
||||
| Self::ClaudeSonnet4
|
||||
| Self::ClaudeSonnet4_5
|
||||
| Self::Claude3_5Sonnet
|
||||
| Self::Claude3_7Sonnet
|
||||
| Self::Claude3_5Haiku
|
||||
@@ -327,6 +356,7 @@ impl Model {
|
||||
Self::ClaudeOpus4Thinking
|
||||
| Self::ClaudeOpus4_1Thinking
|
||||
| Self::ClaudeSonnet4Thinking
|
||||
| Self::ClaudeSonnet4_5Thinking
|
||||
| Self::Claude3_7SonnetThinking => AnthropicModelMode::Thinking {
|
||||
budget_tokens: Some(4_096),
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
@@ -12,6 +13,7 @@ use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use smol::{fs, io::AsyncReadExt};
|
||||
use smol::{fs::File, process::Command};
|
||||
use std::mem;
|
||||
use std::{
|
||||
env::{
|
||||
self,
|
||||
@@ -84,31 +86,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -896,6 +904,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")
|
||||
|
||||
@@ -22,7 +22,6 @@ pub struct BedrockModelCacheConfiguration {
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
|
||||
pub enum Model {
|
||||
// Anthropic models (already included)
|
||||
#[default]
|
||||
#[serde(rename = "claude-sonnet-4", alias = "claude-sonnet-4-latest")]
|
||||
ClaudeSonnet4,
|
||||
#[serde(
|
||||
@@ -30,6 +29,14 @@ pub enum Model {
|
||||
alias = "claude-sonnet-4-thinking-latest"
|
||||
)]
|
||||
ClaudeSonnet4Thinking,
|
||||
#[default]
|
||||
#[serde(rename = "claude-sonnet-4-5", alias = "claude-sonnet-4-5-latest")]
|
||||
ClaudeSonnet4_5,
|
||||
#[serde(
|
||||
rename = "claude-sonnet-4-5-thinking",
|
||||
alias = "claude-sonnet-4-5-thinking-latest"
|
||||
)]
|
||||
ClaudeSonnet4_5Thinking,
|
||||
#[serde(rename = "claude-opus-4", alias = "claude-opus-4-latest")]
|
||||
ClaudeOpus4,
|
||||
#[serde(rename = "claude-opus-4-1", alias = "claude-opus-4-1-latest")]
|
||||
@@ -144,6 +151,14 @@ impl Model {
|
||||
Ok(Self::Claude3_7Sonnet)
|
||||
} else if id.starts_with("claude-3-7-sonnet-thinking") {
|
||||
Ok(Self::Claude3_7SonnetThinking)
|
||||
} else if id.starts_with("claude-sonnet-4-5-thinking") {
|
||||
Ok(Self::ClaudeSonnet4_5Thinking)
|
||||
} else if id.starts_with("claude-sonnet-4-5") {
|
||||
Ok(Self::ClaudeSonnet4_5)
|
||||
} else if id.starts_with("claude-sonnet-4-thinking") {
|
||||
Ok(Self::ClaudeSonnet4Thinking)
|
||||
} else if id.starts_with("claude-sonnet-4") {
|
||||
Ok(Self::ClaudeSonnet4)
|
||||
} else {
|
||||
anyhow::bail!("invalid model id {id}");
|
||||
}
|
||||
@@ -153,6 +168,8 @@ impl Model {
|
||||
match self {
|
||||
Model::ClaudeSonnet4 => "claude-sonnet-4",
|
||||
Model::ClaudeSonnet4Thinking => "claude-sonnet-4-thinking",
|
||||
Model::ClaudeSonnet4_5 => "claude-sonnet-4-5",
|
||||
Model::ClaudeSonnet4_5Thinking => "claude-sonnet-4-5-thinking",
|
||||
Model::ClaudeOpus4 => "claude-opus-4",
|
||||
Model::ClaudeOpus4_1 => "claude-opus-4-1",
|
||||
Model::ClaudeOpus4Thinking => "claude-opus-4-thinking",
|
||||
@@ -214,6 +231,9 @@ impl Model {
|
||||
Model::ClaudeSonnet4 | Model::ClaudeSonnet4Thinking => {
|
||||
"anthropic.claude-sonnet-4-20250514-v1:0"
|
||||
}
|
||||
Model::ClaudeSonnet4_5 | Model::ClaudeSonnet4_5Thinking => {
|
||||
"anthropic.claude-sonnet-4-5-20250929-v1:0"
|
||||
}
|
||||
Model::ClaudeOpus4 | Model::ClaudeOpus4Thinking => {
|
||||
"anthropic.claude-opus-4-20250514-v1:0"
|
||||
}
|
||||
@@ -277,6 +297,8 @@ impl Model {
|
||||
match self {
|
||||
Self::ClaudeSonnet4 => "Claude Sonnet 4",
|
||||
Self::ClaudeSonnet4Thinking => "Claude Sonnet 4 Thinking",
|
||||
Self::ClaudeSonnet4_5 => "Claude Sonnet 4.5",
|
||||
Self::ClaudeSonnet4_5Thinking => "Claude Sonnet 4.5 Thinking",
|
||||
Self::ClaudeOpus4 => "Claude Opus 4",
|
||||
Self::ClaudeOpus4_1 => "Claude Opus 4.1",
|
||||
Self::ClaudeOpus4Thinking => "Claude Opus 4 Thinking",
|
||||
@@ -346,6 +368,8 @@ impl Model {
|
||||
| Self::ClaudeOpus4
|
||||
| Self::ClaudeOpus4_1
|
||||
| Self::ClaudeSonnet4Thinking
|
||||
| Self::ClaudeSonnet4_5
|
||||
| Self::ClaudeSonnet4_5Thinking
|
||||
| Self::ClaudeOpus4Thinking
|
||||
| Self::ClaudeOpus4_1Thinking => 200_000,
|
||||
Self::AmazonNovaPremier => 1_000_000,
|
||||
@@ -361,6 +385,7 @@ impl Model {
|
||||
Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3_5Haiku => 4_096,
|
||||
Self::Claude3_7Sonnet | Self::Claude3_7SonnetThinking => 128_000,
|
||||
Self::ClaudeSonnet4 | Self::ClaudeSonnet4Thinking => 64_000,
|
||||
Self::ClaudeSonnet4_5 | Self::ClaudeSonnet4_5Thinking => 64_000,
|
||||
Self::ClaudeOpus4
|
||||
| Self::ClaudeOpus4Thinking
|
||||
| Self::ClaudeOpus4_1
|
||||
@@ -385,7 +410,9 @@ impl Model {
|
||||
| Self::ClaudeOpus4_1
|
||||
| Self::ClaudeOpus4_1Thinking
|
||||
| Self::ClaudeSonnet4
|
||||
| Self::ClaudeSonnet4Thinking => 1.0,
|
||||
| Self::ClaudeSonnet4Thinking
|
||||
| Self::ClaudeSonnet4_5
|
||||
| Self::ClaudeSonnet4_5Thinking => 1.0,
|
||||
Self::Custom {
|
||||
default_temperature,
|
||||
..
|
||||
@@ -409,6 +436,8 @@ impl Model {
|
||||
| Self::ClaudeOpus4_1Thinking
|
||||
| Self::ClaudeSonnet4
|
||||
| Self::ClaudeSonnet4Thinking
|
||||
| Self::ClaudeSonnet4_5
|
||||
| Self::ClaudeSonnet4_5Thinking
|
||||
| Self::Claude3_5Haiku => true,
|
||||
|
||||
// Amazon Nova models (all support tool use)
|
||||
@@ -439,6 +468,8 @@ impl Model {
|
||||
| Self::Claude3_7SonnetThinking
|
||||
| Self::ClaudeSonnet4
|
||||
| Self::ClaudeSonnet4Thinking
|
||||
| Self::ClaudeSonnet4_5
|
||||
| Self::ClaudeSonnet4_5Thinking
|
||||
| Self::ClaudeOpus4
|
||||
| Self::ClaudeOpus4Thinking
|
||||
| Self::ClaudeOpus4_1
|
||||
@@ -488,9 +519,11 @@ impl Model {
|
||||
Model::Claude3_7SonnetThinking => BedrockModelMode::Thinking {
|
||||
budget_tokens: Some(4096),
|
||||
},
|
||||
Model::ClaudeSonnet4Thinking => BedrockModelMode::Thinking {
|
||||
budget_tokens: Some(4096),
|
||||
},
|
||||
Model::ClaudeSonnet4Thinking | Model::ClaudeSonnet4_5Thinking => {
|
||||
BedrockModelMode::Thinking {
|
||||
budget_tokens: Some(4096),
|
||||
}
|
||||
}
|
||||
Model::ClaudeOpus4Thinking | Model::ClaudeOpus4_1Thinking => {
|
||||
BedrockModelMode::Thinking {
|
||||
budget_tokens: Some(4096),
|
||||
@@ -542,6 +575,8 @@ impl Model {
|
||||
| Model::Claude3_7SonnetThinking
|
||||
| Model::ClaudeSonnet4
|
||||
| Model::ClaudeSonnet4Thinking
|
||||
| Model::ClaudeSonnet4_5
|
||||
| Model::ClaudeSonnet4_5Thinking
|
||||
| Model::ClaudeOpus4
|
||||
| Model::ClaudeOpus4Thinking
|
||||
| Model::ClaudeOpus4_1
|
||||
@@ -575,6 +610,8 @@ impl Model {
|
||||
| Model::Claude3_7SonnetThinking
|
||||
| Model::ClaudeSonnet4
|
||||
| Model::ClaudeSonnet4Thinking
|
||||
| Model::ClaudeSonnet4_5
|
||||
| Model::ClaudeSonnet4_5Thinking
|
||||
| Model::Claude3Haiku
|
||||
| Model::Claude3Sonnet
|
||||
| Model::MetaLlama321BInstructV1
|
||||
@@ -592,7 +629,9 @@ impl Model {
|
||||
| Model::Claude3_7Sonnet
|
||||
| Model::Claude3_7SonnetThinking
|
||||
| Model::ClaudeSonnet4
|
||||
| Model::ClaudeSonnet4Thinking,
|
||||
| Model::ClaudeSonnet4Thinking
|
||||
| Model::ClaudeSonnet4_5
|
||||
| Model::ClaudeSonnet4_5Thinking,
|
||||
"apac",
|
||||
) => Ok(format!("{}.{}", region_group, model_id)),
|
||||
|
||||
@@ -631,6 +670,10 @@ mod tests {
|
||||
Model::ClaudeSonnet4.cross_region_inference_id("eu-west-1")?,
|
||||
"eu.anthropic.claude-sonnet-4-20250514-v1:0"
|
||||
);
|
||||
assert_eq!(
|
||||
Model::ClaudeSonnet4_5.cross_region_inference_id("eu-west-1")?,
|
||||
"eu.anthropic.claude-sonnet-4-5-20250929-v1:0"
|
||||
);
|
||||
assert_eq!(
|
||||
Model::Claude3Sonnet.cross_region_inference_id("eu-west-1")?,
|
||||
"eu.anthropic.claude-3-sonnet-20240229-v1:0"
|
||||
|
||||
@@ -776,6 +776,8 @@ actions!(
|
||||
UniqueLinesCaseInsensitive,
|
||||
/// Removes duplicate lines (case-sensitive).
|
||||
UniqueLinesCaseSensitive,
|
||||
/// Removes the surrounding syntax node (for example brackets, or closures)
|
||||
/// from the current selections.
|
||||
UnwrapSyntaxNode,
|
||||
/// Wraps selections in tag specified by language.
|
||||
WrapSelectionsInTag
|
||||
|
||||
@@ -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)>),
|
||||
|
||||
@@ -142,7 +142,7 @@ use mouse_context_menu::MouseContextMenu;
|
||||
use movement::TextLayoutDetails;
|
||||
use multi_buffer::{
|
||||
ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
|
||||
MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
|
||||
ToOffsetUtf16,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use persistence::DB;
|
||||
@@ -7152,6 +7152,8 @@ impl Editor {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.update_visible_edit_prediction(window, cx);
|
||||
|
||||
if !user_requested
|
||||
&& (!self.should_show_edit_predictions()
|
||||
|| !self.is_focused(window)
|
||||
@@ -7161,7 +7163,6 @@ impl Editor {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.update_visible_edit_prediction(window, cx);
|
||||
provider.refresh(
|
||||
self.project.clone(),
|
||||
buffer,
|
||||
@@ -7854,11 +7855,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 {
|
||||
@@ -15054,12 +15050,8 @@ impl Editor {
|
||||
}
|
||||
|
||||
let mut new_range = old_range.clone();
|
||||
while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
|
||||
{
|
||||
new_range = match containing_range {
|
||||
MultiOrSingleBufferOffsetRange::Single(_) => break,
|
||||
MultiOrSingleBufferOffsetRange::Multi(range) => range,
|
||||
};
|
||||
while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
|
||||
new_range = range;
|
||||
if !node.is_named() {
|
||||
continue;
|
||||
}
|
||||
@@ -15189,20 +15181,14 @@ impl Editor {
|
||||
&& let Some((_, ancestor_range)) =
|
||||
buffer.syntax_ancestor(selection.start..selection.end)
|
||||
{
|
||||
match ancestor_range {
|
||||
MultiOrSingleBufferOffsetRange::Single(range) => range,
|
||||
MultiOrSingleBufferOffsetRange::Multi(range) => range,
|
||||
}
|
||||
ancestor_range
|
||||
} else {
|
||||
selection.range()
|
||||
};
|
||||
|
||||
let mut parent = child.clone();
|
||||
while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
|
||||
parent = match ancestor_range {
|
||||
MultiOrSingleBufferOffsetRange::Single(range) => range,
|
||||
MultiOrSingleBufferOffsetRange::Multi(range) => range,
|
||||
};
|
||||
parent = ancestor_range;
|
||||
if parent.start < child.start || parent.end > child.end {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -9152,6 +9152,64 @@ async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
|
||||
});
|
||||
|
||||
cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
|
||||
|
||||
cx.set_state(indoc! { r#"fn a() {
|
||||
// what
|
||||
// a
|
||||
// ˇlong
|
||||
// method
|
||||
// I
|
||||
// sure
|
||||
// hope
|
||||
// it
|
||||
// works
|
||||
}"# });
|
||||
|
||||
let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
|
||||
let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
|
||||
cx.update(|_, cx| {
|
||||
multi_buffer.update(cx, |multi_buffer, cx| {
|
||||
multi_buffer.set_excerpts_for_path(
|
||||
PathKey::for_buffer(&buffer, cx),
|
||||
buffer,
|
||||
[Point::new(1, 0)..Point::new(1, 0)],
|
||||
3,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
let editor2 = cx.new_window_entity(|window, cx| {
|
||||
Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
|
||||
});
|
||||
|
||||
let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::default(), window, cx, |s| {
|
||||
s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
|
||||
})
|
||||
});
|
||||
|
||||
cx.assert_editor_state(indoc! { "
|
||||
fn a() {
|
||||
// what
|
||||
// a
|
||||
ˇ // long
|
||||
// method"});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
|
||||
});
|
||||
|
||||
// Although we could potentially make the action work when the syntax node
|
||||
// is half-hidden, it seems a bit dangerous as you can't easily tell what it
|
||||
// did. Maybe we could also expand the excerpt to contain the range?
|
||||
cx.assert_editor_state(indoc! { "
|
||||
fn a() {
|
||||
// what
|
||||
// a
|
||||
ˇ // long
|
||||
// method"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
||||
@@ -9323,7 +9323,7 @@ impl Element for EditorElement {
|
||||
.language_settings(cx)
|
||||
.whitespace_map;
|
||||
|
||||
let tab_char = whitespace_map.tab();
|
||||
let tab_char = whitespace_map.tab.clone();
|
||||
let tab_len = tab_char.len();
|
||||
let tab_invisible = window.text_system().shape_line(
|
||||
tab_char,
|
||||
@@ -9339,7 +9339,7 @@ impl Element for EditorElement {
|
||||
None,
|
||||
);
|
||||
|
||||
let space_char = whitespace_map.space();
|
||||
let space_char = whitespace_map.space.clone();
|
||||
let space_len = space_char.len();
|
||||
let space_invisible = window.text_system().shape_line(
|
||||
space_char,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -16,7 +16,7 @@ use itertools::Itertools;
|
||||
use language::{DiagnosticEntry, Language, LanguageRegistry};
|
||||
use lsp::DiagnosticSeverity;
|
||||
use markdown::{Markdown, MarkdownElement, MarkdownStyle};
|
||||
use multi_buffer::{MultiOrSingleBufferOffsetRange, ToOffset, ToPoint};
|
||||
use multi_buffer::{ToOffset, ToPoint};
|
||||
use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart};
|
||||
use settings::Settings;
|
||||
use std::{borrow::Cow, cell::RefCell};
|
||||
@@ -477,13 +477,8 @@ fn show_hover(
|
||||
})
|
||||
.or_else(|| {
|
||||
let snapshot = &snapshot.buffer_snapshot;
|
||||
match snapshot.syntax_ancestor(anchor..anchor)?.1 {
|
||||
MultiOrSingleBufferOffsetRange::Multi(range) => Some(
|
||||
snapshot.anchor_before(range.start)
|
||||
..snapshot.anchor_after(range.end),
|
||||
),
|
||||
MultiOrSingleBufferOffsetRange::Single(_) => None,
|
||||
}
|
||||
let range = snapshot.syntax_ancestor(anchor..anchor)?.1;
|
||||
Some(snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end))
|
||||
})
|
||||
.unwrap_or_else(|| anchor..anchor);
|
||||
|
||||
|
||||
@@ -396,7 +396,6 @@ impl MacTextSystemState {
|
||||
let subpixel_shift = params
|
||||
.subpixel_variant
|
||||
.map(|v| v as f32 / SUBPIXEL_VARIANTS_X as f32);
|
||||
cx.set_allows_font_smoothing(true);
|
||||
cx.set_text_drawing_mode(CGTextDrawingMode::CGTextFill);
|
||||
cx.set_gray_fill_color(0.0, 1.0);
|
||||
cx.set_allows_antialiasing(true);
|
||||
|
||||
@@ -7,7 +7,7 @@ use ec4rs::{
|
||||
property::{FinalNewline, IndentSize, IndentStyle, MaxLineLen, TabWidth, TrimTrailingWs},
|
||||
};
|
||||
use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
|
||||
use gpui::{App, Modifiers};
|
||||
use gpui::{App, Modifiers, SharedString};
|
||||
use itertools::{Either, Itertools};
|
||||
|
||||
pub use settings::{
|
||||
@@ -59,6 +59,12 @@ pub struct AllLanguageSettings {
|
||||
pub(crate) file_types: FxHashMap<Arc<str>, GlobSet>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WhitespaceMap {
|
||||
pub space: SharedString,
|
||||
pub tab: SharedString,
|
||||
}
|
||||
|
||||
/// The settings for a particular language.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LanguageSettings {
|
||||
@@ -118,7 +124,7 @@ pub struct LanguageSettings {
|
||||
/// Whether to show tabs and spaces in the editor.
|
||||
pub show_whitespaces: settings::ShowWhitespaceSetting,
|
||||
/// Visible characters used to render whitespace when show_whitespaces is enabled.
|
||||
pub whitespace_map: settings::WhitespaceMap,
|
||||
pub whitespace_map: WhitespaceMap,
|
||||
/// Whether to start a new line with a comment when a previous line is a comment as well.
|
||||
pub extend_comment_on_newline: bool,
|
||||
/// Inlay hint related settings.
|
||||
@@ -503,6 +509,8 @@ impl settings::Settings for AllLanguageSettings {
|
||||
let prettier = settings.prettier.unwrap();
|
||||
let indent_guides = settings.indent_guides.unwrap();
|
||||
let tasks = settings.tasks.unwrap();
|
||||
let whitespace_map = settings.whitespace_map.unwrap();
|
||||
|
||||
LanguageSettings {
|
||||
tab_size: settings.tab_size.unwrap(),
|
||||
hard_tabs: settings.hard_tabs.unwrap(),
|
||||
@@ -536,7 +544,10 @@ impl settings::Settings for AllLanguageSettings {
|
||||
show_edit_predictions: settings.show_edit_predictions.unwrap(),
|
||||
edit_predictions_disabled_in: settings.edit_predictions_disabled_in.unwrap(),
|
||||
show_whitespaces: settings.show_whitespaces.unwrap(),
|
||||
whitespace_map: settings.whitespace_map.unwrap(),
|
||||
whitespace_map: WhitespaceMap {
|
||||
space: SharedString::new(whitespace_map.space.unwrap().to_string()),
|
||||
tab: SharedString::new(whitespace_map.tab.unwrap().to_string()),
|
||||
},
|
||||
extend_comment_on_newline: settings.extend_comment_on_newline.unwrap(),
|
||||
inlay_hints: InlayHintSettings {
|
||||
enabled: inlay_hints.enabled.unwrap(),
|
||||
|
||||
@@ -151,8 +151,8 @@ impl LanguageModelProvider for AnthropicLanguageModelProvider {
|
||||
|
||||
fn recommended_models(&self, _cx: &App) -> Vec<Arc<dyn LanguageModel>> {
|
||||
[
|
||||
anthropic::Model::ClaudeSonnet4,
|
||||
anthropic::Model::ClaudeSonnet4Thinking,
|
||||
anthropic::Model::ClaudeSonnet4_5,
|
||||
anthropic::Model::ClaudeSonnet4_5Thinking,
|
||||
]
|
||||
.into_iter()
|
||||
.map(|model| self.create_language_model(model))
|
||||
|
||||
@@ -116,26 +116,4 @@
|
||||
)
|
||||
) @item
|
||||
|
||||
; Arrow functions in variable declarations (anywhere in the tree, including nested in functions)
|
||||
(lexical_declaration
|
||||
["let" "const"] @context
|
||||
(variable_declarator
|
||||
name: (_) @name
|
||||
value: (arrow_function)) @item)
|
||||
|
||||
; Async arrow functions in variable declarations
|
||||
(lexical_declaration
|
||||
["let" "const"] @context
|
||||
(variable_declarator
|
||||
name: (_) @name
|
||||
value: (arrow_function
|
||||
"async" @context)) @item)
|
||||
|
||||
; Named function expressions in variable declarations
|
||||
(lexical_declaration
|
||||
["let" "const"] @context
|
||||
(variable_declarator
|
||||
name: (_) @name
|
||||
value: (function_expression)) @item)
|
||||
|
||||
(comment) @annotation
|
||||
|
||||
@@ -106,13 +106,13 @@ impl TyLspAdapter {
|
||||
|
||||
#[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";
|
||||
}
|
||||
|
||||
|
||||
@@ -124,26 +124,4 @@
|
||||
)
|
||||
) @item
|
||||
|
||||
; Arrow functions in variable declarations (anywhere in the tree, including nested in functions)
|
||||
(lexical_declaration
|
||||
["let" "const"] @context
|
||||
(variable_declarator
|
||||
name: (_) @name
|
||||
value: (arrow_function)) @item)
|
||||
|
||||
; Async arrow functions in variable declarations
|
||||
(lexical_declaration
|
||||
["let" "const"] @context
|
||||
(variable_declarator
|
||||
name: (_) @name
|
||||
value: (arrow_function
|
||||
"async" @context)) @item)
|
||||
|
||||
; Named function expressions in variable declarations
|
||||
(lexical_declaration
|
||||
["let" "const"] @context
|
||||
(variable_declarator
|
||||
name: (_) @name
|
||||
value: (function_expression)) @item)
|
||||
|
||||
(comment) @annotation
|
||||
|
||||
@@ -124,26 +124,4 @@
|
||||
)
|
||||
) @item
|
||||
|
||||
; Arrow functions in variable declarations (anywhere in the tree, including nested in functions)
|
||||
(lexical_declaration
|
||||
["let" "const"] @context
|
||||
(variable_declarator
|
||||
name: (_) @name
|
||||
value: (arrow_function)) @item)
|
||||
|
||||
; Async arrow functions in variable declarations
|
||||
(lexical_declaration
|
||||
["let" "const"] @context
|
||||
(variable_declarator
|
||||
name: (_) @name
|
||||
value: (arrow_function
|
||||
"async" @context)) @item)
|
||||
|
||||
; Named function expressions in variable declarations
|
||||
(lexical_declaration
|
||||
["let" "const"] @context
|
||||
(variable_declarator
|
||||
name: (_) @name
|
||||
value: (function_expression)) @item)
|
||||
|
||||
(comment) @annotation
|
||||
|
||||
@@ -80,12 +80,6 @@ pub struct MultiBuffer {
|
||||
buffer_changed_since_sync: Rc<Cell<bool>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum MultiOrSingleBufferOffsetRange {
|
||||
Single(Range<usize>),
|
||||
Multi(Range<usize>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Event {
|
||||
ExcerptsAdded {
|
||||
@@ -6078,19 +6072,17 @@ impl MultiBufferSnapshot {
|
||||
pub fn syntax_ancestor<T: ToOffset>(
|
||||
&self,
|
||||
range: Range<T>,
|
||||
) -> Option<(tree_sitter::Node<'_>, MultiOrSingleBufferOffsetRange)> {
|
||||
) -> Option<(tree_sitter::Node<'_>, Range<usize>)> {
|
||||
let range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||
let mut excerpt = self.excerpt_containing(range.clone())?;
|
||||
let node = excerpt
|
||||
.buffer()
|
||||
.syntax_ancestor(excerpt.map_range_to_buffer(range))?;
|
||||
let node_range = node.byte_range();
|
||||
let range = if excerpt.contains_buffer_range(node_range.clone()) {
|
||||
MultiOrSingleBufferOffsetRange::Multi(excerpt.map_range_from_buffer(node_range))
|
||||
} else {
|
||||
MultiOrSingleBufferOffsetRange::Single(node_range)
|
||||
if !excerpt.contains_buffer_range(node_range.clone()) {
|
||||
return None;
|
||||
};
|
||||
Some((node, range))
|
||||
Some((node, excerpt.map_range_from_buffer(node_range)))
|
||||
}
|
||||
|
||||
pub fn syntax_next_sibling<T: ToOffset>(
|
||||
|
||||
@@ -612,7 +612,7 @@ fn get_or_npm_install_builtin_agent(
|
||||
if let Ok(latest_version) = latest_version
|
||||
&& &latest_version != &file_name.to_string_lossy()
|
||||
{
|
||||
download_latest_version(
|
||||
let download_result = download_latest_version(
|
||||
fs,
|
||||
dir.clone(),
|
||||
node_runtime,
|
||||
@@ -620,7 +620,9 @@ fn get_or_npm_install_builtin_agent(
|
||||
)
|
||||
.await
|
||||
.log_err();
|
||||
if let Some(mut new_version_available) = new_version_available {
|
||||
if let Some(mut new_version_available) = new_version_available
|
||||
&& download_result.is_some()
|
||||
{
|
||||
new_version_available.send(Some(latest_version)).ok();
|
||||
}
|
||||
}
|
||||
@@ -705,7 +707,7 @@ async fn download_latest_version(
|
||||
&dir.join(&version),
|
||||
RenameOptions {
|
||||
ignore_if_exists: true,
|
||||
overwrite: false,
|
||||
overwrite: true,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -25,7 +25,7 @@ impl DapLocator for PythonLocator {
|
||||
if adapter.0.as_ref() != "Debugpy" {
|
||||
return None;
|
||||
}
|
||||
let valid_program = build_config.command.starts_with("\"$ZED_")
|
||||
let valid_program = build_config.command.starts_with("$ZED_")
|
||||
|| Path::new(&build_config.command)
|
||||
.file_name()
|
||||
.is_some_and(|name| name.to_str().is_some_and(|path| path.starts_with("python")));
|
||||
@@ -94,3 +94,53 @@ impl DapLocator for PythonLocator {
|
||||
bail!("Python locator should not require DapLocator::run to be ran");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_python_locator() {
|
||||
let adapter = DebugAdapterName("Debugpy".into());
|
||||
let build_task = TaskTemplate {
|
||||
label: "run module '$ZED_FILE'".into(),
|
||||
command: "$ZED_CUSTOM_PYTHON_ACTIVE_ZED_TOOLCHAIN".into(),
|
||||
args: vec!["-m".into(), "$ZED_CUSTOM_PYTHON_MODULE_NAME".into()],
|
||||
env: Default::default(),
|
||||
cwd: Some("$ZED_WORKTREE_ROOT".into()),
|
||||
use_new_terminal: false,
|
||||
allow_concurrent_runs: false,
|
||||
reveal: task::RevealStrategy::Always,
|
||||
reveal_target: task::RevealTarget::Dock,
|
||||
hide: task::HideStrategy::Never,
|
||||
tags: vec!["python-module-main-method".into()],
|
||||
shell: task::Shell::System,
|
||||
show_summary: false,
|
||||
show_command: false,
|
||||
};
|
||||
|
||||
let expected_scenario = DebugScenario {
|
||||
adapter: "Debugpy".into(),
|
||||
label: "run module 'main.py'".into(),
|
||||
build: None,
|
||||
config: json!({
|
||||
"request": "launch",
|
||||
"python": "$ZED_CUSTOM_PYTHON_ACTIVE_ZED_TOOLCHAIN",
|
||||
"args": [],
|
||||
"cwd": "$ZED_WORKTREE_ROOT",
|
||||
"module": "$ZED_CUSTOM_PYTHON_MODULE_NAME",
|
||||
}),
|
||||
tcp_connection: None,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
PythonLocator
|
||||
.create_scenario(&build_task, "run module 'main.py'", &adapter)
|
||||
.await
|
||||
.expect("Failed to create a scenario"),
|
||||
expected_scenario
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,12 +211,6 @@ impl Project {
|
||||
let activation_script = activation_script.join("; ");
|
||||
let to_run = format_to_run();
|
||||
|
||||
// todo(lw): Alacritty uses `CreateProcessW` on windows with the entire command and arg sequence merged into a single string,
|
||||
// without quoting the arguments
|
||||
#[cfg(windows)]
|
||||
let arg =
|
||||
quote_arg(&format!("{activation_script}; {to_run}"), true);
|
||||
#[cfg(not(windows))]
|
||||
let arg = format!("{activation_script}; {to_run}");
|
||||
|
||||
(
|
||||
@@ -515,37 +509,6 @@ impl Project {
|
||||
}
|
||||
}
|
||||
|
||||
/// We're not using shlex for windows as it is overly eager with escaping some of the special characters (^) we need for nu. Hence, we took
|
||||
/// that quote impl straight from Rust stdlib (Command API).
|
||||
#[cfg(windows)]
|
||||
fn quote_arg(argument: &str, quote: bool) -> String {
|
||||
let mut arg = String::new();
|
||||
if quote {
|
||||
arg.push('"');
|
||||
}
|
||||
|
||||
let mut backslashes: usize = 0;
|
||||
for x in argument.chars() {
|
||||
if x == '\\' {
|
||||
backslashes += 1;
|
||||
} else {
|
||||
if x == '"' {
|
||||
// Add n+1 backslashes to total 2n+1 before internal '"'.
|
||||
arg.extend((0..=backslashes).map(|_| '\\'));
|
||||
}
|
||||
backslashes = 0;
|
||||
}
|
||||
arg.push(x);
|
||||
}
|
||||
|
||||
if quote {
|
||||
// Add n backslashes to total 2n before ending '"'.
|
||||
arg.extend((0..backslashes).map(|_| '\\'));
|
||||
arg.push('"');
|
||||
}
|
||||
arg
|
||||
}
|
||||
|
||||
fn create_remote_shell(
|
||||
spawn_command: Option<(&String, &Vec<String>)>,
|
||||
mut env: HashMap<String, String>,
|
||||
|
||||
@@ -49,6 +49,7 @@ merge_from_overwrites!(
|
||||
bool,
|
||||
f64,
|
||||
f32,
|
||||
char,
|
||||
std::num::NonZeroUsize,
|
||||
std::num::NonZeroU32,
|
||||
String,
|
||||
|
||||
@@ -261,7 +261,7 @@ pub struct LanguageSettingsContent {
|
||||
/// Visible characters used to render whitespace when show_whitespaces is enabled.
|
||||
///
|
||||
/// Default: "•" for spaces, "→" for tabs.
|
||||
pub whitespace_map: Option<WhitespaceMap>,
|
||||
pub whitespace_map: Option<WhitespaceMapContent>,
|
||||
/// Whether to start a new line with a comment when a previous line is a comment as well.
|
||||
///
|
||||
/// Default: true
|
||||
@@ -354,23 +354,9 @@ pub enum ShowWhitespaceSetting {
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
|
||||
pub struct WhitespaceMap {
|
||||
pub space: Option<String>,
|
||||
pub tab: Option<String>,
|
||||
}
|
||||
|
||||
impl WhitespaceMap {
|
||||
pub fn space(&self) -> SharedString {
|
||||
self.space
|
||||
.as_ref()
|
||||
.map_or_else(|| SharedString::from("•"), |s| SharedString::from(s))
|
||||
}
|
||||
|
||||
pub fn tab(&self) -> SharedString {
|
||||
self.tab
|
||||
.as_ref()
|
||||
.map_or_else(|| SharedString::from("→"), |s| SharedString::from(s))
|
||||
}
|
||||
pub struct WhitespaceMapContent {
|
||||
pub space: Option<char>,
|
||||
pub tab: Option<char>,
|
||||
}
|
||||
|
||||
/// The behavior of `editor::Rewrap`.
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -283,10 +284,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");
|
||||
@@ -333,4 +336,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"]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
description = "The fast, collaborative code editor."
|
||||
edition.workspace = true
|
||||
name = "zed"
|
||||
version = "0.206.0"
|
||||
version = "0.206.6"
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
authors = ["Zed Team <hi@zed.dev>"]
|
||||
|
||||
@@ -1 +1 @@
|
||||
dev
|
||||
preview
|
||||
Reference in New Issue
Block a user