Fossil

Check-in [e7bc33f080]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Proof-of-concept code that allows markdown-style hyperlinks to appear in check-in comments. Markdown-style hyperlinks are not allowed in ordinary text/x-fossil-wiki for backwards compatibility. This simply change reduces the need to provide full markdown support for check-in comments.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | comment-markdown-links
Files: files | file ages | folders
SHA3-256: e7bc33f0801e47ac9be846ed614d9c29ee850c9f1e2665b6915982055d6cb3da
User & Date: drh 2025-03-04 00:39:34.646
Context
2025-03-04
11:48
Change the rendering option to WIKI_MARKDOWN_SPAN with the idea of eventually supporting all kinds of span-markdown, just not block-markdown. Add support for auto-links. check-in: f80b892178 user: drh tags: comment-markdown-links
00:39
Proof-of-concept code that allows markdown-style hyperlinks to appear in check-in comments. Markdown-style hyperlinks are not allowed in ordinary text/x-fossil-wiki for backwards compatibility. This simply change reduces the need to provide full markdown support for check-in comments. check-in: e7bc33f080 user: drh tags: comment-markdown-links
2025-03-03
23:23
Fix the printf() %W and %!W conversions so that they can both be used during the same Fossil run. check-in: adbb0b9255 user: drh tags: trunk
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/backlink.c.
368
369
370
371
372
373
374






375
376
377
378
379
380
381
382
  }
  bklnk.srcid = srcid;
  assert( ValidBklnk(srctype) );
  assert( ValidMTC(mimetype) );
  bklnk.srctype = srctype;
  bklnk.mtime = mtime;
  if( mimetype==MT_NONE || mimetype==MT_WIKI ){






    wiki_extract_links(zSrc, &bklnk, srctype==BKLNK_COMMENT ? WIKI_INLINE : 0);
  }else if( mimetype==MT_MARKDOWN ){
    markdown_extract_links(zSrc, &bklnk);
  }
}

