Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:59:11.188Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

path_link.rs

523 lines · 19.0 KB · rust
1use crate::Workspace;
2use gpui::{App, AppContext, Entity, Task, WeakEntity};
3use itertools::Itertools;
4use project::{Entry, Worktree};
5use std::path::{Path, PathBuf};
6use util::{
7    paths::{PathStyle, PathWithPosition, normalize_lexically},
8    rel_path::RelPath,
9};
10
11#[cfg(any(test, feature = "test-support"))]
12#[derive(Debug, Clone, Copy, Eq, PartialEq)]
13pub enum OpenTargetFoundBy {
14    WorktreeExact,
15    WorktreeScan,
16    BackgroundPathResolution,
17}
18
19#[derive(Debug, Clone, Copy, Eq, PartialEq)]
20pub enum BackgroundPathChecks {
21    LocalFileSystem,
22    ProjectPathResolution,
23}
24
25#[derive(Debug, Clone)]
26pub enum OpenTarget {
27    Worktree(
28        PathWithPosition,
29        Entry,
30        #[cfg(any(test, feature = "test-support"))] OpenTargetFoundBy,
31    ),
32    Path(
33        PathWithPosition,
34        bool,
35        #[cfg(any(test, feature = "test-support"))] OpenTargetFoundBy,
36    ),
37}
38
39impl OpenTarget {
40    pub fn is_file(&self) -> bool {
41        match self {
42            OpenTarget::Worktree(_, entry, ..) => entry.is_file(),
43            OpenTarget::Path(_, is_dir, ..) => !is_dir,
44        }
45    }
46
47    pub fn is_dir(&self) -> bool {
48        match self {
49            OpenTarget::Worktree(_, entry, ..) => entry.is_dir(),
50            OpenTarget::Path(_, is_dir, ..) => *is_dir,
51        }
52    }
53
54    pub fn path(&self) -> &PathWithPosition {
55        match self {
56            OpenTarget::Worktree(path, ..) => path,
57            OpenTarget::Path(path, ..) => path,
58        }
59    }
60
61    #[cfg(any(test, feature = "test-support"))]
62    pub fn found_by(&self) -> OpenTargetFoundBy {
63        match self {
64            OpenTarget::Worktree(.., found_by) => *found_by,
65            OpenTarget::Path(.., found_by) => *found_by,
66        }
67    }
68}
69
70pub fn sanitize_path_text(text: &str) -> &str {
71    let start = first_unbalanced_open_paren(text).unwrap_or(0);
72    let mut sanitized = &text[start..];
73    let (open_parens, mut close_parens) =
74        sanitized
75            .chars()
76            .fold((0, 0), |(opens, closes), character| match character {
77                '(' => (opens + 1, closes),
78                ')' => (opens, closes + 1),
79                _ => (opens, closes),
80            });
81
82    while let Some(last_char) = sanitized.chars().last() {
83        let should_remove = match last_char {
84            '.' | ',' | ':' | ';' => true,
85            '(' => true,
86            ')' if close_parens > open_parens => {
87                close_parens -= 1;
88                true
89            }
90            _ => false,
91        };
92
93        if should_remove {
94            sanitized = &sanitized[..sanitized.len() - last_char.len_utf8()];
95        } else {
96            break;
97        }
98    }
99
100    sanitized
101}
102
103/// Returns the byte offset just past the first unbalanced `(` in `text`, or
104/// `None` if all parentheses are balanced.
105pub fn first_unbalanced_open_paren(text: &str) -> Option<usize> {
106    let mut balance: i32 = 0;
107    let mut first_unmatched = None;
108    for (index, character) in text.char_indices() {
109        match character {
110            '(' => {
111                if balance == 0 {
112                    first_unmatched = Some(index + character.len_utf8());
113                }
114                balance += 1;
115            }
116            ')' => {
117                balance -= 1;
118                if balance <= 0 {
119                    balance = 0;
120                    first_unmatched = None;
121                }
122            }
123            _ => {}
124        }
125    }
126    first_unmatched.filter(|_| balance > 0)
127}
128
129pub fn possible_open_target(
130    workspace: &WeakEntity<Workspace>,
131    maybe_path: &str,
132    cwd: Option<&Path>,
133    cx: &App,
134) -> Task<Option<OpenTarget>> {
135    possible_open_target_internal(workspace, maybe_path, cwd, cx, None)
136}
137
138#[cfg(any(test, feature = "test-support"))]
139pub fn possible_open_target_with_fs_checks(
140    workspace: &WeakEntity<Workspace>,
141    maybe_path: &str,
142    cwd: Option<&Path>,
143    cx: &App,
144    background_path_checks: BackgroundPathChecks,
145) -> Task<Option<OpenTarget>> {
146    possible_open_target_internal(workspace, maybe_path, cwd, cx, Some(background_path_checks))
147}
148
149fn possible_open_target_internal(
150    workspace: &WeakEntity<Workspace>,
151    maybe_path: &str,
152    cwd: Option<&Path>,
153    cx: &App,
154    background_path_checks: Option<BackgroundPathChecks>,
155) -> Task<Option<OpenTarget>> {
156    let Some(workspace) = workspace.upgrade() else {
157        return Task::ready(None);
158    };
159
160    let mut potential_paths = Vec::new();
161    let original_path = PathWithPosition::from_path(PathBuf::from(maybe_path));
162    let path_with_position = PathWithPosition::parse_str(maybe_path);
163    let worktree_candidates = workspace
164        .read(cx)
165        .worktrees(cx)
166        .sorted_by_key(|worktree| {
167            let worktree_root = worktree.read(cx).abs_path();
168            match cwd.and_then(|cwd| worktree_root.strip_prefix(cwd).ok()) {
169                Some(cwd_child) => cwd_child.components().count(),
170                None => usize::MAX,
171            }
172        })
173        .collect::<Vec<_>>();
174
175    const GIT_DIFF_PATH_PREFIXES: &[&str] = &["a", "b"];
176    for prefix_str in GIT_DIFF_PATH_PREFIXES.iter().chain(std::iter::once(&".")) {
177        if let Some(stripped) = original_path.path.strip_prefix(prefix_str).ok() {
178            potential_paths.push(PathWithPosition {
179                path: stripped.to_owned(),
180                row: original_path.row,
181                column: original_path.column,
182            });
183        }
184        if let Some(stripped) = path_with_position.path.strip_prefix(prefix_str).ok() {
185            potential_paths.push(PathWithPosition {
186                path: stripped.to_owned(),
187                row: path_with_position.row,
188                column: path_with_position.column,
189            });
190        }
191    }
192
193    let insert_both_paths = original_path != path_with_position;
194    potential_paths.insert(0, original_path);
195    if insert_both_paths {
196        potential_paths.insert(1, path_with_position);
197    }
198
199    let mut worktree_paths_to_check = Vec::new();
200    let mut is_cwd_in_worktree = false;
201    let mut open_target = None;
202    'worktree_loop: for worktree in &worktree_candidates {
203        let worktree_root = worktree.read(cx).abs_path();
204        let mut paths_to_check = Vec::with_capacity(potential_paths.len());
205        let relative_cwd = cwd
206            .and_then(|cwd| cwd.strip_prefix(&worktree_root).ok())
207            .and_then(|cwd| RelPath::new(cwd, PathStyle::local()).ok())
208            .and_then(|cwd_stripped| {
209                (cwd_stripped.as_ref() != RelPath::empty()).then(|| {
210                    is_cwd_in_worktree = true;
211                    cwd_stripped
212                })
213            });
214
215        for path_with_position in &potential_paths {
216            let path_to_check = if worktree_root.ends_with(&path_with_position.path) {
217                let root_path_with_position = PathWithPosition {
218                    path: worktree_root.to_path_buf(),
219                    row: path_with_position.row,
220                    column: path_with_position.column,
221                };
222                match worktree.read(cx).root_entry() {
223                    Some(root_entry) => {
224                        open_target = Some(OpenTarget::Worktree(
225                            root_path_with_position,
226                            root_entry.clone(),
227                            #[cfg(any(test, feature = "test-support"))]
228                            OpenTargetFoundBy::WorktreeExact,
229                        ));
230                        break 'worktree_loop;
231                    }
232                    None => root_path_with_position,
233                }
234            } else {
235                PathWithPosition {
236                    path: path_with_position
237                        .path
238                        .strip_prefix(&worktree_root)
239                        .unwrap_or(&path_with_position.path)
240                        .to_owned(),
241                    row: path_with_position.row,
242                    column: path_with_position.column,
243                }
244            };
245
246            let normalized_path = if path_to_check.path.is_relative() {
247                relative_cwd.as_ref().and_then(|relative_cwd| {
248                    let joined = relative_cwd
249                        .as_ref()
250                        .as_std_path()
251                        .join(&path_to_check.path);
252                    normalize_lexically(&joined).ok().and_then(|path| {
253                        RelPath::new(&path, PathStyle::local())
254                            .ok()
255                            .map(std::borrow::Cow::into_owned)
256                    })
257                })
258            } else {
259                None
260            };
261            let original_path = RelPath::new(&path_to_check.path, PathStyle::local()).ok();
262
263            if !worktree.read(cx).is_single_file()
264                && let Some(entry) = normalized_path
265                    .as_ref()
266                    .and_then(|path| worktree.read(cx).entry_for_path(path))
267                    .or_else(|| {
268                        original_path
269                            .as_ref()
270                            .and_then(|path| worktree.read(cx).entry_for_path(path.as_ref()))
271                    })
272            {
273                open_target = Some(OpenTarget::Worktree(
274                    PathWithPosition {
275                        path: worktree.read(cx).absolutize(&entry.path),
276                        row: path_to_check.row,
277                        column: path_to_check.column,
278                    },
279                    entry.clone(),
280                    #[cfg(any(test, feature = "test-support"))]
281                    OpenTargetFoundBy::WorktreeExact,
282                ));
283                break 'worktree_loop;
284            }
285
286            paths_to_check.push(path_to_check);
287        }
288
289        if !paths_to_check.is_empty() {
290            worktree_paths_to_check.push((worktree.clone(), paths_to_check));
291        }
292    }
293
294    if open_target.is_some() {
295        if is_cwd_in_worktree {
296            return Task::ready(open_target);
297        }
298    }
299
300    let project = workspace.read(cx).project().clone();
301    let background_path_checks = background_path_checks.unwrap_or_else(|| {
302        if project.read(cx).is_local() {
303            BackgroundPathChecks::LocalFileSystem
304        } else {
305            BackgroundPathChecks::ProjectPathResolution
306        }
307    });
308
309    let background_resolution_task = match background_path_checks {
310        BackgroundPathChecks::LocalFileSystem => {
311            let fs_paths_to_check =
312                local_paths_to_check(&potential_paths, cwd, &worktree_candidates, cx);
313            let fs = project.read(cx).fs().clone();
314            cx.background_spawn(async move {
315                for mut path_to_check in fs_paths_to_check {
316                    if let Some(fs_path_to_check) = fs.canonicalize(&path_to_check.path).await.ok()
317                        && let Some(metadata) = fs.metadata(&fs_path_to_check).await.ok().flatten()
318                    {
319                        if open_target
320                            .as_ref()
321                            .map(|open_target| open_target.path().path != fs_path_to_check)
322                            .unwrap_or(true)
323                        {
324                            path_to_check.path = fs_path_to_check;
325                            return Some(OpenTarget::Path(
326                                path_to_check,
327                                metadata.is_dir,
328                                #[cfg(any(test, feature = "test-support"))]
329                                OpenTargetFoundBy::BackgroundPathResolution,
330                            ));
331                        }
332
333                        break;
334                    }
335                }
336
337                open_target
338            })
339        }
340        BackgroundPathChecks::ProjectPathResolution => {
341            let paths_to_check = project_paths_to_check(&potential_paths, cwd);
342            cx.spawn(async move |cx| {
343                for mut path_to_check in paths_to_check {
344                    let path = path_to_check.path.to_string_lossy();
345                    let resolve_task = project.update(cx, |project, cx| {
346                        project.resolve_abs_path(path.as_ref(), cx)
347                    });
348
349                    if let Some(resolved_path) = resolve_task.await
350                        && let Some(resolved_abs_path) = {
351                            let is_dir = resolved_path.is_dir();
352                            resolved_path
353                                .into_abs_path()
354                                .map(|resolved_abs_path| (resolved_abs_path, is_dir))
355                        }
356                    {
357                        let (resolved_abs_path, is_dir) = resolved_abs_path;
358                        let resolved_abs_path = PathBuf::from(resolved_abs_path);
359                        if open_target
360                            .as_ref()
361                            .map(|open_target| open_target.path().path != resolved_abs_path)
362                            .unwrap_or(true)
363                        {
364                            path_to_check.path = resolved_abs_path;
365                            return Some(OpenTarget::Path(
366                                path_to_check,
367                                is_dir,
368                                #[cfg(any(test, feature = "test-support"))]
369                                OpenTargetFoundBy::BackgroundPathResolution,
370                            ));
371                        }
372
373                        break;
374                    }
375                }
376
377                open_target
378            })
379        }
380    };
381
382    cx.spawn(async move |cx| {
383        if let Some(open_target) = background_resolution_task.await {
384            return Some(open_target);
385        }
386
387        let worktree_paths_to_check = worktree_paths_to_check
388            .into_iter()
389            .map(|(worktree, paths_to_check)| {
390                let paths_to_check = paths_to_check
391                    .into_iter()
392                    .filter_map(|path_with_position| {
393                        let path = RelPath::new(&path_with_position.path, PathStyle::local())
394                            .ok()?
395                            .into_owned();
396                        Some((path_with_position, path))
397                    })
398                    .collect::<Vec<_>>();
399                (
400                    worktree.read_with(cx, |worktree, _| worktree.snapshot()),
401                    paths_to_check,
402                )
403            })
404            .collect::<Vec<_>>();
405        cx.background_spawn(async move {
406            for (snapshot, paths_to_check) in worktree_paths_to_check {
407                let traversal = snapshot.traverse_from_path(true, true, false, RelPath::empty());
408                for entry in traversal {
409                    if let Some(path_with_position) =
410                        paths_to_check
411                            .iter()
412                            .find_map(|(path_with_position, path)| {
413                                entry.path.ends_with(path).then_some(path_with_position)
414                            })
415                    {
416                        return Some(OpenTarget::Worktree(
417                            PathWithPosition {
418                                path: snapshot.absolutize(&entry.path),
419                                row: path_with_position.row,
420                                column: path_with_position.column,
421                            },
422                            entry.clone(),
423                            #[cfg(any(test, feature = "test-support"))]
424                            OpenTargetFoundBy::WorktreeScan,
425                        ));
426                    }
427                }
428            }
429            None
430        })
431        .await
432    })
433}
434
435fn local_paths_to_check(
436    potential_paths: &[PathWithPosition],
437    cwd: Option<&Path>,
438    worktree_candidates: &[Entity<Worktree>],
439    cx: &App,
440) -> Vec<PathWithPosition> {
441    cwd.iter()
442        .flat_map(|cwd| {
443            potential_paths.iter().filter_map(|path_to_check| {
444                path_to_check.path.is_relative().then(|| PathWithPosition {
445                    path: cwd.join(&path_to_check.path),
446                    row: path_to_check.row,
447                    column: path_to_check.column,
448                })
449            })
450        })
451        .chain(potential_paths.iter().flat_map(|path_to_check| {
452            let mut paths_to_check = Vec::new();
453            let maybe_path = &path_to_check.path;
454            if maybe_path.starts_with("~") {
455                if let Some(home_path) =
456                    maybe_path
457                        .strip_prefix("~")
458                        .ok()
459                        .and_then(|stripped_maybe_path| {
460                            Some(dirs::home_dir()?.join(stripped_maybe_path))
461                        })
462                {
463                    paths_to_check.push(PathWithPosition {
464                        path: home_path,
465                        row: path_to_check.row,
466                        column: path_to_check.column,
467                    });
468                }
469            } else {
470                paths_to_check.push(PathWithPosition {
471                    path: maybe_path.clone(),
472                    row: path_to_check.row,
473                    column: path_to_check.column,
474                });
475                if maybe_path.is_relative() {
476                    for worktree in worktree_candidates {
477                        if !worktree.read(cx).is_single_file() {
478                            paths_to_check.push(PathWithPosition {
479                                path: worktree.read(cx).abs_path().join(maybe_path),
480                                row: path_to_check.row,
481                                column: path_to_check.column,
482                            });
483                        }
484                    }
485                }
486            }
487            paths_to_check
488        }))
489        .collect()
490}
491
492fn project_paths_to_check(
493    potential_paths: &[PathWithPosition],
494    cwd: Option<&Path>,
495) -> Vec<PathWithPosition> {
496    cwd.iter()
497        .flat_map(|cwd| {
498            potential_paths
499                .iter()
500                .filter_map(|path_to_check| normalize_absolute_candidate(cwd, path_to_check))
501        })
502        .chain(potential_paths.iter().filter_map(|path_to_check| {
503            let maybe_path = &path_to_check.path;
504            (maybe_path.starts_with("~") || maybe_path.is_absolute()).then(|| path_to_check.clone())
505        }))
506        .collect()
507}
508
509fn normalize_absolute_candidate(
510    cwd: &Path,
511    path_to_check: &PathWithPosition,
512) -> Option<PathWithPosition> {
513    path_to_check.path.is_relative().then(|| {
514        normalize_lexically(&cwd.join(&path_to_check.path))
515            .ok()
516            .map(|path| PathWithPosition {
517                path,
518                row: path_to_check.row,
519                column: path_to_check.column,
520            })
521    })?
522}
523
Served at tenant.openagents/omega Member data and write actions are omitted.