@@ -1237,7 +1237,27 @@ pub(crate) fn plain_text_summary(md: &str, link_names: &[RenderedLink]) -> Strin
12371237pub ( crate ) struct MarkdownLink {
12381238 pub kind : LinkType ,
12391239 pub link : String ,
1240- pub range : Range < usize > ,
1240+ pub range : MarkdownLinkRange ,
1241+ }
1242+
1243+ #[ derive( Clone , Debug ) ]
1244+ pub ( crate ) enum MarkdownLinkRange {
1245+ /// Normally, markdown link warnings point only at the destination.
1246+ Destination ( Range < usize > ) ,
1247+ /// In some cases, it's not possible to point at the destination.
1248+ /// Usually, this happens because backslashes `\\` are used.
1249+ /// When that happens, point at the whole link, and don't provide structured suggestions.
1250+ WholeLink ( Range < usize > ) ,
1251+ }
1252+
1253+ impl MarkdownLinkRange {
1254+ /// Extracts the inner range.
1255+ pub fn inner_range ( & self ) -> & Range < usize > {
1256+ match self {
1257+ MarkdownLinkRange :: Destination ( range) => range,
1258+ MarkdownLinkRange :: WholeLink ( range) => range,
1259+ }
1260+ }
12411261}
12421262
12431263pub ( crate ) fn markdown_links < R > (
@@ -1257,16 +1277,17 @@ pub(crate) fn markdown_links<R>(
12571277 if md_start <= s_start && s_end <= md_end {
12581278 let start = s_start. offset_from ( md_start) as usize ;
12591279 let end = s_end. offset_from ( md_start) as usize ;
1260- start..end
1280+ MarkdownLinkRange :: Destination ( start..end)
12611281 } else {
1262- fallback
1282+ MarkdownLinkRange :: WholeLink ( fallback)
12631283 }
12641284 } ;
12651285
12661286 let span_for_link = |link : & CowStr < ' _ > , span : Range < usize > | {
12671287 // For diagnostics, we want to underline the link's definition but `span` will point at
12681288 // where the link is used. This is a problem for reference-style links, where the definition
12691289 // is separate from the usage.
1290+
12701291 match link {
12711292 // `Borrowed` variant means the string (the link's destination) may come directly from
12721293 // the markdown text and we can locate the original link destination.
@@ -1275,8 +1296,80 @@ pub(crate) fn markdown_links<R>(
12751296 CowStr :: Borrowed ( s) => locate ( s, span) ,
12761297
12771298 // For anything else, we can only use the provided range.
1278- CowStr :: Boxed ( _) | CowStr :: Inlined ( _) => span,
1299+ CowStr :: Boxed ( _) | CowStr :: Inlined ( _) => MarkdownLinkRange :: WholeLink ( span) ,
1300+ }
1301+ } ;
1302+
1303+ let span_for_offset_backward = |span : Range < usize > , open : u8 , close : u8 | {
1304+ let mut open_brace = !0 ;
1305+ let mut close_brace = !0 ;
1306+ for ( i, b) in md. as_bytes ( ) [ span. clone ( ) ] . iter ( ) . copied ( ) . enumerate ( ) . rev ( ) {
1307+ let i = i + span. start ;
1308+ if b == close {
1309+ close_brace = i;
1310+ break ;
1311+ }
1312+ }
1313+ if close_brace < span. start || close_brace >= span. end {
1314+ return MarkdownLinkRange :: WholeLink ( span) ;
1315+ }
1316+ let mut nesting = 1 ;
1317+ for ( i, b) in md. as_bytes ( ) [ span. start ..close_brace] . iter ( ) . copied ( ) . enumerate ( ) . rev ( ) {
1318+ let i = i + span. start ;
1319+ if b == close {
1320+ nesting += 1 ;
1321+ }
1322+ if b == open {
1323+ nesting -= 1 ;
1324+ }
1325+ if nesting == 0 {
1326+ open_brace = i;
1327+ break ;
1328+ }
1329+ }
1330+ assert ! ( open_brace != close_brace) ;
1331+ if open_brace < span. start || open_brace >= span. end {
1332+ return MarkdownLinkRange :: WholeLink ( span) ;
1333+ }
1334+ // do not actually include braces in the span
1335+ let range = ( open_brace + 1 ) ..close_brace;
1336+ MarkdownLinkRange :: Destination ( range. clone ( ) )
1337+ } ;
1338+
1339+ let span_for_offset_forward = |span : Range < usize > , open : u8 , close : u8 | {
1340+ let mut open_brace = !0 ;
1341+ let mut close_brace = !0 ;
1342+ for ( i, b) in md. as_bytes ( ) [ span. clone ( ) ] . iter ( ) . copied ( ) . enumerate ( ) {
1343+ let i = i + span. start ;
1344+ if b == open {
1345+ open_brace = i;
1346+ break ;
1347+ }
1348+ }
1349+ if open_brace < span. start || open_brace >= span. end {
1350+ return MarkdownLinkRange :: WholeLink ( span) ;
12791351 }
1352+ let mut nesting = 0 ;
1353+ for ( i, b) in md. as_bytes ( ) [ open_brace..span. end ] . iter ( ) . copied ( ) . enumerate ( ) {
1354+ let i = i + open_brace;
1355+ if b == close {
1356+ nesting -= 1 ;
1357+ }
1358+ if b == open {
1359+ nesting += 1 ;
1360+ }
1361+ if nesting == 0 {
1362+ close_brace = i;
1363+ break ;
1364+ }
1365+ }
1366+ assert ! ( open_brace != close_brace) ;
1367+ if open_brace < span. start || open_brace >= span. end {
1368+ return MarkdownLinkRange :: WholeLink ( span) ;
1369+ }
1370+ // do not actually include braces in the span
1371+ let range = ( open_brace + 1 ) ..close_brace;
1372+ MarkdownLinkRange :: Destination ( range. clone ( ) )
12801373 } ;
12811374
12821375 Parser :: new_with_broken_link_callback (
@@ -1287,11 +1380,20 @@ pub(crate) fn markdown_links<R>(
12871380 . into_offset_iter ( )
12881381 . filter_map ( |( event, span) | match event {
12891382 Event :: Start ( Tag :: Link ( link_type, dest, _) ) if may_be_doc_link ( link_type) => {
1290- preprocess_link ( MarkdownLink {
1291- kind : link_type,
1292- range : span_for_link ( & dest, span) ,
1293- link : dest. into_string ( ) ,
1294- } )
1383+ let range = match link_type {
1384+ // Link is pulled from the link itself.
1385+ LinkType :: ReferenceUnknown | LinkType :: ShortcutUnknown => {
1386+ span_for_offset_backward ( span, b'[' , b']' )
1387+ }
1388+ LinkType :: CollapsedUnknown => span_for_offset_forward ( span, b'[' , b']' ) ,
1389+ LinkType :: Inline => span_for_offset_backward ( span, b'(' , b')' ) ,
1390+ // Link is pulled from elsewhere in the document.
1391+ LinkType :: Reference | LinkType :: Collapsed | LinkType :: Shortcut => {
1392+ span_for_link ( & dest, span)
1393+ }
1394+ LinkType :: Autolink | LinkType :: Email => unreachable ! ( ) ,
1395+ } ;
1396+ preprocess_link ( MarkdownLink { kind : link_type, range, link : dest. into_string ( ) } )
12951397 }
12961398 _ => None ,
12971399 } )
0 commit comments