diff --git a/extensions/CMakeLists.txt b/extensions/CMakeLists.txt index 87d70acc4..4881153eb 100644 --- a/extensions/CMakeLists.txt +++ b/extensions/CMakeLists.txt @@ -10,6 +10,7 @@ set(LIBRARY_SOURCES ext_scanners.c ext_scanners.re ext_scanners.h + tasklist.c ) include_directories( diff --git a/extensions/core-extensions.c b/extensions/core-extensions.c index e436a5d15..846e2bc2b 100644 --- a/extensions/core-extensions.c +++ b/extensions/core-extensions.c @@ -3,6 +3,7 @@ #include "strikethrough.h" #include "table.h" #include "tagfilter.h" +#include "tasklist.h" #include "registry.h" #include "plugin.h" @@ -12,6 +13,7 @@ static int core_extensions_registration(cmark_plugin *plugin) { create_strikethrough_extension()); cmark_plugin_register_syntax_extension(plugin, create_autolink_extension()); cmark_plugin_register_syntax_extension(plugin, create_tagfilter_extension()); + cmark_plugin_register_syntax_extension(plugin, create_tasklist_extension()); return 1; } diff --git a/extensions/ext_scanners.c b/extensions/ext_scanners.c index 2590cea6b..c3de227ae 100644 --- a/extensions/ext_scanners.c +++ b/extensions/ext_scanners.c @@ -774,3 +774,386 @@ bufsize_t _scan_table_row_end(const unsigned char *p) { goto yy70; } } +bufsize_t _scan_tasklist(const unsigned char *p) { + const unsigned char *marker = NULL; + const unsigned char *start = p; + + { + unsigned char yych; + static const unsigned char yybm[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 64, 64, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + yych = *(marker = p); + if (yych <= '/') { + if (yych <= 0x1F) { + if (yych <= '\t') { + if (yych <= 0x08) + goto yy83; + goto yy84; + } else { + if (yych <= '\n') + goto yy82; + if (yych <= '\f') + goto yy84; + goto yy83; + } + } else { + if (yych <= '+') { + if (yych <= ' ') + goto yy84; + if (yych <= ')') + goto yy83; + goto yy85; + } else { + if (yych == '-') + goto yy85; + goto yy83; + } + } + } else { + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= '9') + goto yy86; + if (yych <= 0x7F) + goto yy83; + } else { + if (yych <= 0xDF) + goto yy87; + if (yych <= 0xE0) + goto yy89; + goto yy90; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy91; + if (yych <= 0xEF) + goto yy90; + goto yy92; + } else { + if (yych <= 0xF3) + goto yy93; + if (yych <= 0xF4) + goto yy94; + } + } + } + yy82 : { return 0; } + yy83: + ++p; + goto yy82; + yy84: + yych = *(marker = ++p); + if (yybm[0 + yych] & 64) { + goto yy95; + } + if (yych <= ',') { + if (yych <= ')') + goto yy82; + if (yych <= '+') + goto yy97; + goto yy82; + } else { + if (yych <= '-') + goto yy97; + if (yych <= '/') + goto yy82; + if (yych <= '9') + goto yy98; + goto yy82; + } + yy85: + yych = *(marker = ++p); + if (yych <= '\n') { + if (yych == '\t') + goto yy99; + goto yy82; + } else { + if (yych <= '\f') + goto yy99; + if (yych == ' ') + goto yy99; + goto yy82; + } + yy86: + yych = *(marker = ++p); + if (yych <= 0x1F) { + if (yych <= '\t') { + if (yych <= 0x08) + goto yy102; + goto yy97; + } else { + if (yych <= '\n') + goto yy82; + if (yych <= '\f') + goto yy97; + goto yy102; + } + } else { + if (yych <= 0x7F) { + if (yych <= ' ') + goto yy97; + goto yy102; + } else { + if (yych <= 0xC1) + goto yy82; + if (yych <= 0xF4) + goto yy102; + goto yy82; + } + } + yy87: + yych = *++p; + if (yych <= 0x7F) + goto yy88; + if (yych <= 0xBF) + goto yy83; + yy88: + p = marker; + goto yy82; + yy89: + yych = *++p; + if (yych <= 0x9F) + goto yy88; + if (yych <= 0xBF) + goto yy87; + goto yy88; + yy90: + yych = *++p; + if (yych <= 0x7F) + goto yy88; + if (yych <= 0xBF) + goto yy87; + goto yy88; + yy91: + yych = *++p; + if (yych <= 0x7F) + goto yy88; + if (yych <= 0x9F) + goto yy87; + goto yy88; + yy92: + yych = *++p; + if (yych <= 0x8F) + goto yy88; + if (yych <= 0xBF) + goto yy90; + goto yy88; + yy93: + yych = *++p; + if (yych <= 0x7F) + goto yy88; + if (yych <= 0xBF) + goto yy90; + goto yy88; + yy94: + yych = *++p; + if (yych <= 0x7F) + goto yy88; + if (yych <= 0x8F) + goto yy90; + goto yy88; + yy95: + yych = *++p; + if (yybm[0 + yych] & 64) { + goto yy95; + } + if (yych <= ',') { + if (yych <= ')') + goto yy88; + if (yych >= ',') + goto yy88; + } else { + if (yych <= '-') + goto yy97; + if (yych <= '/') + goto yy88; + if (yych <= '9') + goto yy98; + goto yy88; + } + yy97: + yych = *++p; + if (yych == '[') + goto yy88; + goto yy100; + yy98: + yych = *++p; + if (yych <= '\n') { + if (yych == '\t') + goto yy97; + goto yy102; + } else { + if (yych <= '\f') + goto yy97; + if (yych == ' ') + goto yy97; + goto yy102; + } + yy99: + yych = *++p; + yy100: + if (yych <= '\f') { + if (yych == '\t') + goto yy99; + if (yych <= '\n') + goto yy88; + goto yy99; + } else { + if (yych <= ' ') { + if (yych <= 0x1F) + goto yy88; + goto yy99; + } else { + if (yych == '[') + goto yy110; + goto yy88; + } + } + yy101: + yych = *++p; + yy102: + if (yybm[0 + yych] & 128) { + goto yy101; + } + if (yych <= 0xC1) { + if (yych <= '\f') { + if (yych <= 0x08) + goto yy97; + if (yych == '\n') + goto yy88; + goto yy99; + } else { + if (yych == ' ') + goto yy99; + if (yych <= 0x7F) + goto yy97; + goto yy88; + } + } else { + if (yych <= 0xED) { + if (yych <= 0xDF) + goto yy103; + if (yych <= 0xE0) + goto yy104; + if (yych <= 0xEC) + goto yy105; + goto yy106; + } else { + if (yych <= 0xF0) { + if (yych <= 0xEF) + goto yy105; + goto yy107; + } else { + if (yych <= 0xF3) + goto yy108; + if (yych <= 0xF4) + goto yy109; + goto yy88; + } + } + } + yy103: + yych = *++p; + if (yych <= 0x7F) + goto yy88; + if (yych <= 0xBF) + goto yy97; + goto yy88; + yy104: + yych = *++p; + if (yych <= 0x9F) + goto yy88; + if (yych <= 0xBF) + goto yy103; + goto yy88; + yy105: + yych = *++p; + if (yych <= 0x7F) + goto yy88; + if (yych <= 0xBF) + goto yy103; + goto yy88; + yy106: + yych = *++p; + if (yych <= 0x7F) + goto yy88; + if (yych <= 0x9F) + goto yy103; + goto yy88; + yy107: + yych = *++p; + if (yych <= 0x8F) + goto yy88; + if (yych <= 0xBF) + goto yy105; + goto yy88; + yy108: + yych = *++p; + if (yych <= 0x7F) + goto yy88; + if (yych <= 0xBF) + goto yy105; + goto yy88; + yy109: + yych = *++p; + if (yych <= 0x7F) + goto yy88; + if (yych <= 0x8F) + goto yy105; + goto yy88; + yy110: + yych = *++p; + if (yych <= 'W') { + if (yych != ' ') + goto yy88; + } else { + if (yych <= 'X') + goto yy111; + if (yych != 'x') + goto yy88; + } + yy111: + yych = *++p; + if (yych != ']') + goto yy88; + yych = *++p; + if (yych <= '\n') { + if (yych != '\t') + goto yy88; + } else { + if (yych <= '\f') + goto yy113; + if (yych != ' ') + goto yy88; + } + yy113: + yych = *++p; + if (yych <= '\n') { + if (yych == '\t') + goto yy113; + } else { + if (yych <= '\f') + goto yy113; + if (yych == ' ') + goto yy113; + } + { return (bufsize_t)(p - start); } + } +} diff --git a/extensions/ext_scanners.h b/extensions/ext_scanners.h index 3bfe586c1..6dd4a725d 100644 --- a/extensions/ext_scanners.h +++ b/extensions/ext_scanners.h @@ -11,11 +11,13 @@ bufsize_t _scan_table_start(const unsigned char *p); bufsize_t _scan_table_cell(const unsigned char *p); bufsize_t _scan_table_cell_end(const unsigned char *p); bufsize_t _scan_table_row_end(const unsigned char *p); +bufsize_t _scan_tasklist(const unsigned char *p); #define scan_table_start(c, l, n) _ext_scan_at(&_scan_table_start, c, l, n) #define scan_table_cell(c, l, n) _ext_scan_at(&_scan_table_cell, c, l, n) #define scan_table_cell_end(c, l, n) _ext_scan_at(&_scan_table_cell_end, c, l, n) #define scan_table_row_end(c, l, n) _ext_scan_at(&_scan_table_row_end, c, l, n) +#define scan_tasklist(c, l, n) _ext_scan_at(&_scan_tasklist, c, l, n) #ifdef __cplusplus } diff --git a/extensions/ext_scanners.re b/extensions/ext_scanners.re index 25bdc0807..94a4c6732 100644 --- a/extensions/ext_scanners.re +++ b/extensions/ext_scanners.re @@ -31,6 +31,8 @@ bufsize_t _ext_scan_at(bufsize_t (*scanner)(const unsigned char *), unsigned cha table_marker = (spacechar*[:]?[-]+[:]?spacechar*); table_cell = (escaped_char|[^|\r\n])*; + + tasklist = spacechar*("-"|"+"|"*"|[0-9]+.)spacechar+("[ ]"|"[x]")spacechar+; */ bufsize_t _scan_table_start(const unsigned char *p) @@ -72,3 +74,12 @@ bufsize_t _scan_table_row_end(const unsigned char *p) .? { return 0; } */ } +bufsize_t _scan_tasklist(const unsigned char *p) +{ + const unsigned char *marker = NULL; + const unsigned char *start = p; +/*!re2c + tasklist { return (bufsize_t)(p - start); } + .? { return 0; } +*/ +} diff --git a/extensions/tasklist.c b/extensions/tasklist.c new file mode 100644 index 000000000..7351a4481 --- /dev/null +++ b/extensions/tasklist.c @@ -0,0 +1,127 @@ +#include "tasklist.h" +#include +#include +#include +#include "ext_scanners.h" + +typedef enum { + CMARK_TASKLIST_NOCHECKED, + CMARK_TASKLIST_CHECKED, +} cmark_tasklist_type; + +static const char *get_type_string(cmark_syntax_extension *extension, cmark_node *node) { + return "tasklist"; +} + +static bool parse_node_item_prefix(cmark_parser *parser, const char *input, + cmark_node *container) { + bool res = false; + + if (parser->indent >= + container->as.list.marker_offset + container->as.list.padding) { + cmark_parser_advance_offset(parser, input, container->as.list.marker_offset + + container->as.list.padding, + true); + res = true; + } else if (parser->blank && container->first_child != NULL) { + // if container->first_child is NULL, then the opening line + // of the list item was blank after the list marker; in this + // case, we are done with the list item. + cmark_parser_advance_offset(parser, input, parser->first_nonspace - parser->offset, + false); + res = true; + } + return res; +} + +static int matches(cmark_syntax_extension *self, cmark_parser *parser, + unsigned char *input, int len, + cmark_node *parent_container) { + return parse_node_item_prefix(parser, (const char*)input, parent_container); +} + +static int can_contain(cmark_syntax_extension *extension, cmark_node *node, + cmark_node_type child_type) { + return (node->type == CMARK_NODE_ITEM) ? 1 : 0; +} + +static cmark_node *open_tasklist_item(cmark_syntax_extension *self, + int indented, cmark_parser *parser, + cmark_node *parent_container, + unsigned char *input, int len) { + cmark_node_type node_type = cmark_node_get_type(parent_container); + if (node_type != CMARK_NODE_ITEM) { + return NULL; + } + + bufsize_t matched = scan_tasklist(input, len, 0); + if (!matched) { + return NULL; + } + + cmark_node_set_syntax_extension(parent_container, self); + cmark_parser_advance_offset(parser, (char *)input, 3, false); + + long userdata; + if (strstr((char*)input, "[x]")) { + userdata = CMARK_TASKLIST_CHECKED; + } else { + userdata = CMARK_TASKLIST_NOCHECKED; + } + cmark_node_set_user_data(parent_container, (void*)userdata); + + return NULL; +} + +static void commonmark_render(cmark_syntax_extension *extension, + cmark_renderer *renderer, cmark_node *node, + cmark_event_type ev_type, int options) { + bool entering = (ev_type == CMARK_EVENT_ENTER); + if (entering) { + renderer->cr(renderer); + long userdata = (long)cmark_node_get_user_data(node); + if (userdata == CMARK_TASKLIST_CHECKED) { + renderer->out(renderer, node, " - [x] ", false, LITERAL); + } else { + renderer->out(renderer, node, " - [ ] ", false, LITERAL); + } + cmark_strbuf_puts(renderer->prefix, " "); + } else { + cmark_strbuf_truncate(renderer->prefix, renderer->prefix->size - 4); + renderer->cr(renderer); + } +} + +static void html_render(cmark_syntax_extension *extension, + cmark_html_renderer *renderer, cmark_node *node, + cmark_event_type ev_type, int options) { + bool entering = (ev_type == CMARK_EVENT_ENTER); + if (entering) { + cmark_html_render_cr(renderer->html); + cmark_strbuf_puts(renderer->html, "
  • html, options); + cmark_strbuf_putc(renderer->html, '>'); + long userdata = (long)cmark_node_get_user_data(node); + if (userdata == CMARK_TASKLIST_CHECKED) { + cmark_strbuf_puts(renderer->html, " "); + } else { + cmark_strbuf_puts(renderer->html, " "); + } + } else { + cmark_strbuf_puts(renderer->html, "
  • \n"); + } +} + +cmark_syntax_extension *create_tasklist_extension(void) { + cmark_syntax_extension *ext = cmark_syntax_extension_new("tasklist"); + + cmark_syntax_extension_set_match_block_func(ext, matches); + cmark_syntax_extension_set_get_type_string_func(ext, get_type_string); + cmark_syntax_extension_set_open_block_func(ext, open_tasklist_item); + cmark_syntax_extension_set_can_contain_func(ext, can_contain); + cmark_syntax_extension_set_commonmark_render_func(ext, commonmark_render); + cmark_syntax_extension_set_plaintext_render_func(ext, commonmark_render); + cmark_syntax_extension_set_html_render_func(ext, html_render); + + return ext; +} diff --git a/extensions/tasklist.h b/extensions/tasklist.h new file mode 100644 index 000000000..26e9d96d2 --- /dev/null +++ b/extensions/tasklist.h @@ -0,0 +1,8 @@ +#ifndef TASKLIST_H +#define TASKLIST_H + +#include "cmark-gfm-core-extensions.h" + +cmark_syntax_extension *create_tasklist_extension(void); + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 148f29ce1..2b5c99bc5 100755 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -70,7 +70,7 @@ IF (PYTHONINTERP_FOUND) "--no-normalize" "--spec" "${CMAKE_CURRENT_SOURCE_DIR}/extensions.txt" "--program" "${CMAKE_CURRENT_BINARY_DIR}/../src/cmark-gfm" - "--extensions" "table strikethrough autolink tagfilter footnotes" + "--extensions" "table strikethrough autolink tagfilter footnotes tasklist" ) add_test(roundtrip_extensions_executable @@ -78,7 +78,7 @@ IF (PYTHONINTERP_FOUND) "${CMAKE_CURRENT_SOURCE_DIR}/roundtrip_tests.py" "--spec" "${CMAKE_CURRENT_SOURCE_DIR}/extensions.txt" "--program" "${CMAKE_CURRENT_BINARY_DIR}/../src/cmark-gfm" - "--extensions" "table strikethrough autolink tagfilter footnotes" + "--extensions" "table strikethrough autolink tagfilter footnotes tasklist" ) add_test(option_table_prefer_style_attributes @@ -86,7 +86,7 @@ IF (PYTHONINTERP_FOUND) "${CMAKE_CURRENT_SOURCE_DIR}/roundtrip_tests.py" "--spec" "${CMAKE_CURRENT_SOURCE_DIR}/extensions-table-prefer-style-attributes.txt" "--program" "${CMAKE_CURRENT_BINARY_DIR}/../src/cmark-gfm --table-prefer-style-attributes" - "--extensions" "table strikethrough autolink tagfilter footnotes" + "--extensions" "table strikethrough autolink tagfilter footnotes tasklist" ) add_test(option_full_info_string diff --git a/test/extensions.txt b/test/extensions.txt index 3894ec575..b2d06165d 100644 --- a/test/extensions.txt +++ b/test/extensions.txt @@ -711,3 +711,32 @@ Autolink and tables. ```````````````````````````````` + +## Task lists + +```````````````````````````````` example +- [ ] foo +- [x] bar +. +
      +
    • foo
    • +
    • bar
    • +
    +```````````````````````````````` + +```````````````````````````````` example +- [x] foo + - [ ] bar + - [x] baz +- [ ] bim +. +
      +
    • foo +
        +
      • bar
      • +
      • baz
      • +
      +
    • +
    • bim
    • +
    +````````````````````````````````