diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f67a447cbab9f0536fd86474632b2fb24bde65a9..8dcb1ebb9e93953806a2c595c71666d48e6019cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -538,7 +538,7 @@ jobs: ref: ${{ github.ref }} - name: Limit target directory size - run: script/clear-target-dir-if-larger-than 100 + run: script/clear-target-dir-if-larger-than 300 - name: Determine version and release channel if: ${{ startsWith(github.ref, 'refs/tags/v') }} diff --git a/.github/workflows/deploy_collab.yml b/.github/workflows/deploy_collab.yml index ff2a3589e4c5482089536919618f1bbff982c63c..c61879faa8cd0a5dbdbed03a140f8e558f13322b 100644 --- a/.github/workflows/deploy_collab.yml +++ b/.github/workflows/deploy_collab.yml @@ -49,7 +49,7 @@ jobs: - name: Limit target directory size shell: bash -euxo pipefail {0} - run: script/clear-target-dir-if-larger-than 100 + run: script/clear-target-dir-if-larger-than 300 - name: Run tests shell: bash -euxo pipefail {0} diff --git a/crates/acp_thread/src/acp_thread.rs b/crates/acp_thread/src/acp_thread.rs index 99c62201fa0c2576e588c5cc7325d525c2d03503..5ecf2be445ecf8afc6a93e2961302758ea0037ae 100644 --- a/crates/acp_thread/src/acp_thread.rs +++ b/crates/acp_thread/src/acp_thread.rs @@ -35,7 +35,7 @@ use std::rc::Rc; use std::time::{Duration, Instant}; use std::{fmt::Display, mem, path::PathBuf, sync::Arc}; use ui::App; -use util::{ResultExt, get_default_system_shell_preferring_bash}; +use util::{ResultExt, get_default_system_shell_preferring_bash, paths::PathStyle}; use uuid::Uuid; #[derive(Debug)] @@ -95,9 +95,14 @@ pub enum AssistantMessageChunk { } impl AssistantMessageChunk { - pub fn from_str(chunk: &str, language_registry: &Arc, cx: &mut App) -> Self { + pub fn from_str( + chunk: &str, + language_registry: &Arc, + path_style: PathStyle, + cx: &mut App, + ) -> Self { Self::Message { - block: ContentBlock::new(chunk.into(), language_registry, cx), + block: ContentBlock::new(chunk.into(), language_registry, path_style, cx), } } @@ -186,6 +191,7 @@ impl ToolCall { tool_call: acp::ToolCall, status: ToolCallStatus, language_registry: Arc, + path_style: PathStyle, terminals: &HashMap>, cx: &mut App, ) -> Result { @@ -199,6 +205,7 @@ impl ToolCall { content.push(ToolCallContent::from_acp( item, language_registry.clone(), + path_style, terminals, cx, )?); @@ -223,6 +230,7 @@ impl ToolCall { &mut self, fields: acp::ToolCallUpdateFields, language_registry: Arc, + path_style: PathStyle, terminals: &HashMap>, cx: &mut App, ) -> Result<()> { @@ -260,12 +268,13 @@ impl ToolCall { // Reuse existing content if we can for (old, new) in self.content.iter_mut().zip(content.by_ref()) { - old.update_from_acp(new, language_registry.clone(), terminals, cx)?; + old.update_from_acp(new, language_registry.clone(), path_style, terminals, cx)?; } for new in content { self.content.push(ToolCallContent::from_acp( new, language_registry.clone(), + path_style, terminals, cx, )?) @@ -450,21 +459,23 @@ impl ContentBlock { pub fn new( block: acp::ContentBlock, language_registry: &Arc, + path_style: PathStyle, cx: &mut App, ) -> Self { let mut this = Self::Empty; - this.append(block, language_registry, cx); + this.append(block, language_registry, path_style, cx); this } pub fn new_combined( blocks: impl IntoIterator, language_registry: Arc, + path_style: PathStyle, cx: &mut App, ) -> Self { let mut this = Self::Empty; for block in blocks { - this.append(block, &language_registry, cx); + this.append(block, &language_registry, path_style, cx); } this } @@ -473,6 +484,7 @@ impl ContentBlock { &mut self, block: acp::ContentBlock, language_registry: &Arc, + path_style: PathStyle, cx: &mut App, ) { if matches!(self, ContentBlock::Empty) @@ -482,7 +494,7 @@ impl ContentBlock { return; } - let new_content = self.block_string_contents(block); + let new_content = self.block_string_contents(block, path_style); match self { ContentBlock::Empty => { @@ -492,7 +504,7 @@ impl ContentBlock { markdown.update(cx, |markdown, cx| markdown.append(&new_content, cx)); } ContentBlock::ResourceLink { resource_link } => { - let existing_content = Self::resource_link_md(&resource_link.uri); + let existing_content = Self::resource_link_md(&resource_link.uri, path_style); let combined = format!("{}\n{}", existing_content, new_content); *self = Self::create_markdown_block(combined, language_registry, cx); @@ -511,11 +523,11 @@ impl ContentBlock { } } - fn block_string_contents(&self, block: acp::ContentBlock) -> String { + fn block_string_contents(&self, block: acp::ContentBlock, path_style: PathStyle) -> String { match block { acp::ContentBlock::Text(text_content) => text_content.text, acp::ContentBlock::ResourceLink(resource_link) => { - Self::resource_link_md(&resource_link.uri) + Self::resource_link_md(&resource_link.uri, path_style) } acp::ContentBlock::Resource(acp::EmbeddedResource { resource: @@ -524,14 +536,14 @@ impl ContentBlock { .. }), .. - }) => Self::resource_link_md(&uri), + }) => Self::resource_link_md(&uri, path_style), acp::ContentBlock::Image(image) => Self::image_md(&image), acp::ContentBlock::Audio(_) | acp::ContentBlock::Resource(_) => String::new(), } } - fn resource_link_md(uri: &str) -> String { - if let Some(uri) = MentionUri::parse(uri).log_err() { + fn resource_link_md(uri: &str, path_style: PathStyle) -> String { + if let Some(uri) = MentionUri::parse(uri, path_style).log_err() { uri.as_link().to_string() } else { uri.to_string() @@ -577,6 +589,7 @@ impl ToolCallContent { pub fn from_acp( content: acp::ToolCallContent, language_registry: Arc, + path_style: PathStyle, terminals: &HashMap>, cx: &mut App, ) -> Result { @@ -584,6 +597,7 @@ impl ToolCallContent { acp::ToolCallContent::Content { content } => Ok(Self::ContentBlock(ContentBlock::new( content, &language_registry, + path_style, cx, ))), acp::ToolCallContent::Diff { diff } => Ok(Self::Diff(cx.new(|cx| { @@ -607,6 +621,7 @@ impl ToolCallContent { &mut self, new: acp::ToolCallContent, language_registry: Arc, + path_style: PathStyle, terminals: &HashMap>, cx: &mut App, ) -> Result<()> { @@ -622,7 +637,7 @@ impl ToolCallContent { }; if needs_update { - *self = Self::from_acp(new, language_registry, terminals, cx)?; + *self = Self::from_acp(new, language_registry, path_style, terminals, cx)?; } Ok(()) } @@ -1142,6 +1157,7 @@ impl AcpThread { cx: &mut Context, ) { let language_registry = self.project.read(cx).languages().clone(); + let path_style = self.project.read(cx).path_style(cx); let entries_len = self.entries.len(); if let Some(last_entry) = self.entries.last_mut() @@ -1153,12 +1169,12 @@ impl AcpThread { }) = last_entry { *id = message_id.or(id.take()); - content.append(chunk.clone(), &language_registry, cx); + content.append(chunk.clone(), &language_registry, path_style, cx); chunks.push(chunk); let idx = entries_len - 1; cx.emit(AcpThreadEvent::EntryUpdated(idx)); } else { - let content = ContentBlock::new(chunk.clone(), &language_registry, cx); + let content = ContentBlock::new(chunk.clone(), &language_registry, path_style, cx); self.push_entry( AgentThreadEntry::UserMessage(UserMessage { id: message_id, @@ -1178,6 +1194,7 @@ impl AcpThread { cx: &mut Context, ) { let language_registry = self.project.read(cx).languages().clone(); + let path_style = self.project.read(cx).path_style(cx); let entries_len = self.entries.len(); if let Some(last_entry) = self.entries.last_mut() && let AgentThreadEntry::AssistantMessage(AssistantMessage { chunks }) = last_entry @@ -1187,10 +1204,10 @@ impl AcpThread { match (chunks.last_mut(), is_thought) { (Some(AssistantMessageChunk::Message { block }), false) | (Some(AssistantMessageChunk::Thought { block }), true) => { - block.append(chunk, &language_registry, cx) + block.append(chunk, &language_registry, path_style, cx) } _ => { - let block = ContentBlock::new(chunk, &language_registry, cx); + let block = ContentBlock::new(chunk, &language_registry, path_style, cx); if is_thought { chunks.push(AssistantMessageChunk::Thought { block }) } else { @@ -1199,7 +1216,7 @@ impl AcpThread { } } } else { - let block = ContentBlock::new(chunk, &language_registry, cx); + let block = ContentBlock::new(chunk, &language_registry, path_style, cx); let chunk = if is_thought { AssistantMessageChunk::Thought { block } } else { @@ -1251,6 +1268,7 @@ impl AcpThread { ) -> Result<()> { let update = update.into(); let languages = self.project.read(cx).languages().clone(); + let path_style = self.project.read(cx).path_style(cx); let ix = match self.index_for_tool_call(update.id()) { Some(ix) => ix, @@ -1267,6 +1285,7 @@ impl AcpThread { meta: None, }), &languages, + path_style, cx, ))], status: ToolCallStatus::Failed, @@ -1286,7 +1305,7 @@ impl AcpThread { match update { ToolCallUpdate::UpdateFields(update) => { let location_updated = update.fields.locations.is_some(); - call.update_fields(update.fields, languages, &self.terminals, cx)?; + call.update_fields(update.fields, languages, path_style, &self.terminals, cx)?; if location_updated { self.resolve_locations(update.id, cx); } @@ -1325,6 +1344,7 @@ impl AcpThread { cx: &mut Context, ) -> Result<(), acp::Error> { let language_registry = self.project.read(cx).languages().clone(); + let path_style = self.project.read(cx).path_style(cx); let id = update.id.clone(); if let Some(ix) = self.index_for_tool_call(&id) { @@ -1332,7 +1352,13 @@ impl AcpThread { unreachable!() }; - call.update_fields(update.fields, language_registry, &self.terminals, cx)?; + call.update_fields( + update.fields, + language_registry, + path_style, + &self.terminals, + cx, + )?; call.status = status; cx.emit(AcpThreadEvent::EntryUpdated(ix)); @@ -1341,6 +1367,7 @@ impl AcpThread { update.try_into()?, status, language_registry, + self.project.read(cx).path_style(cx), &self.terminals, cx, )?; @@ -1620,6 +1647,7 @@ impl AcpThread { let block = ContentBlock::new_combined( message.clone(), self.project.read(cx).languages().clone(), + self.project.read(cx).path_style(cx), cx, ); let request = acp::PromptRequest { diff --git a/crates/acp_thread/src/mention.rs b/crates/acp_thread/src/mention.rs index bbd13da5fa4124546d5457755f2bd2f5d737ccac..b78eac4903a259a1044892fb2c8233f7e973f025 100644 --- a/crates/acp_thread/src/mention.rs +++ b/crates/acp_thread/src/mention.rs @@ -7,10 +7,10 @@ use std::{ fmt, ops::RangeInclusive, path::{Path, PathBuf}, - str::FromStr, }; use ui::{App, IconName, SharedString}; use url::Url; +use util::paths::PathStyle; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] pub enum MentionUri { @@ -49,7 +49,7 @@ pub enum MentionUri { } impl MentionUri { - pub fn parse(input: &str) -> Result { + pub fn parse(input: &str, path_style: PathStyle) -> Result { fn parse_line_range(fragment: &str) -> Result> { let range = fragment .strip_prefix("L") @@ -74,25 +74,34 @@ impl MentionUri { let path = url.path(); match url.scheme() { "file" => { - let path = url.to_file_path().ok().context("Extracting file path")?; + let path = if path_style.is_windows() { + path.trim_start_matches("/") + } else { + path + }; + if let Some(fragment) = url.fragment() { let line_range = parse_line_range(fragment)?; if let Some(name) = single_query_param(&url, "symbol")? { Ok(Self::Symbol { name, - abs_path: path, + abs_path: path.into(), line_range, }) } else { Ok(Self::Selection { - abs_path: Some(path), + abs_path: Some(path.into()), line_range, }) } } else if input.ends_with("/") { - Ok(Self::Directory { abs_path: path }) + Ok(Self::Directory { + abs_path: path.into(), + }) } else { - Ok(Self::File { abs_path: path }) + Ok(Self::File { + abs_path: path.into(), + }) } } "zed" => { @@ -213,18 +222,14 @@ impl MentionUri { pub fn to_uri(&self) -> Url { match self { MentionUri::File { abs_path } => { - let mut url = Url::parse("zed:///").unwrap(); - url.set_path("/agent/file"); - url.query_pairs_mut() - .append_pair("path", &abs_path.to_string_lossy()); + let mut url = Url::parse("file:///").unwrap(); + url.set_path(&abs_path.to_string_lossy()); url } MentionUri::PastedImage => Url::parse("zed:///agent/pasted-image").unwrap(), MentionUri::Directory { abs_path } => { - let mut url = Url::parse("zed:///").unwrap(); - url.set_path("/agent/directory"); - url.query_pairs_mut() - .append_pair("path", &abs_path.to_string_lossy()); + let mut url = Url::parse("file:///").unwrap(); + url.set_path(&abs_path.to_string_lossy()); url } MentionUri::Symbol { @@ -232,10 +237,9 @@ impl MentionUri { name, line_range, } => { - let mut url = Url::parse("zed:///").unwrap(); - url.set_path(&format!("/agent/symbol/{name}")); - url.query_pairs_mut() - .append_pair("path", &abs_path.to_string_lossy()); + let mut url = Url::parse("file:///").unwrap(); + url.set_path(&abs_path.to_string_lossy()); + url.query_pairs_mut().append_pair("symbol", name); url.set_fragment(Some(&format!( "L{}:{}", line_range.start() + 1, @@ -247,13 +251,14 @@ impl MentionUri { abs_path, line_range, } => { - let mut url = Url::parse("zed:///").unwrap(); - if let Some(abs_path) = abs_path { - url.set_path("/agent/selection"); - url.query_pairs_mut() - .append_pair("path", &abs_path.to_string_lossy()); + let mut url = if let Some(path) = abs_path { + let mut url = Url::parse("file:///").unwrap(); + url.set_path(&path.to_string_lossy()); + url } else { + let mut url = Url::parse("zed:///").unwrap(); url.set_path("/agent/untitled-buffer"); + url }; url.set_fragment(Some(&format!( "L{}:{}", @@ -288,14 +293,6 @@ impl MentionUri { } } -impl FromStr for MentionUri { - type Err = anyhow::Error; - - fn from_str(s: &str) -> anyhow::Result { - Self::parse(s) - } -} - pub struct MentionLink<'a>(&'a MentionUri); impl fmt::Display for MentionLink<'_> { @@ -338,93 +335,81 @@ mod tests { #[test] fn test_parse_file_uri() { - let old_uri = uri!("file:///path/to/file.rs"); - let parsed = MentionUri::parse(old_uri).unwrap(); + let file_uri = uri!("file:///path/to/file.rs"); + let parsed = MentionUri::parse(file_uri, PathStyle::local()).unwrap(); match &parsed { MentionUri::File { abs_path } => { - assert_eq!(abs_path.to_str().unwrap(), path!("/path/to/file.rs")); + assert_eq!(abs_path, Path::new(path!("/path/to/file.rs"))); } _ => panic!("Expected File variant"), } - let new_uri = parsed.to_uri().to_string(); - assert!(new_uri.starts_with("zed:///agent/file")); - assert_eq!(MentionUri::parse(&new_uri).unwrap(), parsed); + assert_eq!(parsed.to_uri().to_string(), file_uri); } #[test] fn test_parse_directory_uri() { - let old_uri = uri!("file:///path/to/dir/"); - let parsed = MentionUri::parse(old_uri).unwrap(); + let file_uri = uri!("file:///path/to/dir/"); + let parsed = MentionUri::parse(file_uri, PathStyle::local()).unwrap(); match &parsed { MentionUri::Directory { abs_path } => { - assert_eq!(abs_path.to_str().unwrap(), path!("/path/to/dir/")); + assert_eq!(abs_path, Path::new(path!("/path/to/dir/"))); } _ => panic!("Expected Directory variant"), } - let new_uri = parsed.to_uri().to_string(); - assert!(new_uri.starts_with("zed:///agent/directory")); - assert_eq!(MentionUri::parse(&new_uri).unwrap(), parsed); + assert_eq!(parsed.to_uri().to_string(), file_uri); } #[test] fn test_to_directory_uri_without_slash() { let uri = MentionUri::Directory { - abs_path: PathBuf::from(path!("/path/to/dir")), + abs_path: PathBuf::from(path!("/path/to/dir/")), }; - let uri_string = uri.to_uri().to_string(); - assert!(uri_string.starts_with("zed:///agent/directory")); - assert_eq!(MentionUri::parse(&uri_string).unwrap(), uri); + let expected = uri!("file:///path/to/dir/"); + assert_eq!(uri.to_uri().to_string(), expected); } #[test] fn test_parse_symbol_uri() { - let old_uri = uri!("file:///path/to/file.rs?symbol=MySymbol#L10:20"); - let parsed = MentionUri::parse(old_uri).unwrap(); + let symbol_uri = uri!("file:///path/to/file.rs?symbol=MySymbol#L10:20"); + let parsed = MentionUri::parse(symbol_uri, PathStyle::local()).unwrap(); match &parsed { MentionUri::Symbol { abs_path: path, name, line_range, } => { - assert_eq!(path.to_str().unwrap(), path!("/path/to/file.rs")); + assert_eq!(path, Path::new(path!("/path/to/file.rs"))); assert_eq!(name, "MySymbol"); assert_eq!(line_range.start(), &9); assert_eq!(line_range.end(), &19); } _ => panic!("Expected Symbol variant"), } - let new_uri = parsed.to_uri().to_string(); - assert!(new_uri.starts_with("zed:///agent/symbol/MySymbol")); - assert_eq!(MentionUri::parse(&new_uri).unwrap(), parsed); + assert_eq!(parsed.to_uri().to_string(), symbol_uri); } #[test] fn test_parse_selection_uri() { - let old_uri = uri!("file:///path/to/file.rs#L5:15"); - let parsed = MentionUri::parse(old_uri).unwrap(); + let selection_uri = uri!("file:///path/to/file.rs#L5:15"); + let parsed = MentionUri::parse(selection_uri, PathStyle::local()).unwrap(); match &parsed { MentionUri::Selection { abs_path: path, line_range, } => { - assert_eq!( - path.as_ref().unwrap().to_str().unwrap(), - path!("/path/to/file.rs") - ); + assert_eq!(path.as_ref().unwrap(), Path::new(path!("/path/to/file.rs"))); assert_eq!(line_range.start(), &4); assert_eq!(line_range.end(), &14); } _ => panic!("Expected Selection variant"), } - let new_uri = parsed.to_uri().to_string(); - assert!(new_uri.starts_with("zed:///agent/selection")); - assert_eq!(MentionUri::parse(&new_uri).unwrap(), parsed); + assert_eq!(parsed.to_uri().to_string(), selection_uri); } #[test] fn test_parse_untitled_selection_uri() { let selection_uri = uri!("zed:///agent/untitled-buffer#L1:10"); - let parsed = MentionUri::parse(selection_uri).unwrap(); + let parsed = MentionUri::parse(selection_uri, PathStyle::local()).unwrap(); match &parsed { MentionUri::Selection { abs_path: None, @@ -441,7 +426,7 @@ mod tests { #[test] fn test_parse_thread_uri() { let thread_uri = "zed:///agent/thread/session123?name=Thread+name"; - let parsed = MentionUri::parse(thread_uri).unwrap(); + let parsed = MentionUri::parse(thread_uri, PathStyle::local()).unwrap(); match &parsed { MentionUri::Thread { id: thread_id, @@ -458,7 +443,7 @@ mod tests { #[test] fn test_parse_rule_uri() { let rule_uri = "zed:///agent/rule/d8694ff2-90d5-4b6f-be33-33c1763acd52?name=Some+rule"; - let parsed = MentionUri::parse(rule_uri).unwrap(); + let parsed = MentionUri::parse(rule_uri, PathStyle::local()).unwrap(); match &parsed { MentionUri::Rule { id, name } => { assert_eq!(id.to_string(), "d8694ff2-90d5-4b6f-be33-33c1763acd52"); @@ -472,7 +457,7 @@ mod tests { #[test] fn test_parse_fetch_http_uri() { let http_uri = "http://example.com/path?query=value#fragment"; - let parsed = MentionUri::parse(http_uri).unwrap(); + let parsed = MentionUri::parse(http_uri, PathStyle::local()).unwrap(); match &parsed { MentionUri::Fetch { url } => { assert_eq!(url.to_string(), http_uri); @@ -485,7 +470,7 @@ mod tests { #[test] fn test_parse_fetch_https_uri() { let https_uri = "https://example.com/api/endpoint"; - let parsed = MentionUri::parse(https_uri).unwrap(); + let parsed = MentionUri::parse(https_uri, PathStyle::local()).unwrap(); match &parsed { MentionUri::Fetch { url } => { assert_eq!(url.to_string(), https_uri); @@ -497,40 +482,55 @@ mod tests { #[test] fn test_invalid_scheme() { - assert!(MentionUri::parse("ftp://example.com").is_err()); - assert!(MentionUri::parse("ssh://example.com").is_err()); - assert!(MentionUri::parse("unknown://example.com").is_err()); + assert!(MentionUri::parse("ftp://example.com", PathStyle::local()).is_err()); + assert!(MentionUri::parse("ssh://example.com", PathStyle::local()).is_err()); + assert!(MentionUri::parse("unknown://example.com", PathStyle::local()).is_err()); } #[test] fn test_invalid_zed_path() { - assert!(MentionUri::parse("zed:///invalid/path").is_err()); - assert!(MentionUri::parse("zed:///agent/unknown/test").is_err()); + assert!(MentionUri::parse("zed:///invalid/path", PathStyle::local()).is_err()); + assert!(MentionUri::parse("zed:///agent/unknown/test", PathStyle::local()).is_err()); } #[test] fn test_invalid_line_range_format() { // Missing L prefix - assert!(MentionUri::parse(uri!("file:///path/to/file.rs#10:20")).is_err()); + assert!( + MentionUri::parse(uri!("file:///path/to/file.rs#10:20"), PathStyle::local()).is_err() + ); // Missing colon separator - assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L1020")).is_err()); + assert!( + MentionUri::parse(uri!("file:///path/to/file.rs#L1020"), PathStyle::local()).is_err() + ); // Invalid numbers - assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L10:abc")).is_err()); - assert!(MentionUri::parse(uri!("file:///path/to/file.rs#Labc:20")).is_err()); + assert!( + MentionUri::parse(uri!("file:///path/to/file.rs#L10:abc"), PathStyle::local()).is_err() + ); + assert!( + MentionUri::parse(uri!("file:///path/to/file.rs#Labc:20"), PathStyle::local()).is_err() + ); } #[test] fn test_invalid_query_parameters() { // Invalid query parameter name - assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L10:20?invalid=test")).is_err()); + assert!( + MentionUri::parse( + uri!("file:///path/to/file.rs#L10:20?invalid=test"), + PathStyle::local() + ) + .is_err() + ); // Too many query parameters assert!( - MentionUri::parse(uri!( - "file:///path/to/file.rs#L10:20?symbol=test&another=param" - )) + MentionUri::parse( + uri!("file:///path/to/file.rs#L10:20?symbol=test&another=param"), + PathStyle::local() + ) .is_err() ); } @@ -538,8 +538,14 @@ mod tests { #[test] fn test_zero_based_line_numbers() { // Test that 0-based line numbers are rejected (should be 1-based) - assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L0:10")).is_err()); - assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L1:0")).is_err()); - assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L0:0")).is_err()); + assert!( + MentionUri::parse(uri!("file:///path/to/file.rs#L0:10"), PathStyle::local()).is_err() + ); + assert!( + MentionUri::parse(uri!("file:///path/to/file.rs#L1:0"), PathStyle::local()).is_err() + ); + assert!( + MentionUri::parse(uri!("file:///path/to/file.rs#L0:0"), PathStyle::local()).is_err() + ); } } diff --git a/crates/agent/src/agent.rs b/crates/agent/src/agent.rs index 63ee0adf191cbe309229c57b950d11ca7a3680e3..631c1122f85421e8f4f19a7a64efd82da0528162 100644 --- a/crates/agent/src/agent.rs +++ b/crates/agent/src/agent.rs @@ -1035,12 +1035,13 @@ impl acp_thread::AgentConnection for NativeAgentConnection { let session_id = params.session_id.clone(); log::info!("Received prompt request for session: {}", session_id); log::debug!("Prompt blocks count: {}", params.prompt.len()); + let path_style = self.0.read(cx).project.read(cx).path_style(cx); - self.run_turn(session_id, cx, |thread, cx| { + self.run_turn(session_id, cx, move |thread, cx| { let content: Vec = params .prompt .into_iter() - .map(Into::into) + .map(|block| UserMessageContent::from_content_block(block, path_style)) .collect::>(); log::debug!("Converted prompt to message: {} chars", content.len()); log::debug!("Message id: {:?}", id); diff --git a/crates/agent/src/thread.rs b/crates/agent/src/thread.rs index d3414d84c8f5594a567e5b38b45ddf0739965365..4016f3a5f53da95c0adca80ebfc5808addd55e09 100644 --- a/crates/agent/src/thread.rs +++ b/crates/agent/src/thread.rs @@ -50,7 +50,7 @@ use std::{ time::{Duration, Instant}, }; use std::{fmt::Write, path::PathBuf}; -use util::{ResultExt, debug_panic, markdown::MarkdownCodeBlock}; +use util::{ResultExt, debug_panic, markdown::MarkdownCodeBlock, paths::PathStyle}; use uuid::Uuid; const TOOL_CANCELED_MESSAGE: &str = "Tool canceled by user"; @@ -2538,8 +2538,8 @@ impl From<&str> for UserMessageContent { } } -impl From for UserMessageContent { - fn from(value: acp::ContentBlock) -> Self { +impl UserMessageContent { + pub fn from_content_block(value: acp::ContentBlock, path_style: PathStyle) -> Self { match value { acp::ContentBlock::Text(text_content) => Self::Text(text_content.text), acp::ContentBlock::Image(image_content) => Self::Image(convert_image(image_content)), @@ -2548,7 +2548,7 @@ impl From for UserMessageContent { Self::Text("[audio]".to_string()) } acp::ContentBlock::ResourceLink(resource_link) => { - match MentionUri::parse(&resource_link.uri) { + match MentionUri::parse(&resource_link.uri, path_style) { Ok(uri) => Self::Mention { uri, content: String::new(), @@ -2561,7 +2561,7 @@ impl From for UserMessageContent { } acp::ContentBlock::Resource(resource) => match resource.resource { acp::EmbeddedResourceResource::TextResourceContents(resource) => { - match MentionUri::parse(&resource.uri) { + match MentionUri::parse(&resource.uri, path_style) { Ok(uri) => Self::Mention { uri, content: resource.text, diff --git a/crates/agent_ui/src/acp/message_editor.rs b/crates/agent_ui/src/acp/message_editor.rs index c24cefcf2d5fc04baffeb9f3d1a1ecaf9dd05268..91e9850b082f0d8432984b49aa4cd82f9794e898 100644 --- a/crates/agent_ui/src/acp/message_editor.rs +++ b/crates/agent_ui/src/acp/message_editor.rs @@ -1062,6 +1062,7 @@ impl MessageEditor { ) { self.clear(window, cx); + let path_style = self.project.read(cx).path_style(cx); let mut text = String::new(); let mut mentions = Vec::new(); @@ -1074,7 +1075,8 @@ impl MessageEditor { resource: acp::EmbeddedResourceResource::TextResourceContents(resource), .. }) => { - let Some(mention_uri) = MentionUri::parse(&resource.uri).log_err() else { + let Some(mention_uri) = MentionUri::parse(&resource.uri, path_style).log_err() + else { continue; }; let start = text.len(); @@ -1090,7 +1092,9 @@ impl MessageEditor { )); } acp::ContentBlock::ResourceLink(resource) => { - if let Some(mention_uri) = MentionUri::parse(&resource.uri).log_err() { + if let Some(mention_uri) = + MentionUri::parse(&resource.uri, path_style).log_err() + { let start = text.len(); write!(&mut text, "{}", mention_uri.as_link()).ok(); let end = text.len(); @@ -1105,7 +1109,7 @@ impl MessageEditor { meta: _, }) => { let mention_uri = if let Some(uri) = uri { - MentionUri::parse(&uri) + MentionUri::parse(&uri, path_style) } else { Ok(MentionUri::PastedImage) }; @@ -2293,7 +2297,10 @@ mod tests { panic!("Unexpected mentions"); }; pretty_assertions::assert_eq!(content, "1"); - pretty_assertions::assert_eq!(uri, &url_one.parse::().unwrap()); + pretty_assertions::assert_eq!( + uri, + &MentionUri::parse(&url_one, PathStyle::local()).unwrap() + ); } let contents = message_editor @@ -2314,7 +2321,10 @@ mod tests { let [(uri, Mention::UriOnly)] = contents.as_slice() else { panic!("Unexpected mentions"); }; - pretty_assertions::assert_eq!(uri, &url_one.parse::().unwrap()); + pretty_assertions::assert_eq!( + uri, + &MentionUri::parse(&url_one, PathStyle::local()).unwrap() + ); } cx.simulate_input(" "); @@ -2375,7 +2385,10 @@ mod tests { panic!("Unexpected mentions"); }; pretty_assertions::assert_eq!(content, "8"); - pretty_assertions::assert_eq!(uri, &url_eight.parse::().unwrap()); + pretty_assertions::assert_eq!( + uri, + &MentionUri::parse(&url_eight, PathStyle::local()).unwrap() + ); } editor.update(&mut cx, |editor, cx| { diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 7e5d5b48a13adb7c3133245cd520f7b48c46517a..3638faf9336f79d692f820df39266ab7b85360a8 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -4305,7 +4305,8 @@ impl AcpThreadView { return; }; - if let Some(mention) = MentionUri::parse(&url).log_err() { + if let Some(mention) = MentionUri::parse(&url, workspace.read(cx).path_style(cx)).log_err() + { workspace.update(cx, |workspace, cx| match mention { MentionUri::File { abs_path } => { let project = workspace.project(); diff --git a/crates/editor/src/inlays/inlay_hints.rs b/crates/editor/src/inlays/inlay_hints.rs index 3aab7be8207ef09b9ae18140cbfcd749eb9bc0d1..2cbea33932d8f6f2c9924036dd60bb2ef68044dc 100644 --- a/crates/editor/src/inlays/inlay_hints.rs +++ b/crates/editor/src/inlays/inlay_hints.rs @@ -344,7 +344,7 @@ impl Editor { .extend(invalidate_hints_for_buffers); let mut buffers_to_query = HashMap::default(); - for (excerpt_id, (buffer, buffer_version, visible_range)) in visible_excerpts { + for (_, (buffer, buffer_version, visible_range)) in visible_excerpts { let buffer_id = buffer.read(cx).remote_id(); if !self.registered_buffers.contains_key(&buffer_id) { continue; @@ -358,13 +358,11 @@ impl Editor { buffers_to_query .entry(buffer_id) .or_insert_with(|| VisibleExcerpts { - excerpts: Vec::new(), ranges: Vec::new(), buffer_version: buffer_version.clone(), buffer: buffer.clone(), }); visible_excerpts.buffer_version = buffer_version; - visible_excerpts.excerpts.push(excerpt_id); visible_excerpts.ranges.push(buffer_anchor_range); } @@ -850,7 +848,6 @@ impl Editor { #[derive(Debug)] struct VisibleExcerpts { - excerpts: Vec, ranges: Vec>, buffer_version: Global, buffer: Entity, @@ -2017,7 +2014,7 @@ pub mod tests { task_lsp_request_ranges.lock().push(params.range); task_lsp_request_count.fetch_add(1, Ordering::Release); Ok(Some(vec![lsp::InlayHint { - position: params.range.end, + position: params.range.start, label: lsp::InlayHintLabel::String( params.range.end.line.to_string(), ), diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index faf53fa802c858822c20635f4ebb906cbdd4b886..139ac25fe781a97ee448ebc83034538c19a56aa6 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -6844,7 +6844,7 @@ impl LspStore { && range.start.is_valid(&buffer_snapshot) && range.end.is_valid(&buffer_snapshot) && hint.position.cmp(&range.start, &buffer_snapshot).is_ge() - && hint.position.cmp(&range.end, &buffer_snapshot).is_le() + && hint.position.cmp(&range.end, &buffer_snapshot).is_lt() }); (server_id, new_hints) }) diff --git a/crates/ui/src/components/scrollbar.rs b/crates/ui/src/components/scrollbar.rs index bfcaa93eb41f22c36d106273f6da98da38981f62..eb1d210280872a8b1606439c04c1f67954e50373 100644 --- a/crates/ui/src/components/scrollbar.rs +++ b/crates/ui/src/components/scrollbar.rs @@ -101,13 +101,21 @@ where T: ScrollableHandle, { let element_id = config.id.take().unwrap_or_else(|| caller_location.into()); + let track_color = config.track_color; - window.use_keyed_state(element_id, cx, |window, cx| { + let state = window.use_keyed_state(element_id, cx, |window, cx| { let parent_id = cx.entity_id(); ScrollbarStateWrapper( cx.new(|cx| ScrollbarState::new_from_config(config, parent_id, window, cx)), ) - }) + }); + + state.update(cx, |state, cx| { + state + .0 + .update(cx, |state, _cx| state.update_track_color(track_color)) + }); + state } pub trait WithScrollbar: Sized { @@ -334,7 +342,7 @@ enum ReservedSpace { #[default] None, Thumb, - Track(Hsla), + Track, } impl ReservedSpace { @@ -343,14 +351,7 @@ impl ReservedSpace { } fn needs_scroll_track(&self) -> bool { - matches!(self, ReservedSpace::Track(_)) - } - - fn track_color(&self) -> Option { - match self { - ReservedSpace::Track(color) => Some(*color), - _ => None, - } + *self == ReservedSpace::Track } } @@ -385,6 +386,7 @@ pub struct Scrollbars { tracked_entity: Option>, scrollable_handle: Handle, visibility: Point, + track_color: Option, scrollbar_width: ScrollbarWidth, } @@ -406,6 +408,7 @@ impl Scrollbars { scrollable_handle: Handle::Untracked(ScrollHandle::new), tracked_entity: None, visibility: show_along.apply_to(Default::default(), ReservedSpace::Thumb), + track_color: None, scrollbar_width: ScrollbarWidth::Normal, } } @@ -446,6 +449,7 @@ impl Scrollbars { scrollbar_width, visibility, get_visibility, + track_color, .. } = self; @@ -455,6 +459,7 @@ impl Scrollbars { tracked_entity: tracked_entity_id, visibility, scrollbar_width, + track_color, get_visibility, } } @@ -465,7 +470,8 @@ impl Scrollbars { } pub fn with_track_along(mut self, along: ScrollAxes, background_color: Hsla) -> Self { - self.visibility = along.apply_to(self.visibility, ReservedSpace::Track(background_color)); + self.visibility = along.apply_to(self.visibility, ReservedSpace::Track); + self.track_color = Some(background_color); self } @@ -593,6 +599,7 @@ struct ScrollbarState { show_behavior: ShowBehavior, get_visibility: fn(&App) -> ShowScrollbar, visibility: Point, + track_color: Option, show_state: VisibilityState, mouse_in_parent: bool, last_prepaint_state: Option, @@ -622,6 +629,7 @@ impl ScrollbarState { scroll_handle, width: config.scrollbar_width, visibility: config.visibility, + track_color: config.track_color, show_behavior, get_visibility: config.get_visibility, show_state: VisibilityState::from_behavior(show_behavior), @@ -794,6 +802,10 @@ impl ScrollbarState { } } + fn update_track_color(&mut self, track_color: Option) { + self.track_color = track_color; + } + fn parent_hovered(&self, window: &Window) -> bool { self.last_prepaint_state .as_ref() @@ -1103,8 +1115,10 @@ impl Element for ScrollbarElement { .not() .then(|| ScrollbarPrepaintState { thumbs: { - let thumb_ranges = self.state.read(cx).thumb_ranges().collect::>(); - let width = self.state.read(cx).width.to_pixels(); + let state = self.state.read(cx); + let thumb_ranges = state.thumb_ranges().collect::>(); + let width = state.width.to_pixels(); + let track_color = state.track_color; let additional_padding = if thumb_ranges.len() == 2 { width @@ -1169,8 +1183,7 @@ impl Element for ScrollbarElement { }, HitboxBehavior::BlockMouseExceptScroll, ), - track_background: reserved_space - .track_color() + track_background: track_color .map(|color| (padded_bounds.dilate(SCROLLBAR_PADDING), color)), reserved_space, }