Skip to repository content523 lines · 19.0 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T04:59:11.188Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
path_link.rs
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