/*
** COMMAND: test-backlinks







>
>
>
>
>
>
|







368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
  }
  bklnk.srcid = srcid;
  assert( ValidBklnk(srctype) );
  assert( ValidMTC(mimetype) );
  bklnk.srctype = srctype;
  bklnk.mtime = mtime;
  if( mimetype==MT_NONE || mimetype==MT_WIKI ){
    int flags;
    if( srctype==BKLNK_COMMENT ){
      flags = WIKI_INLINE | WIKI_MARKDOWN_LINK;
    }else{
      flags = 0;
    }
    wiki_extract_links(zSrc, &bklnk, flags);
  }else if( mimetype==MT_MARKDOWN ){
    markdown_extract_links(zSrc, &bklnk);
  }
}

/*
** COMMAND: test-backlinks
Changes to src/markdown_html.c.
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
  char zClose[20];

  if( zLink==0 || zLink[0]==0 ){
    zClose[0] = 0;
  }else{
    static const int flags =
       WIKI_NOBADLINKS |
       WIKI_MARKDOWNLINKS
    ;
    wiki_resolve_hyperlink(ob, flags, zLink, zClose, sizeof(zClose), 0, zTitle);
  }
  if( blob_size(content)==0 ){
    if( link ) blob_appendb(ob, link);
  }else{
    blob_appendb(ob, content);







|







813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
  char zClose[20];

  if( zLink==0 || zLink[0]==0 ){
    zClose[0] = 0;
  }else{
    static const int flags =
       WIKI_NOBADLINKS |
       WIKI_MARKDOWN_URL
    ;
    wiki_resolve_hyperlink(ob, flags, zLink, zClose, sizeof(zClose), 0, zTitle);
  }
  if( blob_size(content)==0 ){
    if( link ) blob_appendb(ob, link);
  }else{
    blob_appendb(ob, content);
Changes to src/printf.c.
262
263
264
265
266
267
268

269
270
271
272
273
274
275
    }
    if( db_get_boolean("timeline-plaintext", 0) ){
      wikiFlags |= WIKI_LINKSONLY;
    }
    if( db_get_boolean("timeline-hard-newlines", 0) ){
      wikiFlags |= WIKI_NEWLINE;
    }

  }
  if( altForm2 ){
    /* block markup (ex: <p>, <table>) allowed */
    return  wikiFlags & ~WIKI_NOBLOCK;
  }else{
    /* Do not allow any block format.  Everything in a <span> */
    return wikiFlags;







>







262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
    }
    if( db_get_boolean("timeline-plaintext", 0) ){
      wikiFlags |= WIKI_LINKSONLY;
    }
    if( db_get_boolean("timeline-hard-newlines", 0) ){
      wikiFlags |= WIKI_NEWLINE;
    }
    wikiFlags |= WIKI_MARKDOWN_LINK;
  }
  if( altForm2 ){
    /* block markup (ex: <p>, <table>) allowed */
    return  wikiFlags & ~WIKI_NOBLOCK;
  }else{
    /* Do not allow any block format.  Everything in a <span> */
    return wikiFlags;
Changes to src/wikiformat.c.
28
29
30
31
32
33
34
35
36
37
38

39
40
41
42
43
44
45
#define WIKI_HTMLONLY       0x001  /* HTML markup only.  No wiki */
#define WIKI_INLINE         0x002  /* Do not surround with <p>..</p> */
#define WIKI_NOBLOCK        0x004  /* No block markup of any kind */
#define WIKI_BUTTONS        0x008  /* Allow sub-menu buttons */
#define WIKI_NOBADLINKS     0x010  /* Ignore broken hyperlinks */
#define WIKI_LINKSONLY      0x020  /* No markup.  Only decorate links */
#define WIKI_NEWLINE        0x040  /* Honor \n - break lines at each \n */
#define WIKI_MARKDOWNLINKS  0x080  /* Resolve hyperlinks as in markdown */
#define WIKI_SAFE           0x100  /* Make the result safe for embedding */
#define WIKI_TARGET_BLANK   0x200  /* Hyperlinks go to a new window */
#define WIKI_NOBRACKET      0x400  /* Omit extra [..] around hyperlinks */

#endif


/*
** These are the only markup attributes allowed.
*/
enum allowed_attr_t {







|



>







28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#define WIKI_HTMLONLY       0x001  /* HTML markup only.  No wiki */
#define WIKI_INLINE         0x002  /* Do not surround with <p>..</p> */
#define WIKI_NOBLOCK        0x004  /* No block markup of any kind */
#define WIKI_BUTTONS        0x008  /* Allow sub-menu buttons */
#define WIKI_NOBADLINKS     0x010  /* Ignore broken hyperlinks */
#define WIKI_LINKSONLY      0x020  /* No markup.  Only decorate links */
#define WIKI_NEWLINE        0x040  /* Honor \n - break lines at each \n */
#define WIKI_MARKDOWN_URL   0x080  /* Hyperlink targets as in markdown */
#define WIKI_SAFE           0x100  /* Make the result safe for embedding */
#define WIKI_TARGET_BLANK   0x200  /* Hyperlinks go to a new window */
#define WIKI_NOBRACKET      0x400  /* Omit extra [..] around hyperlinks */
#define WIKI_MARKDOWN_LINK  0x800  /* Markdown link syntax: [display](URL) */
#endif


/*
** These are the only markup attributes allowed.
*/
enum allowed_attr_t {
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
      blob_appendf(pOut, "<a href=\"%R/wiki?name=%T\"%s>", z, zExtra);
    }
  }else if( strlen(zTarget)>=10 && fossil_isdigit(zTarget[0]) && zTarget[4]=='-'
            && db_int(0, "SELECT datetime(%Q) NOT NULL", zTarget) ){
    /* Dates or date-and-times in ISO8610 resolve to a link to the
    ** timeline for that date */
    blob_appendf(pOut, "<a href=\"%R/timeline?c=%T\"%s>", zTarget, zExtra);
  }else if( mFlags & WIKI_MARKDOWNLINKS ){
    /* If none of the above, and if rendering links for markdown, then
    ** create a link to the literal text of the target */
    blob_appendf(pOut, "<a href=\"%h\"%s>", zTarget, zExtra);
  }else if( zOrig && zTarget>=&zOrig[2]
        && zTarget[-1]=='[' && !fossil_isspace(zTarget[-2]) ){
    /* If the hyperlink markup is not preceded by whitespace, then it
    ** is probably a C-language subscript or similar, not really a
    ** hyperlink.  Just ignore it. */
    zTerm = "";







|
|
|







1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
      blob_appendf(pOut, "<a href=\"%R/wiki?name=%T\"%s>", z, zExtra);
    }
  }else if( strlen(zTarget)>=10 && fossil_isdigit(zTarget[0]) && zTarget[4]=='-'
            && db_int(0, "SELECT datetime(%Q) NOT NULL", zTarget) ){
    /* Dates or date-and-times in ISO8610 resolve to a link to the
    ** timeline for that date */
    blob_appendf(pOut, "<a href=\"%R/timeline?c=%T\"%s>", zTarget, zExtra);
  }else if( mFlags & WIKI_MARKDOWN_URL ){
    /* If none of the above, and if rendering link references for markdown,
    ** then create a link to the literal text of the target */
    blob_appendf(pOut, "<a href=\"%h\"%s>", zTarget, zExtra);
  }else if( zOrig && zTarget>=&zOrig[2]
        && zTarget[-1]=='[' && !fossil_isspace(zTarget[-2]) ){
    /* If the hyperlink markup is not preceded by whitespace, then it
    ** is probably a C-language subscript or similar, not really a
    ** hyperlink.  Just ignore it. */
    zTerm = "";
1572
1573
1574
1575
1576
1577
1578

1579
1580
1581
1582
1583
1584
1585
















1586
1587
1588
1589
1590
1591
1592
1593

1594
1595
1596
1597
1598
1599
1600
          blob_append_string(p->pOut, "&amp;");
        }
        break;
      }
      case TOKEN_LINK: {
        char *zTarget;
        char *zDisplay = 0;

        int i, j;
        int savedState;
        char zClose[20];
        char cS1 = 0;
        int iS1 = 0;

        startAutoParagraph(p);
















        zTarget = &z[1];
        for(i=1; z[i] && z[i]!=']'; i++){
          if( z[i]=='|' && zDisplay==0 ){
            zDisplay = &z[i+1];
            for(j=i; j>0 && fossil_isspace(z[j-1]); j--){}
            iS1 = j;
            cS1 = z[j];
            z[j] = 0;

          }
        }
        z[i] = 0;
        if( zDisplay==0 ){
          zDisplay = zTarget + interwiki_removable_prefix(zTarget);
        }else{
          while( fossil_isspace(*zDisplay) ) zDisplay++;







>







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
|
|
|
|
>







1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
          blob_append_string(p->pOut, "&amp;");
        }
        break;
      }
      case TOKEN_LINK: {
        char *zTarget;
        char *zDisplay = 0;
        char *zEnd;
        int i, j;
        int savedState;
        char zClose[20];
        char cS1 = 0;
        int iS1 = 0;

        startAutoParagraph(p);
        if( z[n]=='('
         && (p->state & WIKI_MARKDOWN_LINK)!=0
         && (zEnd = strchr(z+n+1,')'))!=0
        ){
          /* Markdown-style hyperlinks: [display-text](URL) or [](URL) */
          if( n>2 ){
            zDisplay = &z[1];
            z[n] = 0;
          }else{
            zDisplay = 0;
          }
          zTarget = &z[n+1];
          for(i=n+1; z[i] && z[i]!=')' && z[i]!=' '; i++){}
          n = (int)(zEnd - z) + 1;
        }else{
          /* Wiki-style hyperlinks: [URL|display-text] or [URL] */
          zTarget = &z[1];
          for(i=1; z[i] && z[i]!=']'; i++){
            if( z[i]=='|' && zDisplay==0 ){
              zDisplay = &z[i+1];
              for(j=i; j>0 && fossil_isspace(z[j-1]); j--){}
              iS1 = j;
              cS1 = z[j];
              z[j] = 0;
            }
          }
        }
        z[i] = 0;
        if( zDisplay==0 ){
          zDisplay = zTarget + interwiki_removable_prefix(zTarget);
        }else{
          while( fossil_isspace(*zDisplay) ) zDisplay++;
1872
1873
1874
1875
1876
1877
1878

1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892

1893
1894
1895
1896
1897
1898
1899
**
** Options:
**    --buttons        Set the WIKI_BUTTONS flag
**    --dark-pikchr    Render pikchrs in dark mode
**    --htmlonly       Set the WIKI_HTMLONLY flag
**    --inline         Set the WIKI_INLINE flag
**    --linksonly      Set the WIKI_LINKSONLY flag

**    --nobadlinks     Set the WIKI_NOBADLINKS flag
**    --noblock        Set the WIKI_NOBLOCK flag
**    --text            Run the output through html_to_plaintext().
*/
void test_wiki_render(void){
  Blob in, out;
  int flags = 0;
  int bText;
  if( find_option("buttons",0,0)!=0 ) flags |= WIKI_BUTTONS;
  if( find_option("htmlonly",0,0)!=0 ) flags |= WIKI_HTMLONLY;
  if( find_option("linksonly",0,0)!=0 ) flags |= WIKI_LINKSONLY;
  if( find_option("nobadlinks",0,0)!=0 ) flags |= WIKI_NOBADLINKS;
  if( find_option("inline",0,0)!=0 ) flags |= WIKI_INLINE;
  if( find_option("noblock",0,0)!=0 ) flags |= WIKI_NOBLOCK;

  if( find_option("dark-pikchr",0,0)!=0 ){
    pikchr_to_html_add_flags( PIKCHR_PROCESS_DARK_MODE );
  }
  bText = find_option("text",0,0)!=0;
  db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
  verify_all_options();
  if( g.argc!=3 ) usage("FILE");







>


|











>







1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
**
** Options:
**    --buttons        Set the WIKI_BUTTONS flag
**    --dark-pikchr    Render pikchrs in dark mode
**    --htmlonly       Set the WIKI_HTMLONLY flag
**    --inline         Set the WIKI_INLINE flag
**    --linksonly      Set the WIKI_LINKSONLY flag
**    --md-links       Allow markdown link syntax
**    --nobadlinks     Set the WIKI_NOBADLINKS flag
**    --noblock        Set the WIKI_NOBLOCK flag
**    --text           Run the output through html_to_plaintext().
*/
void test_wiki_render(void){
  Blob in, out;
  int flags = 0;
  int bText;
  if( find_option("buttons",0,0)!=0 ) flags |= WIKI_BUTTONS;
  if( find_option("htmlonly",0,0)!=0 ) flags |= WIKI_HTMLONLY;
  if( find_option("linksonly",0,0)!=0 ) flags |= WIKI_LINKSONLY;
  if( find_option("nobadlinks",0,0)!=0 ) flags |= WIKI_NOBADLINKS;
  if( find_option("inline",0,0)!=0 ) flags |= WIKI_INLINE;
  if( find_option("noblock",0,0)!=0 ) flags |= WIKI_NOBLOCK;
  if( find_option("md-links",0,0)!=0 ) flags |= WIKI_MARKDOWN_LINK;
  if( find_option("dark-pikchr",0,0)!=0 ){
    pikchr_to_html_add_flags( PIKCHR_PROCESS_DARK_MODE );
  }
  bText = find_option("text",0,0)!=0;
  db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
  verify_all_options();
  if( g.argc!=3 ) usage("FILE");
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048











2049
2050
2051

2052
2053
2054
2055
2056
2057
2058
    if( wikiHtmlOnly ){
      n = nextRawToken(z, &renderer, &tokenType);
    }else{
      n = nextWikiToken(z, &renderer, &tokenType);
    }
    switch( tokenType ){
      case TOKEN_LINK: {
        char *zTarget;
        int i;












        zTarget = &z[1];
        for(i=0; zTarget[i] && zTarget[i]!='|' && zTarget[i]!=']'; i++){}
        while(i>1 && zTarget[i-1]==' '){ i--; }

        backlink_create(pBklnk, zTarget, i);
        break;
      }
      case TOKEN_MARKUP: {
        const char *zId;
        int iDiv;
        parseMarkup(&markup, z);







|


>
>
>
>
>
>
>
>
>
>
>
|
|
|
>







2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
    if( wikiHtmlOnly ){
      n = nextRawToken(z, &renderer, &tokenType);
    }else{
      n = nextWikiToken(z, &renderer, &tokenType);
    }
    switch( tokenType ){
      case TOKEN_LINK: {
        char *zTarget, *zEnd;
        int i;

        if( z[n]=='('
         && (flags & WIKI_MARKDOWN_LINK)!=0
         && (zEnd = strchr(z+n+1,')'))!=0
        ){
          /* Markdown-style hyperlinks: [display-text](URL) or [](URL) */
          z += n+1;
          zTarget = z;
          for(i=1; z[i] && z[i]!=')' && z[i]!=' '; i++){}
          n = (int)(zEnd - z) + 1;
        }else{
          /* Wiki-style hyperlinks: [URL|display-text] or [URL] */
          zTarget = &z[1];
          for(i=0; zTarget[i] && zTarget[i]!='|' && zTarget[i]!=']'; i++){}
          while(i>1 && zTarget[i-1]==' '){ i--; }
        }
        backlink_create(pBklnk, zTarget, i);
        break;
      }
      case TOKEN_MARKUP: {
        const char *zId;
        int iDiv;
        parseMarkup(&markup, z);