@@ -1387,12 +1387,19 @@ fn parseHoverFileUri(arena: std.mem.Allocator, url: []const u8) ?struct { path:
13871387 const prefix = "file://" ;
13881388 if (! std .ascii .startsWithIgnoreCase (url , prefix )) return null ;
13891389
1390- var path_part = url [prefix .len .. ];
1390+ // Everything up to the `#L<line>[:C<col>]` fragment, if any, is the URI itself — kept
1391+ // whole (still `file://`-prefixed) so it can go through `UriUtil.uriToPath` below rather
1392+ // than being percent-decoded by hand here. Hand-decoding used to skip the Windows
1393+ // drive-letter fixup `uriToPath` applies (`file:///C:/...` -> `C:\...`, not the
1394+ // `/C:/...` mess a plain percent-decode leaves you with), which is why "Go to" links
1395+ // inside a hover popup — unlike plain Ctrl+click goto-definition, which already went
1396+ // through `uriToPath` — used to fail on Windows with `error.BadPathName`.
1397+ var uri_part = url ;
13911398 var line_1based : u32 = 0 ;
13921399 var character_1based : u32 = 0 ;
1393- if (std .mem .indexOfScalar (u8 , path_part , '#' )) | hash | {
1394- const frag = path_part [hash + 1 .. ];
1395- path_part = path_part [0.. hash ];
1400+ if (std .mem .indexOfScalar (u8 , url , '#' )) | hash | {
1401+ const frag = url [hash + 1 .. ];
1402+ uri_part = url [0.. hash ];
13961403 if (frag .len >= 2 and (frag [0 ] == 'L' or frag [0 ] == 'l' )) {
13971404 var i : usize = 1 ;
13981405 var line : u32 = 0 ;
@@ -1411,23 +1418,7 @@ fn parseHoverFileUri(arena: std.mem.Allocator, url: []const u8) ?struct { path:
14111418 }
14121419 }
14131420
1414- var out : std .ArrayListUnmanaged (u8 ) = .empty ;
1415- var i : usize = 0 ;
1416- while (i < path_part .len ) {
1417- if (path_part [i ] == '%' and i + 2 < path_part .len ) {
1418- const byte = std .fmt .parseInt (u8 , path_part [i + 1 .. i + 3 ], 16 ) catch {
1419- out .append (arena , path_part [i ]) catch return null ;
1420- i += 1 ;
1421- continue ;
1422- };
1423- out .append (arena , byte ) catch return null ;
1424- i += 3 ;
1425- } else {
1426- out .append (arena , path_part [i ]) catch return null ;
1427- i += 1 ;
1428- }
1429- }
1430- const path = out .toOwnedSlice (arena ) catch return null ;
1421+ const path = core .lsp .UriUtil .uriToPath (arena , uri_part ) catch return null ;
14311422 return .{ .path = path , .line_1based = line_1based , .character_1based = character_1based };
14321423}
14331424
0 commit comments