Skip to content

add superscript extension #31

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: gfm
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions extensions/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ set(LIBRARY_SOURCES
ext_scanners.re
ext_scanners.h
tasklist.c
superscript.c
)

include_directories(
Expand Down
2 changes: 2 additions & 0 deletions extensions/core-extensions.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "tasklist.h"
#include "registry.h"
#include "plugin.h"
#include "superscript.h"

static int core_extensions_registration(cmark_plugin *plugin) {
cmark_plugin_register_syntax_extension(plugin, create_table_extension());
Expand All @@ -15,6 +16,7 @@ static int core_extensions_registration(cmark_plugin *plugin) {
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());
cmark_plugin_register_syntax_extension(plugin, create_superscript_extension());
return 1;
}

Expand Down
246 changes: 246 additions & 0 deletions extensions/superscript.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
#include "superscript.h"
#include <inlines.h>
#include <parser.h>
#include <render.h>

static cmark_chunk *S_get_node_literal_chunk(cmark_node *node) {
if (node == NULL) {
return NULL;
}

switch (node->type) {
case CMARK_NODE_HTML_BLOCK:
case CMARK_NODE_TEXT:
case CMARK_NODE_HTML_INLINE:
case CMARK_NODE_CODE:
case CMARK_NODE_FOOTNOTE_REFERENCE:
return &node->as.literal;

case CMARK_NODE_CODE_BLOCK:
return &node->as.code.literal;

default:
break;
}

return NULL;
}

static bool S_node_contains_space(cmark_node *node) {
cmark_chunk *chunk = S_get_node_literal_chunk(node);
if (chunk)
return (cmark_chunk_strchr(chunk, ' ', 0) != chunk->len);
else
return false;
}

static bool S_children_contain_space(cmark_node *parent) {
cmark_node *node = parent->first_child;
while (node) {
if (S_node_contains_space(node)) {
return true;
}
node = node->next;
}

return false;
}

cmark_node_type CMARK_NODE_SUPERSCRIPT;

static cmark_node *match(cmark_syntax_extension *self, cmark_parser *parser,
cmark_node *parent, unsigned char character,
cmark_inline_parser *inline_parser) {
cmark_node *res = NULL;
int initpos = cmark_inline_parser_get_offset(inline_parser);

if (character == '^') {
if (cmark_inline_parser_peek_at(inline_parser, initpos + 1) == '(') {
res = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem);
cmark_node_set_literal(res, "^(");
res->start_line = res->end_line = cmark_inline_parser_get_line(inline_parser);
res->start_column = cmark_inline_parser_get_column(inline_parser);
res->end_column = res->start_column + 2;

cmark_inline_parser_set_offset(inline_parser, initpos + 2);
cmark_inline_parser_push_delimiter(inline_parser, '^', true, false, res);
} else {
int startpos = initpos + 1;
int endpos = startpos;

cmark_chunk *chunk = cmark_inline_parser_get_chunk(inline_parser);
bufsize_t len = chunk->len;

while (endpos < len) {
unsigned char seekchar = cmark_inline_parser_peek_at(inline_parser, endpos);
if (cmark_isspace(seekchar) || (cmark_ispunct(seekchar) && seekchar != '^'))
break;
endpos++;
}

int nodelen = endpos - startpos;

// don't emit an empty node
if (nodelen == 0)
return NULL;

cmark_inline_parser_set_offset(inline_parser, startpos);

res = cmark_node_new_with_mem_and_ext(CMARK_NODE_SUPERSCRIPT, parser->mem, self);
res->as.literal = cmark_chunk_dup(chunk, startpos, nodelen);
res->start_line = cmark_inline_parser_get_line(inline_parser);
res->start_column = cmark_inline_parser_get_column(inline_parser);

cmark_inline_parser_set_offset(inline_parser, endpos);

res->end_line = cmark_inline_parser_get_line(inline_parser);
res->end_column = cmark_inline_parser_get_column(inline_parser);

const char *text = cmark_chunk_to_cstr(parser->mem, &res->as.literal);
cmark_node_set_string_content(res, text);

cmark_parse_inlines(parser, res, parser->refmap, parser->options);
}
} else if (character == ')') {
res = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem);
cmark_node_set_literal(res, ")");
res->start_line = res->end_line = cmark_inline_parser_get_line(inline_parser);
res->start_column = cmark_inline_parser_get_column(inline_parser);
res->end_column = res->start_column + 1;

cmark_inline_parser_set_offset(inline_parser, initpos + 1);
cmark_inline_parser_push_delimiter(inline_parser, '^', false, true, res);
}

return res;
}

static delimiter *insert(cmark_syntax_extension *self, cmark_parser *parser,
cmark_inline_parser *inline_parser, delimiter *opener,
delimiter *closer) {
cmark_node *superscript;
cmark_node *tmp, *next;
delimiter *delim, *tmp_delim;
delimiter *res = closer->next;

superscript = opener->inl_text;

if (!cmark_node_set_type(superscript, CMARK_NODE_SUPERSCRIPT))
return res;

cmark_node_set_syntax_extension(superscript, self);

tmp = cmark_node_next(opener->inl_text);

while (tmp) {
if (tmp == closer->inl_text)
break;
next = cmark_node_next(tmp);
cmark_node_append_child(superscript, tmp);
tmp = next;
}

superscript->end_column = closer->inl_text->start_column + closer->inl_text->as.literal.len - 1;
cmark_node_free(closer->inl_text);

delim = closer;
while (delim != NULL && delim != opener) {
tmp_delim = delim->previous;
cmark_inline_parser_remove_delimiter(inline_parser, delim);
delim = tmp_delim;
}

cmark_inline_parser_remove_delimiter(inline_parser, opener);

return res;
}

