cmark

My personal build of CMark ✏️

Commit
8a04d10c5570231baa4326817d7f10f8f1f04a57
Parent
f99a680b089eee01797e085a6d9f5c5398907a57
Author
John MacFarlane <jgm@berkeley.edu>
Date

commonmark renderer - render links as autolinks when appropriate.

Diffstat

1 file changed, 70 insertions, 21 deletions

Status File Name N° Changes Insertions Deletions
Modified src/commonmark.c 91 70 21
diff --git a/src/commonmark.c b/src/commonmark.c
@@ -9,6 +9,7 @@
 #include "node.h"
 #include "buffer.h"
 #include "utf8.h"
+#include "scanners.h"
 
 // Functions to convert cmark_nodes to commonmark strings.
 
@@ -43,8 +44,7 @@ typedef enum  {
 	LITERAL,
 	NORMAL,
 	TITLE,
-	URL,
-	BRACED_URL
+	URL
 } escaping;
 
 static inline bool
@@ -69,8 +69,6 @@ needs_escaping(escaping escape,
 	} else if (escape == URL) {
 		return (c == '`' || c == '<' || c == '>' || isspace(c) ||
 			c == '\\' || c == ')' || c == '(');
-	} else if (escape == BRACED_URL) {
-		return (c == '`' || c == '<' || c == '>' || c == '\\');
 	} else {
 		return false;
 	}
@@ -233,6 +231,33 @@ shortest_unused_backtick_sequence(cmark_chunk *code)
 	return i;
 }
 
+static bool
+is_autolink(cmark_node *node)
+{
+	const char *title;
+	const char *url;
+
+	if (node->type != CMARK_NODE_LINK) {
+		return false;
+	}
+
+	url = cmark_node_get_url(node);
+	if (url == NULL ||
+	    _scan_scheme((unsigned char *)url) == 0) {
+		return false;
+	}
+
+	title = cmark_node_get_title(node);
+	// if it has a title, we can't treat it as an autolink:
+	if (title != NULL && strnlen(title, 1) > 0) {
+		return false;
+	}
+	cmark_consolidate_text_nodes(node);
+	return (strncmp(url,
+			(char*)node->as.literal.data,
+			node->as.literal.len) == 0);
+}
+
 // if node is a block node, returns node.
 // otherwise returns first block-level node that is an ancestor of node.
 static cmark_node*
@@ -326,7 +351,7 @@ S_render_node(cmark_node *node, cmark_event_type ev_type,
 				 list_delim == CMARK_PAREN_DELIM ?
 				 ")" : ".",
 				 list_number < 10 ? "  " : " ");
-			marker_width = strlen(listmarker);
+			marker_width = strnlen(listmarker, 63);
 		}
 		if (entering) {
 			if (cmark_node_get_list_type(node->parent) ==
@@ -367,7 +392,7 @@ S_render_node(cmark_node *node, cmark_event_type ev_type,
 		// use indented form if no info, and code doesn't
 		// begin or end with a blank line, and code isn't
 		// first thing in a list item
-		if ((info == NULL || strlen(info) == 0) &&
+		if ((info == NULL || strnlen(info, 1) == 0) &&
 		    (code->len > 2 &&
 		     !isspace(code->data[0]) &&
 		     !(isspace(code->data[code->len - 1]) &&
@@ -483,21 +508,40 @@ S_render_node(cmark_node *node, cmark_event_type ev_type,
 		break;
 
 	case CMARK_NODE_LINK:
-		if (entering) {
-			lit(state, "[", false);
+		if (is_autolink(node)) {
+			if (entering) {
+				lit(state, "<", false);
+				if (strncmp(cmark_node_get_url(node),
+					    "mailto:", 7) == 0) {
+					lit(state,
+					    (char *)cmark_node_get_url(node) + 7,
+					    false);
+				} else {
+					lit(state,
+					    (char *)cmark_node_get_url(node),
+					    false);
+				}
+				lit(state, ">", false);
+				// return signal to skip contents of node...
+				return 0;
+			}
 		} else {
-			// TODO - emit autolink when url matches link text
-			// TODO - backslash-escape " and \ inside url, title
-			// for both links and images
-			lit(state, "](", false);
-			out(state, cmark_chunk_literal(cmark_node_get_url(node)), false, URL);
-			title = cmark_node_get_title(node);
-			if (title && strlen(title) > 0) {
-				lit(state, " \"", true);
-				out(state, cmark_chunk_literal(title), false, TITLE);
-				lit(state, "\"", false);
+			if (entering) {
+				lit(state, "[", false);
+			} else {
+				lit(state, "](", false);
+				out(state,
+				    cmark_chunk_literal(cmark_node_get_url(node)),
+				    false, URL);
+				title = cmark_node_get_title(node);
+				if (title && strnlen(title, 1) > 0) {
+					lit(state, " \"", true);
+					out(state, cmark_chunk_literal(title),
+					    false, TITLE);
+					lit(state, "\"", false);
+				}
+				lit(state, ")", false);
 			}
-			lit(state, ")", false);
 		}
 		break;
 
@@ -508,7 +552,7 @@ S_render_node(cmark_node *node, cmark_event_type ev_type,
 			lit(state, "](", false);
 			out(state, cmark_chunk_literal(cmark_node_get_url(node)), false, URL);
 			title = cmark_node_get_title(node);
-			if (title && strlen(title) > 0) {
+			if (title && strnlen(title, 1) > 0) {
 				lit(state, " \"", true);
 				out(state, cmark_chunk_literal(title), false, TITLE);
 				lit(state, "\"", false);
@@ -542,7 +586,12 @@ char *cmark_render_commonmark(cmark_node *root, int options, int width)
 
 	while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
 		cur = cmark_iter_get_node(iter);
-		S_render_node(cur, ev_type, &state);
+		if (!S_render_node(cur, ev_type, &state)) {
+			// a false value causes us to skip processing
+			// the node's contents.  this is used for
+			// autolinks.
+			cmark_iter_reset(iter, cur, CMARK_EVENT_EXIT);
+		}
 	}
 	result = (char *)cmark_strbuf_detach(&commonmark);