Release Notes: - N/A
Unified · Split
| @@ -2840,10 +2840,10 @@ pub(crate) fn open_context( · 2839 unmodified lines | ||
| 2840 | 2840 | } |
| 2841 | 2841 | } |
| 2842 | 2842 | AssistantContext::Directory(directory_context) => { |
| 2843 | − let path = directory_context.project_path.clone(); | |
| 2843 | + let project_path = directory_context.project_path(cx); | |
| 2844 | 2844 | workspace.update(cx, |workspace, cx| { |
| 2845 | 2845 | workspace.project().update(cx, |project, cx| { |
| 2846 | − if let Some(entry) = project.entry_for_path(&path, cx) { | |
| 2846 | + if let Some(entry) = project.entry_for_path(&project_path, cx) { | |
| 2847 | 2847 | cx.emit(project::Event::RevealInProjectPanel(entry.id)); |
| 2848 | 2848 | } |
| 2849 | 2849 | }) |
| @@ -1,9 +1,9 @@ | ||
| 1 | −use std::{ops::Range, sync::Arc}; | |
| 1 | +use std::{ops::Range, path::Path, sync::Arc}; | |
| 2 | 2 | |
| 3 | 3 | use gpui::{App, Entity, SharedString}; |
| 4 | 4 | use language::{Buffer, File}; |
| 5 | 5 | use language_model::LanguageModelRequestMessage; |
| 6 | −use project::ProjectPath; | |
| 6 | +use project::{ProjectPath, Worktree}; | |
| 7 | 7 | use serde::{Deserialize, Serialize}; |
| 8 | 8 | use text::{Anchor, BufferId}; |
| 9 | 9 | use ui::IconName; |
| @@ -69,10 +69,21 @@ pub struct FileContext { · 59 unmodified lines | ||
| 69 | 69 | #[derive(Debug, Clone)] |
| 70 | 70 | pub struct DirectoryContext { |
| 71 | 71 | pub id: ContextId, |
| 72 | − pub project_path: ProjectPath, | |
| 72 | + pub worktree: Entity<Worktree>, | |
| 73 | + pub path: Arc<Path>, | |
| 74 | + /// Buffers of the files within the directory. | |
| 73 | 75 | pub context_buffers: Vec<ContextBuffer>, |
| 74 | 76 | } |
| 75 | 77 | |
| 78 | +impl DirectoryContext { | |
| 79 | + pub fn project_path(&self, cx: &App) -> ProjectPath { | |
| 80 | + ProjectPath { | |
| 81 | + worktree_id: self.worktree.read(cx).id(), | |
| 82 | + path: self.path.clone(), | |
| 83 | + } | |
| 84 | + } | |
| 85 | +} | |
| 86 | + | |
| 76 | 87 | #[derive(Debug, Clone)] |
| 77 | 88 | pub struct SymbolContext { |
| 78 | 89 | pub id: ContextId, |
| @@ -86,12 +97,11 @@ pub struct FetchedUrlContext { · 7 unmodified lines | ||
| 86 | 97 | pub text: SharedString, |
| 87 | 98 | } |
| 88 | 99 | |
| 89 | −// TODO: Model<Thread> holds onto the thread even if the thread is deleted. Can either handle this | |
| 90 | −// explicitly or have a WeakModel<Thread> and remove during snapshot. | |
| 91 | − | |
| 92 | 100 | #[derive(Debug, Clone)] |
| 93 | 101 | pub struct ThreadContext { |
| 94 | 102 | pub id: ContextId, |
| 103 | + // TODO: Entity<Thread> holds onto the thread even if the thread is deleted. Should probably be | |
| 104 | + // a WeakEntity and handle removal from the UI when it has dropped. | |
| 95 | 105 | pub thread: Entity<Thread>, |
| 96 | 106 | pub text: SharedString, |
| 97 | 107 | } |
| @@ -105,12 +115,11 @@ impl ThreadContext { · 7 unmodified lines | ||
| 105 | 115 | } |
| 106 | 116 | } |
| 107 | 117 | |
| 108 | −// TODO: Model<Buffer> holds onto the buffer even if the file is deleted and closed. Should remove | |
| 109 | −// the context from the message editor in this case. | |
| 110 | − | |
| 111 | 118 | #[derive(Clone)] |
| 112 | 119 | pub struct ContextBuffer { |
| 113 | 120 | pub id: BufferId, |
| 121 | + // TODO: Entity<Buffer> holds onto the thread even if the thread is deleted. Should probably be | |
| 122 | + // a WeakEntity and handle removal from the UI when it has dropped. | |
| 114 | 123 | pub buffer: Entity<Buffer>, |
| 115 | 124 | pub file: Arc<dyn File>, |
| 116 | 125 | pub version: clock::Global, |
| @@ -289,12 +289,14 @@ impl ContextPicker { · 288 unmodified lines | ||
| 289 | 289 | path_prefix, |
| 290 | 290 | } => { |
| 291 | 291 | let context_store = self.context_store.clone(); |
| 292 | + let worktree_id = project_path.worktree_id; | |
| 292 | 293 | let path = project_path.path.clone(); |
| 293 | 294 | |
| 294 | 295 | ContextMenuItem::custom_entry( |
| 295 | 296 | move |_window, cx| { |
| 296 | 297 | render_file_context_entry( |
| 297 | 298 | ElementId::NamedInteger("ctx-recent".into(), ix), |
| 299 | + worktree_id, | |
| 298 | 300 | &path, |
| 299 | 301 | &path_prefix, |
| 300 | 302 | false, |
| @@ -466,7 +468,7 @@ fn recent_context_picker_entries( · 165 unmodified lines | ||
| 466 | 468 | recent.extend( |
| 467 | 469 | workspace |
| 468 | 470 | .recent_navigation_history_iter(cx) |
| 469 | − .filter(|(path, _)| !current_files.contains(&path.path.to_path_buf())) | |
| 471 | + .filter(|(path, _)| !current_files.contains(path)) | |
| 470 | 472 | .take(4) |
| 471 | 473 | .filter_map(|(project_path, _)| { |
| 472 | 474 | project |
| @@ -189,6 +189,7 @@ impl PickerDelegate for FileContextPickerDelegate { · 188 unmodified lines | ||
| 189 | 189 | .toggle_state(selected) |
| 190 | 190 | .child(render_file_context_entry( |
| 191 | 191 | ElementId::NamedInteger("file-ctx-picker".into(), ix), |
| 192 | + WorktreeId::from_usize(mat.worktree_id), | |
| 192 | 193 | &mat.path, |
| 193 | 194 | &mat.path_prefix, |
| 194 | 195 | mat.is_dir, |
| @@ -328,19 +329,26 @@ pub fn extract_file_name_and_directory( · 133 unmodified lines | ||
| 328 | 329 | |
| 329 | 330 | pub fn render_file_context_entry( |
| 330 | 331 | id: ElementId, |
| 331 | − path: &Path, | |
| 332 | + worktree_id: WorktreeId, | |
| 333 | + path: &Arc<Path>, | |
| 332 | 334 | path_prefix: &Arc<str>, |
| 333 | 335 | is_directory: bool, |
| 334 | 336 | context_store: WeakEntity<ContextStore>, |
| 335 | 337 | cx: &App, |
| 336 | 338 | ) -> Stateful<Div> { |
| 337 | − let (file_name, directory) = extract_file_name_and_directory(path, path_prefix); | |
| 339 | + let (file_name, directory) = extract_file_name_and_directory(&path, path_prefix); | |
| 338 | 340 | |
| 339 | 341 | let added = context_store.upgrade().and_then(|context_store| { |
| 342 | + let project_path = ProjectPath { | |
| 343 | + worktree_id, | |
| 344 | + path: path.clone(), | |
| 345 | + }; | |
| 340 | 346 | if is_directory { |
| 341 | − context_store.read(cx).includes_directory(path) | |
| 347 | + context_store.read(cx).includes_directory(&project_path) | |
| 342 | 348 | } else { |
| 343 | − context_store.read(cx).will_include_file_path(path, cx) | |
| 349 | + context_store | |
| 350 | + .read(cx) | |
| 351 | + .will_include_file_path(&project_path, cx) | |
| 344 | 352 | } |
| 345 | 353 | }); |
| 346 | 354 | |
| @@ -380,8 +388,9 @@ pub fn render_file_context_entry( · 33 unmodified lines | ||
| 380 | 388 | ) |
| 381 | 389 | .child(Label::new("Added").size(LabelSize::Small)), |
| 382 | 390 | ), |
| 383 | − FileInclusion::InDirectory(dir_name) => { | |
| 384 | − let dir_name = dir_name.to_string_lossy().into_owned(); | |
| 391 | + FileInclusion::InDirectory(directory_project_path) => { | |
| 392 | + // TODO: Consider using worktree full_path to include worktree name. | |
| 393 | + let directory_path = directory_project_path.path.to_string_lossy().into_owned(); | |
| 385 | 394 | |
| 386 | 395 | el.child( |
| 387 | 396 | h_flex() |
| @@ -395,7 +404,7 @@ pub fn render_file_context_entry( · 7 unmodified lines | ||
| 395 | 404 | ) |
| 396 | 405 | .child(Label::new("Included").size(LabelSize::Small)), |
| 397 | 406 | ) |
| 398 | − .tooltip(Tooltip::text(format!("in {dir_name}"))) | |
| 407 | + .tooltip(Tooltip::text(format!("in {directory_path}"))) | |
| 399 | 408 | } |
| 400 | 409 | }) |
| 401 | 410 | } |
| @@ -1,5 +1,5 @@ | ||
| 1 | 1 | use std::ops::Range; |
| 2 | −use std::path::{Path, PathBuf}; | |
| 2 | +use std::path::Path; | |
| 3 | 3 | use std::sync::Arc; |
| 4 | 4 | |
| 5 | 5 | use anyhow::{Context as _, Result, anyhow}; |
| @@ -28,7 +28,7 @@ pub struct ContextStore { · 22 unmodified lines | ||
| 28 | 28 | // TODO: If an EntityId is used for all context types (like BufferId), can remove ContextId. |
| 29 | 29 | next_context_id: ContextId, |
| 30 | 30 | files: BTreeMap<BufferId, ContextId>, |
| 31 | − directories: HashMap<PathBuf, ContextId>, | |
| 31 | + directories: HashMap<ProjectPath, ContextId>, | |
| 32 | 32 | symbols: HashMap<ContextSymbolId, ContextId>, |
| 33 | 33 | symbol_buffers: HashMap<ContextSymbolId, Entity<Buffer>>, |
| 34 | 34 | symbols_by_path: HashMap<ProjectPath, Vec<ContextSymbolId>>, |
| @@ -93,7 +93,7 @@ impl ContextStore { · 58 unmodified lines | ||
| 93 | 93 | let buffer_id = this.update(cx, |_, cx| buffer.read(cx).remote_id())?; |
| 94 | 94 | |
| 95 | 95 | let already_included = this.update(cx, |this, cx| { |
| 96 | − match this.will_include_buffer(buffer_id, &project_path.path) { | |
| 96 | + match this.will_include_buffer(buffer_id, &project_path) { | |
| 97 | 97 | Some(FileInclusion::Direct(context_id)) => { |
| 98 | 98 | if remove_if_exists { |
| 99 | 99 | this.remove_context(context_id, cx); |
| @@ -159,7 +159,7 @@ impl ContextStore { · 59 unmodified lines | ||
| 159 | 159 | return Task::ready(Err(anyhow!("failed to read project"))); |
| 160 | 160 | }; |
| 161 | 161 | |
| 162 | − let already_included = match self.includes_directory(&project_path.path) { | |
| 162 | + let already_included = match self.includes_directory(&project_path) { | |
| 163 | 163 | Some(FileInclusion::Direct(context_id)) => { |
| 164 | 164 | if remove_if_exists { |
| 165 | 165 | self.remove_context(context_id, cx); |
| @@ -223,14 +223,12 @@ impl ContextStore { · 57 unmodified lines | ||
| 223 | 223 | .collect::<Vec<_>>(); |
| 224 | 224 | |
| 225 | 225 | if context_buffers.is_empty() { |
| 226 | − return Err(anyhow!( | |
| 227 | − "No text files found in {}", | |
| 228 | − &project_path.path.display() | |
| 229 | − )); | |
| 226 | + let full_path = cx.update(|cx| worktree.read(cx).full_path(&project_path.path))?; | |
| 227 | + return Err(anyhow!("No text files found in {}", &full_path.display())); | |
| 230 | 228 | } |
| 231 | 229 | |
| 232 | 230 | this.update(cx, |this, cx| { |
| 233 | − this.insert_directory(project_path, context_buffers, cx); | |
| 231 | + this.insert_directory(worktree, project_path, context_buffers, cx); | |
| 234 | 232 | })?; |
| 235 | 233 | |
| 236 | 234 | anyhow::Ok(()) |
| @@ -239,17 +237,20 @@ impl ContextStore { · 2 unmodified lines | ||
| 239 | 237 | |
| 240 | 238 | fn insert_directory( |
| 241 | 239 | &mut self, |
| 240 | + worktree: Entity<Worktree>, | |
| 242 | 241 | project_path: ProjectPath, |
| 243 | 242 | context_buffers: Vec<ContextBuffer>, |
| 244 | 243 | cx: &mut Context<Self>, |
| 245 | 244 | ) { |
| 246 | 245 | let id = self.next_context_id.post_inc(); |
| 247 | − self.directories.insert(project_path.path.to_path_buf(), id); | |
| 246 | + let path = project_path.path.clone(); | |
| 247 | + self.directories.insert(project_path, id); | |
| 248 | 248 | |
| 249 | 249 | self.context |
| 250 | 250 | .push(AssistantContext::Directory(DirectoryContext { |
| 251 | 251 | id, |
| 252 | − project_path, | |
| 252 | + worktree, | |
| 253 | + path, | |
| 253 | 254 | context_buffers, |
| 254 | 255 | })); |
| 255 | 256 | cx.notify(); |
| @@ -478,23 +479,31 @@ impl ContextStore { · 222 unmodified lines | ||
| 478 | 479 | /// Returns whether the buffer is already included directly in the context, or if it will be |
| 479 | 480 | /// included in the context via a directory. Directory inclusion is based on paths rather than |
| 480 | 481 | /// buffer IDs as the directory will be re-scanned. |
| 481 | − pub fn will_include_buffer(&self, buffer_id: BufferId, path: &Path) -> Option<FileInclusion> { | |
| 482 | + pub fn will_include_buffer( | |
| 483 | + &self, | |
| 484 | + buffer_id: BufferId, | |
| 485 | + project_path: &ProjectPath, | |
| 486 | + ) -> Option<FileInclusion> { | |
| 482 | 487 | if let Some(context_id) = self.files.get(&buffer_id) { |
| 483 | 488 | return Some(FileInclusion::Direct(*context_id)); |
| 484 | 489 | } |
| 485 | 490 | |
| 486 | − self.will_include_file_path_via_directory(path) | |
| 491 | + self.will_include_file_path_via_directory(project_path) | |
| 487 | 492 | } |
| 488 | 493 | |
| 489 | 494 | /// Returns whether this file path is already included directly in the context, or if it will be |
| 490 | 495 | /// included in the context via a directory. |
| 491 | − pub fn will_include_file_path(&self, path: &Path, cx: &App) -> Option<FileInclusion> { | |
| 496 | + pub fn will_include_file_path( | |
| 497 | + &self, | |
| 498 | + project_path: &ProjectPath, | |
| 499 | + cx: &App, | |
| 500 | + ) -> Option<FileInclusion> { | |
| 492 | 501 | if !self.files.is_empty() { |
| 493 | 502 | let found_file_context = self.context.iter().find(|context| match &context { |
| 494 | 503 | AssistantContext::File(file_context) => { |
| 495 | 504 | let buffer = file_context.context_buffer.buffer.read(cx); |
| 496 | − if let Some(file_path) = buffer_path_log_err(buffer, cx) { | |
| 497 | − *file_path == *path | |
| 505 | + if let Some(context_path) = buffer.project_path(cx) { | |
| 506 | + &context_path == project_path | |
| 498 | 507 | } else { |
| 499 | 508 | false |
| 500 | 509 | } |
| @@ -506,31 +515,40 @@ impl ContextStore { · 5 unmodified lines | ||
| 506 | 515 | } |
| 507 | 516 | } |
| 508 | 517 | |
| 509 | − self.will_include_file_path_via_directory(path) | |
| 518 | + self.will_include_file_path_via_directory(project_path) | |
| 510 | 519 | } |
| 511 | 520 | |
| 512 | − fn will_include_file_path_via_directory(&self, path: &Path) -> Option<FileInclusion> { | |
| 521 | + fn will_include_file_path_via_directory( | |
| 522 | + &self, | |
| 523 | + project_path: &ProjectPath, | |
| 524 | + ) -> Option<FileInclusion> { | |
| 513 | 525 | if self.directories.is_empty() { |
| 514 | 526 | return None; |
| 515 | 527 | } |
| 516 | 528 | |
| 517 | − let mut buf = path.to_path_buf(); | |
| 529 | + let mut path_buf = project_path.path.to_path_buf(); | |
| 518 | 530 | |
| 519 | − while buf.pop() { | |
| 520 | − if let Some(_) = self.directories.get(&buf) { | |
| 521 | − return Some(FileInclusion::InDirectory(buf)); | |
| 531 | + while path_buf.pop() { | |
| 532 | + // TODO: This isn't very efficient. Consider using a better representation of the | |
| 533 | + // directories map. | |
| 534 | + let directory_project_path = ProjectPath { | |
| 535 | + worktree_id: project_path.worktree_id, | |
| 536 | + path: path_buf.clone().into(), | |
| 537 | + }; | |
| 538 | + if let Some(_) = self.directories.get(&directory_project_path) { | |
| 539 | + return Some(FileInclusion::InDirectory(directory_project_path)); | |
| 522 | 540 | } |
| 523 | 541 | } |
| 524 | 542 | |
| 525 | 543 | None |
| 526 | 544 | } |
| 527 | 545 | |
| 528 | − pub fn includes_directory(&self, path: &Path) -> Option<FileInclusion> { | |
| 529 | − if let Some(context_id) = self.directories.get(path) { | |
| 546 | + pub fn includes_directory(&self, project_path: &ProjectPath) -> Option<FileInclusion> { | |
| 547 | + if let Some(context_id) = self.directories.get(project_path) { | |
| 530 | 548 | return Some(FileInclusion::Direct(*context_id)); |
| 531 | 549 | } |
| 532 | 550 | |
| 533 | − self.will_include_file_path_via_directory(path) | |
| 551 | + self.will_include_file_path_via_directory(project_path) | |
| 534 | 552 | } |
| 535 | 553 | |
| 536 | 554 | pub fn included_symbol(&self, symbol_id: &ContextSymbolId) -> Option<ContextId> { |
| @@ -564,13 +582,13 @@ impl ContextStore { · 27 unmodified lines | ||
| 564 | 582 | } |
| 565 | 583 | } |
| 566 | 584 | |
| 567 | − pub fn file_paths(&self, cx: &App) -> HashSet<PathBuf> { | |
| 585 | + pub fn file_paths(&self, cx: &App) -> HashSet<ProjectPath> { | |
| 568 | 586 | self.context |
| 569 | 587 | .iter() |
| 570 | 588 | .filter_map(|context| match context { |
| 571 | 589 | AssistantContext::File(file) => { |
| 572 | 590 | let buffer = file.context_buffer.buffer.read(cx); |
| 573 | − buffer_path_log_err(buffer, cx).map(|p| p.to_path_buf()) | |
| 591 | + buffer.project_path(cx) | |
| 574 | 592 | } |
| 575 | 593 | AssistantContext::Directory(_) |
| 576 | 594 | | AssistantContext::Symbol(_) |
| @@ -587,7 +605,7 @@ impl ContextStore { · 10 unmodified lines | ||
| 587 | 605 | |
| 588 | 606 | pub enum FileInclusion { |
| 589 | 607 | Direct(ContextId), |
| 590 | − InDirectory(PathBuf), | |
| 608 | + InDirectory(ProjectPath), | |
| 591 | 609 | } |
| 592 | 610 | |
| 593 | 611 | // ContextBuffer without text. |
| @@ -654,19 +672,6 @@ fn collect_buffer_info_and_text( · 60 unmodified lines | ||
| 654 | 672 | Ok((buffer_info, text_task)) |
| 655 | 673 | } |
| 656 | 674 | |
| 657 | −pub fn buffer_path_log_err(buffer: &Buffer, cx: &App) -> Option<Arc<Path>> { | |
| 658 | − if let Some(file) = buffer.file() { | |
| 659 | − let mut path = file.path().clone(); | |
| 660 | − if path.as_os_str().is_empty() { | |
| 661 | − path = file.full_path(cx).into(); | |
| 662 | − } | |
| 663 | − Some(path) | |
| 664 | − } else { | |
| 665 | − log::error!("Buffer that had a path unexpectedly no longer has a path."); | |
| 666 | − None | |
| 667 | − } | |
| 668 | −} | |
| 669 | − | |
| 670 | 675 | fn to_fenced_codeblock(path: &Path, content: Rope) -> SharedString { |
| 671 | 676 | let path_extension = path.extension().and_then(|ext| ext.to_str()); |
| 672 | 677 | let path_string = path.to_string_lossy(); |
| @@ -742,13 +747,13 @@ pub fn refresh_context_store_text( · 69 unmodified lines | ||
| 742 | 747 | } |
| 743 | 748 | } |
| 744 | 749 | AssistantContext::Directory(directory_context) => { |
| 750 | + let directory_path = directory_context.project_path(cx); | |
| 745 | 751 | let should_refresh = changed_buffers.is_empty() |
| 746 | 752 | || changed_buffers.iter().any(|buffer| { |
| 747 | − let buffer = buffer.read(cx); | |
| 748 | − | |
| 749 | − buffer_path_log_err(&buffer, cx).map_or(false, |path| { | |
| 750 | − path.starts_with(&directory_context.project_path.path) | |
| 751 | − }) | |
| 753 | + let Some(buffer_path) = buffer.read(cx).project_path(cx) else { | |
| 754 | + return false; | |
| 755 | + }; | |
| 756 | + buffer_path.starts_with(&directory_path) | |
| 752 | 757 | }); |
| 753 | 758 | |
| 754 | 759 | if should_refresh { |
| @@ -835,14 +840,16 @@ fn refresh_directory_text( · 80 unmodified lines | ||
| 835 | 840 | let context_buffers = future::join_all(futures); |
| 836 | 841 | |
| 837 | 842 | let id = directory_context.id; |
| 838 | − let project_path = directory_context.project_path.clone(); | |
| 843 | + let worktree = directory_context.worktree.clone(); | |
| 844 | + let path = directory_context.path.clone(); | |
| 839 | 845 | Some(cx.spawn(async move |cx| { |
| 840 | 846 | let context_buffers = context_buffers.await; |
| 841 | 847 | context_store |
| 842 | 848 | .update(cx, |context_store, _| { |
| 843 | 849 | let new_directory_context = DirectoryContext { |
| 844 | 850 | id, |
| 845 | − project_path, | |
| 851 | + worktree, | |
| 852 | + path, | |
| 846 | 853 | context_buffers, |
| 847 | 854 | }; |
| 848 | 855 | context_store.replace_context(AssistantContext::Directory(new_directory_context)); |
| @@ -1,3 +1,4 @@ | ||
| 1 | +use std::path::Path; | |
| 1 | 2 | use std::rc::Rc; |
| 2 | 3 | |
| 3 | 4 | use collections::HashSet; |
| @@ -9,6 +10,7 @@ use gpui::{ · 5 unmodified lines | ||
| 9 | 10 | }; |
| 10 | 11 | use itertools::Itertools; |
| 11 | 12 | use language::Buffer; |
| 13 | +use project::ProjectItem; | |
| 12 | 14 | use ui::{KeyBinding, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*}; |
| 13 | 15 | use workspace::{Workspace, notifications::NotifyResultExt}; |
| 14 | 16 | |
| @@ -93,26 +95,23 @@ impl ContextStrip { · 78 unmodified lines | ||
| 93 | 95 | let active_buffer_entity = editor.buffer().read(cx).as_singleton()?; |
| 94 | 96 | let active_buffer = active_buffer_entity.read(cx); |
| 95 | 97 | |
| 96 | − let path = active_buffer.file()?.full_path(cx); | |
| 98 | + let project_path = active_buffer.project_path(cx)?; | |
| 97 | 99 | |
| 98 | 100 | if self |
| 99 | 101 | .context_store |
| 100 | 102 | .read(cx) |
| 101 | − .will_include_buffer(active_buffer.remote_id(), &path) | |
| 103 | + .will_include_buffer(active_buffer.remote_id(), &project_path) | |
| 102 | 104 | .is_some() |
| 103 | 105 | { |
| 104 | 106 | return None; |
| 105 | 107 | } |
| 106 | 108 | |
| 107 | − let name = match path.file_name() { | |
| 108 | − Some(name) => name.to_string_lossy().into_owned().into(), | |
| 109 | − None => path.to_string_lossy().into_owned().into(), | |
| 110 | − }; | |
| 109 | + let file_name = active_buffer.file()?.file_name(cx); | |
| 111 | 110 | |
| 112 | − let icon_path = FileIcons::get_icon(&path, cx); | |
| 111 | + let icon_path = FileIcons::get_icon(&Path::new(&file_name), cx); | |
| 113 | 112 | |
| 114 | 113 | Some(SuggestedContext::File { |
| 115 | − name, | |
| 114 | + name: file_name.to_string_lossy().into_owned().into(), | |
| 116 | 115 | buffer: active_buffer_entity.downgrade(), |
| 117 | 116 | icon_path, |
| 118 | 117 | }) |
| @@ -280,9 +280,10 @@ impl AddedContext { · 279 unmodified lines | ||
| 280 | 280 | } |
| 281 | 281 | |
| 282 | 282 | AssistantContext::Directory(directory_context) => { |
| 283 | − // TODO: handle worktree disambiguation. Maybe by storing an `Arc<dyn File>` to also | |
| 284 | − // handle renames? | |
| 285 | − let full_path = &directory_context.project_path.path; | |
| 283 | + let full_path = directory_context | |
| 284 | + .worktree | |
| 285 | + .read(cx) | |
| 286 | + .full_path(&directory_context.path); | |
| 286 | 287 | let full_path_string: SharedString = |
| 287 | 288 | full_path.to_string_lossy().into_owned().into(); |
| 288 | 289 | let name = full_path |
| @@ -334,6 +334,10 @@ impl ProjectPath { · 333 unmodified lines | ||
| 334 | 334 | path: Path::new("").into(), |
| 335 | 335 | } |
| 336 | 336 | } |
| 337 | + | |
| 338 | + pub fn starts_with(&self, other: &ProjectPath) -> bool { | |
| 339 | + self.worktree_id == other.worktree_id && self.path.starts_with(&other.path) | |
| 340 | + } | |
| 337 | 341 | } |
| 338 | 342 | |
| 339 | 343 | #[derive(Debug, Default)] |
| @@ -1172,6 +1172,31 @@ impl Worktree { · 1171 unmodified lines | ||
| 1172 | 1172 | pub fn is_single_file(&self) -> bool { |
| 1173 | 1173 | self.root_dir().is_none() |
| 1174 | 1174 | } |
| 1175 | + | |
| 1176 | + /// For visible worktrees, returns the path with the worktree name as the first component. | |
| 1177 | + /// Otherwise, returns an absolute path. | |
| 1178 | + pub fn full_path(&self, worktree_relative_path: &Path) -> PathBuf { | |
| 1179 | + let mut full_path = PathBuf::new(); | |
| 1180 | + | |
| 1181 | + if self.is_visible() { | |
| 1182 | + full_path.push(self.root_name()); | |
| 1183 | + } else { | |
| 1184 | + let path = self.abs_path(); | |
| 1185 | + | |
| 1186 | + if self.is_local() && path.starts_with(home_dir().as_path()) { | |
| 1187 | + full_path.push("~"); | |
| 1188 | + full_path.push(path.strip_prefix(home_dir().as_path()).unwrap()); | |
| 1189 | + } else { | |
| 1190 | + full_path.push(path) | |
| 1191 | + } | |
| 1192 | + } | |
| 1193 | + | |
| 1194 | + if worktree_relative_path.components().next().is_some() { | |
| 1195 | + full_path.push(&worktree_relative_path); | |
| 1196 | + } | |
| 1197 | + | |
| 1198 | + full_path | |
| 1199 | + } | |
| 1175 | 1200 | } |
| 1176 | 1201 | |
| 1177 | 1202 | impl LocalWorktree { |
| @@ -3229,27 +3254,7 @@ impl language::File for File { · 2051 unmodified lines | ||
| 3229 | 3254 | } |
| 3230 | 3255 | |
| 3231 | 3256 | fn full_path(&self, cx: &App) -> PathBuf { |
| 3232 | − let mut full_path = PathBuf::new(); | |
| 3233 | − let worktree = self.worktree.read(cx); | |
| 3234 | − | |
| 3235 | − if worktree.is_visible() { | |
| 3236 | − full_path.push(worktree.root_name()); | |
| 3237 | − } else { | |
| 3238 | − let path = worktree.abs_path(); | |
| 3239 | − | |
| 3240 | − if worktree.is_local() && path.starts_with(home_dir().as_path()) { | |
| 3241 | − full_path.push("~"); | |
| 3242 | − full_path.push(path.strip_prefix(home_dir().as_path()).unwrap()); | |
| 3243 | − } else { | |
| 3244 | − full_path.push(path) | |
| 3245 | − } | |
| 3246 | − } | |
| 3247 | − | |
| 3248 | − if self.path.components().next().is_some() { | |
| 3249 | − full_path.push(&self.path); | |
| 3250 | − } | |
| 3251 | − | |
| 3252 | − full_path | |
| 3257 | + self.worktree.read(cx).full_path(&self.path) | |
| 3253 | 3258 | } |
| 3254 | 3259 | |
| 3255 | 3260 | /// Returns the last component of this handle's absolute path. If this handle refers to the root |