@@ -5,7 +5,7 @@ use prompt_store::{PromptId, UserPromptId};
5
5
use serde:: { Deserialize , Serialize } ;
6
6
use std:: {
7
7
fmt,
8
- ops:: Range ,
8
+ ops:: RangeInclusive ,
9
9
path:: { Path , PathBuf } ,
10
10
str:: FromStr ,
11
11
} ;
@@ -17,13 +17,14 @@ pub enum MentionUri {
17
17
File {
18
18
abs_path : PathBuf ,
19
19
} ,
20
+ PastedImage ,
20
21
Directory {
21
22
abs_path : PathBuf ,
22
23
} ,
23
24
Symbol {
24
- path : PathBuf ,
25
+ abs_path : PathBuf ,
25
26
name : String ,
26
- line_range : Range < u32 > ,
27
+ line_range : RangeInclusive < u32 > ,
27
28
} ,
28
29
Thread {
29
30
id : acp:: SessionId ,
@@ -38,8 +39,9 @@ pub enum MentionUri {
38
39
name : String ,
39
40
} ,
40
41
Selection {
41
- path : PathBuf ,
42
- line_range : Range < u32 > ,
42
+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
43
+ abs_path : Option < PathBuf > ,
44
+ line_range : RangeInclusive < u32 > ,
43
45
} ,
44
46
Fetch {
45
47
url : Url ,
@@ -48,36 +50,44 @@ pub enum MentionUri {
48
50
49
51
impl MentionUri {
50
52
pub fn parse ( input : & str ) -> Result < Self > {
53
+ fn parse_line_range ( fragment : & str ) -> Result < RangeInclusive < u32 > > {
54
+ let range = fragment
55
+ . strip_prefix ( "L" )
56
+ . context ( "Line range must start with \" L\" " ) ?;
57
+ let ( start, end) = range
58
+ . split_once ( ":" )
59
+ . context ( "Line range must use colon as separator" ) ?;
60
+ let range = start
61
+ . parse :: < u32 > ( )
62
+ . context ( "Parsing line range start" ) ?
63
+ . checked_sub ( 1 )
64
+ . context ( "Line numbers should be 1-based" ) ?
65
+ ..=end
66
+ . parse :: < u32 > ( )
67
+ . context ( "Parsing line range end" ) ?
68
+ . checked_sub ( 1 )
69
+ . context ( "Line numbers should be 1-based" ) ?;
70
+ Ok ( range)
71
+ }
72
+
51
73
let url = url:: Url :: parse ( input) ?;
52
74
let path = url. path ( ) ;
53
75
match url. scheme ( ) {
54
76
"file" => {
55
77
let path = url. to_file_path ( ) . ok ( ) . context ( "Extracting file path" ) ?;
56
78
if let Some ( fragment) = url. fragment ( ) {
57
- let range = fragment
58
- . strip_prefix ( "L" )
59
- . context ( "Line range must start with \" L\" " ) ?;
60
- let ( start, end) = range
61
- . split_once ( ":" )
62
- . context ( "Line range must use colon as separator" ) ?;
63
- let line_range = start
64
- . parse :: < u32 > ( )
65
- . context ( "Parsing line range start" ) ?
66
- . checked_sub ( 1 )
67
- . context ( "Line numbers should be 1-based" ) ?
68
- ..end
69
- . parse :: < u32 > ( )
70
- . context ( "Parsing line range end" ) ?
71
- . checked_sub ( 1 )
72
- . context ( "Line numbers should be 1-based" ) ?;
79
+ let line_range = parse_line_range ( fragment) ?;
73
80
if let Some ( name) = single_query_param ( & url, "symbol" ) ? {
74
81
Ok ( Self :: Symbol {
75
82
name,
76
- path,
83
+ abs_path : path,
77
84
line_range,
78
85
} )
79
86
} else {
80
- Ok ( Self :: Selection { path, line_range } )
87
+ Ok ( Self :: Selection {
88
+ abs_path : Some ( path) ,
89
+ line_range,
90
+ } )
81
91
}
82
92
} else if input. ends_with ( "/" ) {
83
93
Ok ( Self :: Directory { abs_path : path } )
@@ -105,6 +115,17 @@ impl MentionUri {
105
115
id : rule_id. into ( ) ,
106
116
name,
107
117
} )
118
+ } else if path. starts_with ( "/agent/pasted-image" ) {
119
+ Ok ( Self :: PastedImage )
120
+ } else if path. starts_with ( "/agent/untitled-buffer" ) {
121
+ let fragment = url
122
+ . fragment ( )
123
+ . context ( "Missing fragment for untitled buffer selection" ) ?;
124
+ let line_range = parse_line_range ( fragment) ?;
125
+ Ok ( Self :: Selection {
126
+ abs_path : None ,
127
+ line_range,
128
+ } )
108
129
} else {
109
130
bail ! ( "invalid zed url: {:?}" , input) ;
110
131
}
@@ -121,13 +142,16 @@ impl MentionUri {
121
142
. unwrap_or_default ( )
122
143
. to_string_lossy ( )
123
144
. into_owned ( ) ,
145
+ MentionUri :: PastedImage => "Image" . to_string ( ) ,
124
146
MentionUri :: Symbol { name, .. } => name. clone ( ) ,
125
147
MentionUri :: Thread { name, .. } => name. clone ( ) ,
126
148
MentionUri :: TextThread { name, .. } => name. clone ( ) ,
127
149
MentionUri :: Rule { name, .. } => name. clone ( ) ,
128
150
MentionUri :: Selection {
129
- path, line_range, ..
130
- } => selection_name ( path, line_range) ,
151
+ abs_path : path,
152
+ line_range,
153
+ ..
154
+ } => selection_name ( path. as_deref ( ) , line_range) ,
131
155
MentionUri :: Fetch { url } => url. to_string ( ) ,
132
156
}
133
157
}
@@ -137,6 +161,7 @@ impl MentionUri {
137
161
MentionUri :: File { abs_path } => {
138
162
FileIcons :: get_icon ( abs_path, cx) . unwrap_or_else ( || IconName :: File . path ( ) . into ( ) )
139
163
}
164
+ MentionUri :: PastedImage => IconName :: Image . path ( ) . into ( ) ,
140
165
MentionUri :: Directory { .. } => FileIcons :: get_folder_icon ( false , cx)
141
166
. unwrap_or_else ( || IconName :: Folder . path ( ) . into ( ) ) ,
142
167
MentionUri :: Symbol { .. } => IconName :: Code . path ( ) . into ( ) ,
@@ -157,29 +182,40 @@ impl MentionUri {
157
182
MentionUri :: File { abs_path } => {
158
183
Url :: from_file_path ( abs_path) . expect ( "mention path should be absolute" )
159
184
}
185
+ MentionUri :: PastedImage => Url :: parse ( "zed:///agent/pasted-image" ) . unwrap ( ) ,
160
186
MentionUri :: Directory { abs_path } => {
161
187
Url :: from_directory_path ( abs_path) . expect ( "mention path should be absolute" )
162
188
}
163
189
MentionUri :: Symbol {
164
- path ,
190
+ abs_path ,
165
191
name,
166
192
line_range,
167
193
} => {
168
- let mut url = Url :: from_file_path ( path) . expect ( "mention path should be absolute" ) ;
194
+ let mut url =
195
+ Url :: from_file_path ( abs_path) . expect ( "mention path should be absolute" ) ;
169
196
url. query_pairs_mut ( ) . append_pair ( "symbol" , name) ;
170
197
url. set_fragment ( Some ( & format ! (
171
198
"L{}:{}" ,
172
- line_range. start + 1 ,
173
- line_range. end + 1
199
+ line_range. start( ) + 1 ,
200
+ line_range. end( ) + 1
174
201
) ) ) ;
175
202
url
176
203
}
177
- MentionUri :: Selection { path, line_range } => {
178
- let mut url = Url :: from_file_path ( path) . expect ( "mention path should be absolute" ) ;
204
+ MentionUri :: Selection {
205
+ abs_path : path,
206
+ line_range,
207
+ } => {
208
+ let mut url = if let Some ( path) = path {
209
+ Url :: from_file_path ( path) . expect ( "mention path should be absolute" )
210
+ } else {
211
+ let mut url = Url :: parse ( "zed:///" ) . unwrap ( ) ;
212
+ url. set_path ( "/agent/untitled-buffer" ) ;
213
+ url
214
+ } ;
179
215
url. set_fragment ( Some ( & format ! (
180
216
"L{}:{}" ,
181
- line_range. start + 1 ,
182
- line_range. end + 1
217
+ line_range. start( ) + 1 ,
218
+ line_range. end( ) + 1
183
219
) ) ) ;
184
220
url
185
221
}
@@ -191,7 +227,10 @@ impl MentionUri {
191
227
}
192
228
MentionUri :: TextThread { path, name } => {
193
229
let mut url = Url :: parse ( "zed:///" ) . unwrap ( ) ;
194
- url. set_path ( & format ! ( "/agent/text-thread/{}" , path. to_string_lossy( ) ) ) ;
230
+ url. set_path ( & format ! (
231
+ "/agent/text-thread/{}" ,
232
+ path. to_string_lossy( ) . trim_start_matches( '/' )
233
+ ) ) ;
195
234
url. query_pairs_mut ( ) . append_pair ( "name" , name) ;
196
235
url
197
236
}
@@ -237,12 +276,14 @@ fn single_query_param(url: &Url, name: &'static str) -> Result<Option<String>> {
237
276
}
238
277
}
239
278
240
- pub fn selection_name ( path : & Path , line_range : & Range < u32 > ) -> String {
279
+ pub fn selection_name ( path : Option < & Path > , line_range : & RangeInclusive < u32 > ) -> String {
241
280
format ! (
242
281
"{} ({}:{})" ,
243
- path. file_name( ) . unwrap_or_default( ) . display( ) ,
244
- line_range. start + 1 ,
245
- line_range. end + 1
282
+ path. and_then( |path| path. file_name( ) )
283
+ . unwrap_or( "Untitled" . as_ref( ) )
284
+ . display( ) ,
285
+ * line_range. start( ) + 1 ,
286
+ * line_range. end( ) + 1
246
287
)
247
288
}
248
289
@@ -302,14 +343,14 @@ mod tests {
302
343
let parsed = MentionUri :: parse ( symbol_uri) . unwrap ( ) ;
303
344
match & parsed {
304
345
MentionUri :: Symbol {
305
- path,
346
+ abs_path : path,
306
347
name,
307
348
line_range,
308
349
} => {
309
350
assert_eq ! ( path. to_str( ) . unwrap( ) , path!( "/path/to/file.rs" ) ) ;
310
351
assert_eq ! ( name, "MySymbol" ) ;
311
- assert_eq ! ( line_range. start, 9 ) ;
312
- assert_eq ! ( line_range. end, 19 ) ;
352
+ assert_eq ! ( line_range. start( ) , & 9 ) ;
353
+ assert_eq ! ( line_range. end( ) , & 19 ) ;
313
354
}
314
355
_ => panic ! ( "Expected Symbol variant" ) ,
315
356
}
@@ -321,16 +362,39 @@ mod tests {
321
362
let selection_uri = uri ! ( "file:///path/to/file.rs#L5:15" ) ;
322
363
let parsed = MentionUri :: parse ( selection_uri) . unwrap ( ) ;
323
364
match & parsed {
324
- MentionUri :: Selection { path, line_range } => {
325
- assert_eq ! ( path. to_str( ) . unwrap( ) , path!( "/path/to/file.rs" ) ) ;
326
- assert_eq ! ( line_range. start, 4 ) ;
327
- assert_eq ! ( line_range. end, 14 ) ;
365
+ MentionUri :: Selection {
366
+ abs_path : path,
367
+ line_range,
368
+ } => {
369
+ assert_eq ! (
370
+ path. as_ref( ) . unwrap( ) . to_str( ) . unwrap( ) ,
371
+ path!( "/path/to/file.rs" )
372
+ ) ;
373
+ assert_eq ! ( line_range. start( ) , & 4 ) ;
374
+ assert_eq ! ( line_range. end( ) , & 14 ) ;
328
375
}
329
376
_ => panic ! ( "Expected Selection variant" ) ,
330
377
}
331
378
assert_eq ! ( parsed. to_uri( ) . to_string( ) , selection_uri) ;
332
379
}
333
380
381
+ #[ test]
382
+ fn test_parse_untitled_selection_uri ( ) {
383
+ let selection_uri = uri ! ( "zed:///agent/untitled-buffer#L1:10" ) ;
384
+ let parsed = MentionUri :: parse ( selection_uri) . unwrap ( ) ;
385
+ match & parsed {
386
+ MentionUri :: Selection {
387
+ abs_path : None ,
388
+ line_range,
389
+ } => {
390
+ assert_eq ! ( line_range. start( ) , & 0 ) ;
391
+ assert_eq ! ( line_range. end( ) , & 9 ) ;
392
+ }
393
+ _ => panic ! ( "Expected Selection variant without path" ) ,
394
+ }
395
+ assert_eq ! ( parsed. to_uri( ) . to_string( ) , selection_uri) ;
396
+ }
397
+
334
398
#[ test]
335
399
fn test_parse_thread_uri ( ) {
336
400
let thread_uri = "zed:///agent/thread/session123?name=Thread+name" ;
0 commit comments