@@ -105,6 +105,20 @@ impl Document {
105105
106106 None
107107 }
108+
109+ /// Moves the cursor to a 1-based line/char position.
110+ /// A negative line counts backwards from the end
111+ /// of the document, e.g. -1 is the last line.
112+ pub fn cursor_move_to_goto ( & self , goto : Point ) {
113+ let mut tb = self . buffer . borrow_mut ( ) ;
114+ let x = goto. x . saturating_sub ( 1 ) ;
115+ let y = if goto. y < 0 {
116+ tb. logical_line_count ( ) . saturating_add ( goto. y )
117+ } else {
118+ goto. y . saturating_sub ( 1 )
119+ } ;
120+ tb. cursor_move_to_logical ( Point { x, y } ) ;
121+ }
108122}
109123
110124#[ derive( Default ) ]
@@ -281,22 +295,26 @@ impl DocumentManager {
281295}
282296
283297/// Parse a filename in the form of "filename:line:char".
284- /// Returns the position of the first colon and the line/char coordinates.
298+ /// Returns the filename and the [`Document::cursor_move_to_goto`] coordinates.
285299pub fn parse_filename_goto ( path : & Path ) -> ( & Path , Option < Point > ) {
286300 fn parse ( s : & [ u8 ] ) -> Option < CoordType > {
287- if s. is_empty ( ) {
301+ let ( negative, digits) = match s {
302+ [ b'-' , rest @ ..] => ( true , rest) ,
303+ _ => ( false , s) ,
304+ } ;
305+ if digits. is_empty ( ) {
288306 return None ;
289307 }
290308
291309 let mut num: CoordType = 0 ;
292- for & b in s {
310+ for & b in digits {
293311 if !b. is_ascii_digit ( ) {
294312 return None ;
295313 }
296314 let digit = ( b - b'0' ) as CoordType ;
297315 num = num. checked_mul ( 10 ) ?. checked_add ( digit) ?;
298316 }
299- Some ( num)
317+ Some ( if negative { - num } else { num } )
300318 }
301319
302320 fn find_colon_rev ( bytes : & [ u8 ] , offset : usize ) -> Option < usize > {
@@ -315,19 +333,19 @@ pub fn parse_filename_goto(path: &Path) -> (&Path, Option<Point>) {
315333 Some ( last) => last,
316334 None => return ( path, None ) ,
317335 } ;
318- let last = ( last - 1 ) . max ( 0 ) ;
319336 let mut len = colend;
320- let mut goto = Point { x : 0 , y : last } ;
337+ let mut goto = Point { x : 1 , y : last } ;
321338
322- if let Some ( colbeg) = find_colon_rev ( bytes, colend) {
339+ // Counting backwards is only supported for lines,
340+ // so a negative `last` rules out a char position.
341+ if last >= 0
342+ && let Some ( colbeg) = find_colon_rev ( bytes, colend)
323343 // Same here: Don't allow empty filenames.
324- if colbeg != 0
325- && let Some ( first) = parse ( & bytes[ colbeg + 1 ..colend] )
326- {
327- let first = ( first - 1 ) . max ( 0 ) ;
328- len = colbeg;
329- goto = Point { x : last, y : first } ;
330- }
344+ && colbeg != 0
345+ && let Some ( first) = parse ( & bytes[ colbeg + 1 ..colend] )
346+ {
347+ len = colbeg;
348+ goto = Point { x : last, y : first } ;
331349 }
332350
333351 // Strip off the :line:char suffix.
@@ -351,20 +369,24 @@ mod tests {
351369 assert_eq ! ( parse( "123" ) , ( "123" , None ) ) ;
352370 assert_eq ! ( parse( "abc" ) , ( "abc" , None ) ) ;
353371 assert_eq ! ( parse( ":123" ) , ( ":123" , None ) ) ;
354- assert_eq ! ( parse( "abc:123" ) , ( "abc" , Some ( Point { x: 0 , y: 122 } ) ) ) ;
355- assert_eq ! ( parse( "45:123" ) , ( "45" , Some ( Point { x: 0 , y: 122 } ) ) ) ;
356- assert_eq ! ( parse( ":45:123" ) , ( ":45" , Some ( Point { x: 0 , y: 122 } ) ) ) ;
357- assert_eq ! ( parse( "abc:45:123" ) , ( "abc" , Some ( Point { x: 122 , y: 44 } ) ) ) ;
358- assert_eq ! ( parse( "abc:def:123" ) , ( "abc:def" , Some ( Point { x: 0 , y: 122 } ) ) ) ;
359- assert_eq ! ( parse( "1:2:3" ) , ( "1" , Some ( Point { x: 2 , y: 1 } ) ) ) ;
360- assert_eq ! ( parse( "::3" ) , ( ":" , Some ( Point { x: 0 , y: 2 } ) ) ) ;
361- assert_eq ! ( parse( "1::3" ) , ( "1:" , Some ( Point { x: 0 , y: 2 } ) ) ) ;
372+ assert_eq ! ( parse( "abc:123" ) , ( "abc" , Some ( Point { x: 1 , y: 123 } ) ) ) ;
373+ assert_eq ! ( parse( "45:123" ) , ( "45" , Some ( Point { x: 1 , y: 123 } ) ) ) ;
374+ assert_eq ! ( parse( ":45:123" ) , ( ":45" , Some ( Point { x: 1 , y: 123 } ) ) ) ;
375+ assert_eq ! ( parse( "abc:45:123" ) , ( "abc" , Some ( Point { x: 123 , y: 45 } ) ) ) ;
376+ assert_eq ! ( parse( "abc:def:123" ) , ( "abc:def" , Some ( Point { x: 1 , y: 123 } ) ) ) ;
377+ assert_eq ! ( parse( "1:2:3" ) , ( "1" , Some ( Point { x: 3 , y: 2 } ) ) ) ;
378+ assert_eq ! ( parse( "::3" ) , ( ":" , Some ( Point { x: 1 , y: 3 } ) ) ) ;
379+ assert_eq ! ( parse( "1::3" ) , ( "1:" , Some ( Point { x: 1 , y: 3 } ) ) ) ;
362380 assert_eq ! ( parse( "" ) , ( "" , None ) ) ;
363381 assert_eq ! ( parse( ":" ) , ( ":" , None ) ) ;
364382 assert_eq ! ( parse( "::" ) , ( "::" , None ) ) ;
365- assert_eq ! ( parse( "a:1" ) , ( "a" , Some ( Point { x: 0 , y: 0 } ) ) ) ;
383+ assert_eq ! ( parse( "a:1" ) , ( "a" , Some ( Point { x: 1 , y: 1 } ) ) ) ;
366384 assert_eq ! ( parse( "1:a" ) , ( "1:a" , None ) ) ;
367- assert_eq ! ( parse( "file.txt:10" ) , ( "file.txt" , Some ( Point { x: 0 , y: 9 } ) ) ) ;
368- assert_eq ! ( parse( "file.txt:10:5" ) , ( "file.txt" , Some ( Point { x: 4 , y: 9 } ) ) ) ;
385+ assert_eq ! ( parse( "file.txt:10" ) , ( "file.txt" , Some ( Point { x: 1 , y: 10 } ) ) ) ;
386+ assert_eq ! ( parse( "file.txt:10:5" ) , ( "file.txt" , Some ( Point { x: 5 , y: 10 } ) ) ) ;
387+ assert_eq ! ( parse( "file.txt:-1" ) , ( "file.txt" , Some ( Point { x: 1 , y: -1 } ) ) ) ;
388+ assert_eq ! ( parse( "file.txt:-10:5" ) , ( "file.txt" , Some ( Point { x: 5 , y: -10 } ) ) ) ;
389+ assert_eq ! ( parse( "file.txt:10:-5" ) , ( "file.txt:10" , Some ( Point { x: 1 , y: -5 } ) ) ) ;
390+ assert_eq ! ( parse( "file.txt:-" ) , ( "file.txt:-" , None ) ) ;
369391 }
370392}
0 commit comments