1use agent_client_protocol as acp;
2use anyhow::{Result, bail};
3use std::path::PathBuf;
4
5#[derive(Clone, Debug, PartialEq, Eq)]
6pub enum MentionUri {
7 File(PathBuf),
8 Symbol(PathBuf, String),
9 Thread(acp::SessionId),
10 Rule(String),
11}
12
13impl MentionUri {
14 pub fn parse(input: &str) -> Result<Self> {
15 let url = url::Url::parse(input)?;
16 let path = url.path();
17 match url.scheme() {
18 "file" => {
19 if let Some(fragment) = url.fragment() {
20 Ok(Self::Symbol(path.into(), fragment.into()))
21 } else {
22 Ok(Self::File(path.into()))
23 }
24 }
25 "zed" => {
26 if let Some(thread) = path.strip_prefix("/agent/thread/") {
27 Ok(Self::Thread(acp::SessionId(thread.into())))
28 } else if let Some(rule) = path.strip_prefix("/agent/rule/") {
29 Ok(Self::Rule(rule.into()))
30 } else {
31 bail!("invalid zed url: {:?}", input);
32 }
33 }
34 other => bail!("unrecognized scheme {:?}", other),
35 }
36 }
37
38 pub fn name(&self) -> String {
39 match self {
40 MentionUri::File(path) => path.file_name().unwrap().to_string_lossy().into_owned(),
41 MentionUri::Symbol(_path, name) => name.clone(),
42 MentionUri::Thread(thread) => thread.to_string(),
43 MentionUri::Rule(rule) => rule.clone(),
44 }
45 }
46
47 pub fn to_link(&self) -> String {
48 let name = self.name();
49 let uri = self.to_uri();
50 format!("[{name}]({uri})")
51 }
52
53 pub fn to_uri(&self) -> String {
54 match self {
55 MentionUri::File(path) => {
56 format!("file://{}", path.display())
57 }
58 MentionUri::Symbol(path, name) => {
59 format!("file://{}#{}", path.display(), name)
60 }
61 MentionUri::Thread(thread) => {
62 format!("zed:///agent/thread/{}", thread.0)
63 }
64 MentionUri::Rule(rule) => {
65 format!("zed:///agent/rule/{}", rule)
66 }
67 }
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn test_mention_uri_parse_and_display() {
77 // Test file URI
78 let file_uri = "file:///path/to/file.rs";
79 let parsed = MentionUri::parse(file_uri).unwrap();
80 match &parsed {
81 MentionUri::File(path) => assert_eq!(path.to_str().unwrap(), "/path/to/file.rs"),
82 _ => panic!("Expected File variant"),
83 }
84 assert_eq!(parsed.to_uri(), file_uri);
85
86 // Test symbol URI
87 let symbol_uri = "file:///path/to/file.rs#MySymbol";
88 let parsed = MentionUri::parse(symbol_uri).unwrap();
89 match &parsed {
90 MentionUri::Symbol(path, symbol) => {
91 assert_eq!(path.to_str().unwrap(), "/path/to/file.rs");
92 assert_eq!(symbol, "MySymbol");
93 }
94 _ => panic!("Expected Symbol variant"),
95 }
96 assert_eq!(parsed.to_uri(), symbol_uri);
97
98 // Test thread URI
99 let thread_uri = "zed:///agent/thread/session123";
100 let parsed = MentionUri::parse(thread_uri).unwrap();
101 match &parsed {
102 MentionUri::Thread(session_id) => assert_eq!(session_id.0.as_ref(), "session123"),
103 _ => panic!("Expected Thread variant"),
104 }
105 assert_eq!(parsed.to_uri(), thread_uri);
106
107 // Test rule URI
108 let rule_uri = "zed:///agent/rule/my_rule";
109 let parsed = MentionUri::parse(rule_uri).unwrap();
110 match &parsed {
111 MentionUri::Rule(rule) => assert_eq!(rule, "my_rule"),
112 _ => panic!("Expected Rule variant"),
113 }
114 assert_eq!(parsed.to_uri(), rule_uri);
115
116 // Test invalid scheme
117 assert!(MentionUri::parse("http://example.com").is_err());
118
119 // Test invalid zed path
120 assert!(MentionUri::parse("zed:///invalid/path").is_err());
121 }
122}