static const char *get_type_string(cmark_syntax_extension *extension,
cmark_node *node) {
return node->type == CMARK_NODE_SUPERSCRIPT ? "superscript" : "<unknown>";
}

static int can_contain(cmark_syntax_extension *extension, cmark_node *node,
cmark_node_type child_type) {
if (node->type != CMARK_NODE_SUPERSCRIPT)
return false;

return CMARK_NODE_TYPE_INLINE_P(child_type);
}

static void commonmark_render(cmark_syntax_extension *extension,
cmark_renderer *renderer, cmark_node *node,
cmark_event_type ev_type, int options) {
bool should_wrap = S_children_contain_space(node);
bool entering = (ev_type == CMARK_EVENT_ENTER);
if (entering) {
if (should_wrap)
renderer->out(renderer, node, "^(", false, LITERAL);
else
renderer->out(renderer, node, "^", false, LITERAL);
} else if (!entering && should_wrap) {
renderer->out(renderer, node, ")", false, LITERAL);
}
}

static void latex_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->out(renderer, node, "^{", false, LITERAL);
} else {
renderer->out(renderer, node, "}", false, LITERAL);
}
}

static void man_render(cmark_syntax_extension *extension,
cmark_renderer *renderer, cmark_node *node,
cmark_event_type ev_type, int options) {
// requires MOM
bool entering = (ev_type == CMARK_EVENT_ENTER);
if (entering) {
renderer->cr(renderer);
renderer->out(renderer, node, "\\*[SUP]", false, LITERAL);
} else {
renderer->out(renderer, node, "\\*[SUPX]", false, LITERAL);
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_strbuf_puts(renderer->html, "<sup>");
} else {
cmark_strbuf_puts(renderer->html, "</sup>");
}
}

cmark_syntax_extension *create_superscript_extension(void) {
cmark_syntax_extension *ext = cmark_syntax_extension_new("superscript");
cmark_llist *special_chars = NULL;

cmark_syntax_extension_set_get_type_string_func(ext, get_type_string);
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_latex_render_func(ext, latex_render);
cmark_syntax_extension_set_man_render_func(ext, man_render);
cmark_syntax_extension_set_html_render_func(ext, html_render);
CMARK_NODE_SUPERSCRIPT = cmark_syntax_extension_add_node(1);

cmark_syntax_extension_set_match_inline_func(ext, match);
cmark_syntax_extension_set_inline_from_delim_func(ext, insert);

cmark_mem *mem = cmark_get_default_mem_allocator();
special_chars = cmark_llist_append(mem, special_chars, (void *)'^');
special_chars = cmark_llist_append(mem, special_chars, (void *)')');
cmark_syntax_extension_set_special_inline_chars(ext, special_chars);

cmark_syntax_extension_set_emphasis(ext, 1);

return ext;
}
9 changes: 9 additions & 0 deletions extensions/superscript.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#ifndef CMARK_GFM_SUPERSCRIPT_H
#define CMARK_GFM_SUPERSCRIPT_H

#include "cmark-gfm-core-extensions.h"

extern cmark_node_type CMARK_NODE_SUPERSCRIPT;
cmark_syntax_extension *create_superscript_extension(void);

#endif /* CMARK_GFM_SUPERSCRIPT_H */
44 changes: 44 additions & 0 deletions test/spec.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7734,6 +7734,50 @@ new paragraph~~.

</div>

<div class="extension">

## Superscript (extension)

GFM provides the `superscript` extension, which adds "superscript" spans.

There are two ways to write superscripts. For simple uses, you can use a
caret (`^`) to style the text between it and the next space (or the end of
the line):

```````````````````````````````` example superscript
y = x^2 + 2

Superscripting a whole ^word
.
<p>y = x<sup>2</sup> + 2</p>
<p>Superscripting a whole <sup>word</sup></p>
````````````````````````````````

In addition, if you would like to raise more than one word, you can add
parentheses around the text you would like to style in a superscript:

```````````````````````````````` example superscript
I would like to ^(raise this whole phrase), please.
.
<p>I would like to <sup>raise this whole phrase</sup>, please.</p>
````````````````````````````````

Superscripts can be nested, by adding additional carets:

```````````````````````````````` example superscript
z = t^x^2
.
<p>z = t<sup>x<sup>2</sup></sup></p>
````````````````````````````````

```````````````````````````````` example superscript
For my next trick, I will ^(raise my text ^(twice)), at the same time!
.
<p>For my next trick, I will <sup>raise my text <sup>twice</sup></sup>, at the same time!</p>
````````````````````````````````

</div>

## Links

A link contains [link text] (the visible text), a [link destination]
Expand Down