/* * Markdown mode for QEmacs. * * Copyright (c) 2014-2017 Charlie Gordon. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "qe.h" static ModeDef litcoffee_mode; enum { /* TODO: define specific styles */ MKD_STYLE_HEADING1 = QE_STYLE_FUNCTION, MKD_STYLE_HEADING2 = QE_STYLE_STRING, MKD_STYLE_HEADING3 = QE_STYLE_VARIABLE, MKD_STYLE_HEADING4 = QE_STYLE_TYPE, MKD_STYLE_TILDE = QE_STYLE_PREPROCESS, MKD_STYLE_COMMENT = QE_STYLE_COMMENT, MKD_STYLE_BLOCK_QUOTE = QE_STYLE_FUNCTION, MKD_STYLE_TABLE = QE_STYLE_TYPE, MKD_STYLE_HBAR = QE_STYLE_VARIABLE, MKD_STYLE_STRONG2 = QE_STYLE_FUNCTION, MKD_STYLE_STRONG1 = QE_STYLE_FUNCTION, MKD_STYLE_EMPHASIS2 = QE_STYLE_VARIABLE, MKD_STYLE_EMPHASIS1 = QE_STYLE_VARIABLE, MKD_STYLE_CODE = QE_STYLE_STRING, MKD_STYLE_IMAGE_LINK = QE_STYLE_KEYWORD, MKD_STYLE_REF_LINK = QE_STYLE_KEYWORD, MKD_STYLE_DLIST = QE_STYLE_NUMBER, MKD_STYLE_LIST = QE_STYLE_NUMBER, }; enum { IN_MKD_LANG_STATE = 0x00FF, IN_MKD_LEVEL = 0x0700, IN_MKD_BLOCK = 0x7800, IN_MKD_HTML_BLOCK = 0x8000, IN_MKD_HTML_COMMENT = 0xC000, MKD_LANG_SHIFT = 11, MKD_LANG_MAX = (IN_MKD_BLOCK >> MKD_LANG_SHIFT), MKD_LEVEL_SHIFT = 8, MKD_LEVEL_MAX = 7, }; static ModeDef *mkd_lang_def[MKD_LANG_MAX + 1]; #define MKD_BULLET_STYLES 4 static int MkdBulletStyles[MKD_BULLET_STYLES] = { MKD_STYLE_HEADING1, MKD_STYLE_HEADING2, MKD_STYLE_HEADING3, MKD_STYLE_HEADING4, }; static int mkd_scan_chunk(const unsigned int *str, const char *begin, const char *end, int min_width) { int i, j; for (i = 0; begin[i]; i++) { if (str[i] != begin[i]) return 0; } for (j = 0; j < min_width; j++) { if (str[i + j] == '\0') return 0; } for (i += j; str[i] != '\0'; i++) { for (j = 0; end[j]; j++) { if (str[i + j] != end[j]) break; } if (!end[j]) return i + j; } return 0; } static int mkd_add_lang(const char *lang_name) { ModeDef *m; int lang = 0; if (lang_name && (m = qe_find_mode(lang_name, MODEF_SYNTAX)) != NULL) { for (lang = 1; lang < MKD_LANG_MAX; lang++) { if (mkd_lang_def[lang] == NULL) mkd_lang_def[lang] = m; if (mkd_lang_def[lang] == m) break; } } return lang; } static void mkd_colorize_line(QEColorizeContext *cp, unsigned int *str, int n, ModeDef *syn) { int colstate = cp->colorize_state; int level, indent, i = 0, j, start = i, base_style = 0; if (syn == &litcoffee_mode) base_style = MKD_STYLE_COMMENT; for (indent = j = 0;; j++) { if (str[j] == ' ') indent++; else if (str[j] == '\t') indent += 4; else break; } if (str[i] == '<' && str[i + 1] == '!' && str[i + 2] == '-' && str[i + 3] == '-') { colstate |= IN_MKD_HTML_COMMENT; i += 3; } if ((colstate & IN_MKD_HTML_COMMENT) == IN_MKD_HTML_COMMENT) { while (i < n) { int c = str[i++]; if (c == '-' && str[i] == '-' && str[i + 1] == '>') { i += 2; colstate &= ~IN_MKD_HTML_COMMENT; break; } } SET_COLOR(str, start, i, MKD_STYLE_COMMENT); cp->colorize_state = colstate; return; } if (str[i] == '>') { if (str[++i] == ' ') i++; SET_COLOR(str, start, i, MKD_STYLE_BLOCK_QUOTE); start = i; } if (colstate & IN_MKD_BLOCK) { /* Should count number of ~ to detect end of block */ if (ustrstart(str + j, "~~~", NULL) || ustrstart(str + j, "```", NULL)) { colstate &= ~IN_MKD_BLOCK; i = n; SET_COLOR(str, start, i, MKD_STYLE_TILDE); } else { int lang = (colstate & IN_MKD_BLOCK) >> MKD_LANG_SHIFT; if (mkd_lang_def[lang]) { cp->colorize_state = colstate & IN_MKD_LANG_STATE; mkd_lang_def[lang]->colorize_func(cp, str + i, n - i, mkd_lang_def[lang]); colstate &= ~IN_MKD_LANG_STATE; colstate |= cp->colorize_state & IN_MKD_LANG_STATE; } else { SET_COLOR(str, start, n, MKD_STYLE_CODE); } i = n; } cp->colorize_state = colstate; return; } if (colstate & IN_MKD_HTML_BLOCK) { if (i < n && str[i] != '<' && !qe_isblank(str[i])) { /* formating error, exit html block */ colstate = 0; } } if ((colstate & IN_MKD_HTML_BLOCK) || (str[i] == '<' && (str[i + 1] == '!' || str[i + 1] == '?' || qe_isalpha(str[i + 1])))) { /* block level HTML markup */ colstate &= ~IN_MKD_HTML_BLOCK; cp->colorize_state = colstate; htmlsrc_mode.colorize_func(cp, str, n, &htmlsrc_mode); colstate = cp->colorize_state; colstate |= IN_MKD_HTML_BLOCK; if ((str[i] & CHAR_MASK) == '<' && (str[i + 1] & CHAR_MASK) == '/') colstate = 0; cp->colorize_state = colstate; return; } if (str[i] == '#') { /* Check for heading: initial string of '#' followed by ' ' */ for (i++; str[i] == '#'; i++) continue; if (qe_isblank(str[i])) { base_style = MkdBulletStyles[(i - start - 1) % MKD_BULLET_STYLES]; i++; SET_COLOR(str, start, i, base_style); } } else if (str[i] == '%') { /* pandoc extension: line comment */ i = n; SET_COLOR(str, start, i, MKD_STYLE_COMMENT); } else if (str[i] == '-') { /* dashes underline a heading */ for (i++; str[i] == '-'; i++) continue; if (i == n) { SET_COLOR(str, start, i, MKD_STYLE_HEADING2); } } else if (str[i] == '=') { /* equal signs indicate a heading */ for (i++; str[i] == '='; i++) continue; if (i == n) { SET_COLOR(str, start, i, MKD_STYLE_HEADING1); } } else if (str[i] == '|') { base_style = MKD_STYLE_TABLE; } else if (ustrstart(str + j, "~~~", NULL) || ustrstart(str + j, "```", NULL)) { /* verbatim block */ char lang_name[16]; int lang = syn->colorize_flags, len; // was MKD_LANG_MAX colstate &= ~(IN_MKD_BLOCK | IN_MKD_LANG_STATE); for (i = j + 3; qe_isblank(str[i]); i++) continue; for (len = 0; i < n && !qe_isblank(str[i]); i++) { if (len < countof(lang_name) - 1) lang_name[len++] = str[i]; } lang_name[len] = '\0'; if (len) { lang = mkd_add_lang(lang_name); } colstate |= lang << MKD_LANG_SHIFT; i = n; SET_COLOR(str, start, i, MKD_STYLE_TILDE); } /* [X] unordered lists: /[-*+] / */ /* [X] ordered lists: /[0-9]+[.] / */ /* [ ] list continuation lines are indented 1 level */ /* [ ] code blocks are indented one extra level */ /* [X] horizontal rules: /^ *([-*_][ ]*){3-}$/ */ /* [/] inline links: /[[].*[]]([(].*[)])?/ */ /* [/] reference links: /[[].*[]][ ]*[[][a-zA-Z0-9 ,.;:?]*[]])/ */ /* [/] references: /[ ]{0-3}[[][a-zA-Z0-9 ,.;:?]+[]]:.*)/ */ /* [/] images: same as links, preceded by ! */ /* [X] automatic links and email addresses: */ /* [X] emphasis: _.*_ \*.*\* __.*__ \*\*.*\*\* */ /* [X] code span: `code` */ /* [X] code span with embedded `: ``code`` or `` code `` */ /* [X] litteral chars: isolate them or escape them with \ */ /* \ ` * _ { } [ ] ( ) # + - . ! */ level = (colstate & IN_MKD_LEVEL) >> MKD_LEVEL_SHIFT; i = j; if (i < n) { start = i; /* ignore blank lines for level and code triggers */ if (indent < level * 4) { level = indent >> 2; } indent -= level * 4; if (indent >= 4) { int lang = syn->colorize_flags; /* default language */ /* Should detect sequel lines in ordered/unordered lists */ if (mkd_lang_def[lang]) { cp->colorize_state = colstate & IN_MKD_LANG_STATE; mkd_lang_def[lang]->colorize_func(cp, str + 4, n - 4, mkd_lang_def[lang]); colstate &= ~IN_MKD_LANG_STATE; colstate |= cp->colorize_state & IN_MKD_LANG_STATE; } else { SET_COLOR(str, i, n, MKD_STYLE_CODE); } i = n; } } if (str[i] == '*' || str[i] == '-' || str[i] == '_') { int count = 1; for (j = i + 1; j < n; j++) { if (qe_isblank(str[j])) continue; if (str[j] == str[i]) count++; else break; } if (j == n && count >= 3) { /* Horizontal rule */ start = i; i = n; SET_COLOR(str, start, i, MKD_STYLE_HBAR); } } if (level) { base_style = MKD_STYLE_LIST; } if (qe_isdigit(str[i])) { for (j = i + 1; qe_isdigit(str[j]); j++) continue; if (str[j] == '.' && qe_isblank(str[j + 1])) { level++; base_style = MKD_STYLE_DLIST; start = i; i = j; SET_COLOR(str, start, i, base_style); } } else if ((str[i] == '-' || str[i] == '*' || str[i] == '+') && qe_isblank(str[i + 1])) { start = i; level++; base_style = MKD_STYLE_LIST; SET_COLOR(str, start, i, base_style); } for (;;) { int chunk = 0, chunk_style = base_style; int c, flags; start = i; c = str[i]; if (c == '\0') break; switch (c) { case '#': break; case '*': /* bold */ if (str[i + 1] == '*') { chunk_style = MKD_STYLE_STRONG2; chunk = mkd_scan_chunk(str + i, "**", "**", 1); } else { chunk_style = MKD_STYLE_STRONG1; chunk = mkd_scan_chunk(str + i, "*", "*", 1); } break; case '_': /* emphasis */ if (str[i + 1] == '_') { chunk_style = MKD_STYLE_EMPHASIS2; chunk = mkd_scan_chunk(str + i, "__", "__", 1); } else { chunk_style = MKD_STYLE_EMPHASIS1; chunk = mkd_scan_chunk(str + i, "_", "_", 1); } break; case '`': /* code */ chunk_style = MKD_STYLE_CODE; if (str[i + 1] == '`') { if (str[i + 2] == ' ') { chunk = mkd_scan_chunk(str + i, "`` ", " ``", 1); } else { chunk = mkd_scan_chunk(str + i, "``", "``", 1); } } else { chunk = mkd_scan_chunk(str + i, "`", "`", 1); } break; case '!': /* image link ^[...: <...>] */ chunk_style = MKD_STYLE_IMAGE_LINK; chunk = mkd_scan_chunk(str + i, "![", "]", 1); break; case '[': /* link ^[...: <...>] */ chunk_style = MKD_STYLE_REF_LINK; chunk = mkd_scan_chunk(str + i, "[", "]", 1); break; case '<': /* automatic link */ chunk_style = MKD_STYLE_REF_LINK; chunk = mkd_scan_chunk(str + i, "", 1); if (chunk) break; for (flags = 0, j = i + 1; j < n;) { if (str[j] == '@') flags |= 1; if (str[j++] == '>') { flags |= 2; break; } } if (flags == 3) { chunk = j; break; } break; case '\\': /* escape */ if (strchr("\\`*_{}[]()#+-.!", str[i + 1])) { i += 2; SET_COLOR(str, start, i, base_style); continue; } break; } if (chunk) { i += chunk; SET_COLOR(str, start, i, chunk_style); } else { i++; SET_COLOR1(str, start, base_style); } } colstate &= ~IN_MKD_LEVEL; colstate |= (level << MKD_LEVEL_SHIFT) & IN_MKD_LEVEL; cp->colorize_state = colstate; } static int mkd_is_header_line(EditState *s, int offset) { /* Check if line starts with '#' */ /* XXX: should ignore blocks using colorstate */ return eb_nextc(s->b, eb_goto_bol(s->b, offset), &offset) == '#'; } static int mkd_find_heading(EditState *s, int offset, int *level, int silent) { int offset1, nb, c; offset = eb_goto_bol(s->b, offset); for (;;) { /* Find line starting with '#' */ /* XXX: should ignore blocks using colorstate */ if (eb_nextc(s->b, offset, &offset1) == '#') { for (nb = 1; (c = eb_nextc(s->b, offset1, &offset1)) == '#'; nb++) continue; if (qe_isblank(c)) { *level = nb; return offset; } } if (offset == 0) break; offset = eb_prev_line(s->b, offset); } if (!silent) put_status(s, "Before first heading"); return -1; } static int mkd_next_heading(EditState *s, int offset, int target, int *level) { int offset1, nb, c; for (;;) { offset = eb_next_line(s->b, offset); if (offset >= s->b->total_size) { nb = 0; break; } /* XXX: should ignore blocks using colorstate */ if (eb_nextc(s->b, offset, &offset1) == '#') { for (nb = 1; (c = eb_nextc(s->b, offset1, &offset1)) == '#'; nb++) continue; if (qe_isblank(c) && nb <= target) { break; } } } if (level) *level = nb; return offset; } static int mkd_prev_heading(EditState *s, int offset, int target, int *level) { int offset1, nb, c; for (;;) { if (offset == 0) { nb = 0; break; } offset = eb_prev_line(s->b, offset); /* XXX: should ignore blocks using colorstate */ if (eb_nextc(s->b, offset, &offset1) == '#') { for (nb = 1; (c = eb_nextc(s->b, offset1, &offset1)) == '#'; nb++) continue; if (qe_isblank(c) && nb <= target) { break; } } } if (level) *level = nb; return offset; } static void do_outline_next_vsible_heading(EditState *s) { s->offset = mkd_next_heading(s, s->offset, MKD_LEVEL_MAX, NULL); } static void do_outline_previous_vsible_heading(EditState *s) { s->offset = mkd_prev_heading(s, s->offset, MKD_LEVEL_MAX, NULL); } static void do_outline_up_heading(EditState *s) { int offset, level; offset = mkd_find_heading(s, s->offset, &level, 0); if (offset < 0) return; if (level <= 1) { put_status(s, "Already at top level of the outline"); return; } s->offset = mkd_prev_heading(s, offset, level - 1, &level); } static void do_mkd_backward_same_level(EditState *s) { int offset, level, level1; offset = mkd_find_heading(s, s->offset, &level, 0); if (offset < 0) return; offset = mkd_prev_heading(s, offset, level, &level1); if (level1 != level) { put_status(s, "No previous same-level heading"); return; } s->offset = offset; } static void do_mkd_forward_same_level(EditState *s) { int offset, level, level1; offset = mkd_find_heading(s, s->offset, &level, 0); if (offset < 0) return; offset = mkd_next_heading(s, offset, level, &level1); if (level1 != level) { put_status(s, "No following same-level heading"); return; } s->offset = offset; } static void do_mkd_goto(EditState *s, const char *dest) { int offset, level, level1, nb; const char *p = dest; /* XXX: Should pop up a window with numbered outline index * and let the user select the target interactively. */ /* Jump to numbered destination. */ for (offset = 0, level = 0; qe_isdigit(*p); ) { nb = strtol(p, (char **)&p, 10); if (*p == '.') p++; level++; for (; nb > 0; nb--) { offset = mkd_next_heading(s, offset, level, &level1); if (level != level1) { put_status(s, "Heading not found"); return; } } } if (level) s->offset = offset; } static void do_mkd_mark_element(EditState *s, int subtree) { QEmacsState *qs = s->qe_state; int offset, offset1, level; offset = mkd_find_heading(s, s->offset, &level, 0); if (offset < 0) return; offset1 = mkd_next_heading(s, offset, subtree ? level : MKD_LEVEL_MAX, NULL); /* XXX: if repeating last command, add subtree to region */ if (qs->last_cmd_func != qs->this_cmd_func) s->b->mark = offset; s->offset = offset1; /* activate region hilite */ if (s->qe_state->hilite_region) s->region_style = QE_STYLE_REGION_HILITE; } static void do_mkd_insert_heading(EditState *s, int flags) { int offset, offset0, offset1, level = 1; if (check_read_only(s)) return; offset = mkd_find_heading(s, s->offset, &level, 1); offset0 = eb_goto_bol(s->b, s->offset); offset1 = eb_goto_eol(s->b, s->offset); /* if at beginning of heading line, insert sibling heading before, * if in the middle of a heading line, split the heading, * otherwise, make the current line a heading line at current level. */ if (flags & 2) { /* respect-content: insert heading at end of subtree */ offset = mkd_next_heading(s, offset, level, NULL); eb_insert_uchars(s->b, offset, '\n', 2); } else if (s->offset <= offset + level + 1) { eb_insert_uchar(s->b, offset, '\n'); } else if (offset == offset0 || offset == offset1) { offset = s->offset; offset += eb_insert_uchar(s->b, offset, '\n'); } else { offset = offset0; } offset1 = offset; while (eb_match_uchar(s->b, offset1, ' ', &offset1)) continue; eb_delete(s->b, offset, offset1 - offset); offset += eb_insert_uchars(s->b, offset, '#', level); offset += eb_insert_uchar(s->b, offset, ' '); s->offset = eb_goto_eol(s->b, offset); } static void do_mkd_promote(EditState *s, int dir) { int offset, level; if (check_read_only(s)) return; offset = mkd_find_heading(s, s->offset, &level, 0); if (offset < 0) return; if (dir < 0) { eb_insert_uchar(s->b, offset, '#'); } else if (dir > 0) { if (level > 1) eb_delete_uchar(s->b, offset); else put_status(s, "Cannot promote to level 0"); } } static void do_mkd_promote_subtree(EditState *s, int dir) { int offset, level, level1; if (check_read_only(s)) return; offset = mkd_find_heading(s, s->offset, &level, 0); if (offset < 0) return; for (;;) { if (dir < 0) { eb_insert_uchar(s->b, offset, '#'); } else if (dir > 0) { if (level > 1) { eb_delete_uchar(s->b, offset); } else { put_status(s, "Cannot promote to level 0"); return; } } offset = mkd_next_heading(s, offset, MKD_LEVEL_MAX, &level1); if (level1 <= level) break; } } static void do_mkd_move_subtree(EditState *s, int dir) { int offset, offset1, offset2, level, level1, level2, size; EditBuffer *b1; if (check_read_only(s)) return; if (!mkd_is_header_line(s, s->offset)) { put_status(s, "Not on header line"); return; } offset = mkd_find_heading(s, s->offset, &level, 0); if (offset < 0) return; offset1 = mkd_next_heading(s, offset, level, &level1); size = offset1 - offset; if (dir < 0) { offset2 = mkd_prev_heading(s, offset, level, &level2); if (level2 < level) { put_status(s, "Cannot move substree"); return; } } else { if (offset1 == s->b->total_size || level1 < level) { put_status(s, "Cannot move substree"); return; } offset2 = mkd_next_heading(s, offset1, level, &level2); } b1 = eb_new("*tmp*", BF_SYSTEM | (s->b->flags & BF_STYLES)); eb_set_charset(b1, s->b->charset, s->b->eol_type); eb_insert_buffer_convert(b1, 0, s->b, offset, size); eb_delete(s->b, offset, size); if (offset2 > offset) offset2 -= size; eb_insert_buffer_convert(s->b, offset2, b1, 0, b1->total_size); eb_free(&b1); s->offset = offset2; } static void do_mkd_meta_return(EditState *s) { do_mkd_insert_heading(s, 0); } static void do_mkd_metaleft(EditState *s) { if (mkd_is_header_line(s, s->offset)) do_mkd_promote(s, +1); else do_word_right(s, -1); } static void do_mkd_metaright(EditState *s) { if (mkd_is_header_line(s, s->offset)) do_mkd_promote(s, -1); else do_word_right(s, +1); } static void do_mkd_metadown(EditState *s) { do_mkd_move_subtree(s, +1); } static void do_mkd_metaup(EditState *s) { do_mkd_move_subtree(s, -1); } /* Mkd mode specific commands */ static CmdDef mkd_commands[] = { /* Motion */ CMD2( KEY_CTRLC(KEY_CTRL('n')), KEY_NONE, /* C-c C-n */ "mkd-next-visible-heading", do_outline_next_vsible_heading, ES, "") CMD2( KEY_CTRLC(KEY_CTRL('p')), KEY_NONE, /* C-c C-p */ "mkd-previous-visible-heading", do_outline_previous_vsible_heading, ES, "") CMD2( KEY_CTRLC(KEY_CTRL('u')), KEY_NONE, /* C-c C-u */ "mkd-up-heading", do_outline_up_heading, ES, "") CMD2( KEY_CTRLC(KEY_CTRL('b')), KEY_NONE, /* C-c C-b */ "mkd-backward-same-level", do_mkd_backward_same_level, ES, "") CMD2( KEY_CTRLC(KEY_CTRL('f')), KEY_NONE, /* C-c C-f */ "mkd-forward-same-level", do_mkd_forward_same_level, ES, "") CMD2( KEY_CTRLC(KEY_CTRL('j')), KEY_NONE, /* C-c C-j */ "mkd-goto", do_mkd_goto, ESs, "s{select location to jump to: }[mkdjump]|mkdjump|") CMD3( KEY_META('h'), KEY_NONE, /* M-h */ "mkd-mark-element", do_mkd_mark_element, ESi, 0, "v") CMD3( KEY_CTRLC('@'), KEY_NONE, /* C-c @ */ "mkd-mark-subtree", do_mkd_mark_element, ESi, 1, "v") /* Editing */ CMD3( KEY_NONE, KEY_NONE, /* indirect through M-RET */ "mkd-insert-heading", do_mkd_insert_heading, ESi, 0, "*v") CMD3( KEY_CTRL('j'), KEY_NONE, /* actually C-RET */ "mkd-insert-heading-respect-content", do_mkd_insert_heading, ESi, 2, "*v") CMD3( KEY_NONE, KEY_NONE, "mkd-do-demote", do_mkd_promote, ESi, -1, "*v") CMD3( KEY_NONE, KEY_NONE, "mkd-do-promote", do_mkd_promote, ESi, +1, "*v") CMD3( KEY_CTRLX('>'), KEY_NONE, /* actually M-S-right | C-c C-x R */ "mkd-demote-subtree", do_mkd_promote_subtree, ESi, -1, "*v") CMD3( KEY_CTRLX('<'), KEY_NONE, /* actually M-S-left | C-c C-x L */ "mkd-promote-subtree", do_mkd_promote_subtree, ESi, +1, "*v") CMD3( KEY_NONE, KEY_NONE, "mkd-move-subtree-down", do_mkd_move_subtree, ESi, +1, "*v") CMD3( KEY_NONE, KEY_NONE, "mkd-move-subtree-up", do_mkd_move_subtree, ESi, -1, "*v") CMD2( KEY_META(KEY_RET), KEY_NONE, /* Actually M-RET | C-c C-x m */ "mkd-meta-return", do_mkd_meta_return, ES, "*") CMD2( KEY_ESC, KEY_LEFT, /* actually M-left | C-c C-x l */ "mkd-metaleft", do_mkd_metaleft, ES, "") CMD2( KEY_ESC, KEY_RIGHT, /* actually M-right | C-c C-x r */ "mkd-metaright", do_mkd_metaright, ES, "") CMD2( KEY_ESC, KEY_DOWN, /* actually M-down | C-c C-x d */ "mkd-metadown", do_mkd_metadown, ES, "") CMD2( KEY_ESC, KEY_UP, /* actually M-up | C-c C-x u */ "mkd-metaup", do_mkd_metaup, ES, "") CMD_DEF_END, }; static int mkd_mode_init(EditState *s, EditBuffer *b, int flags) { if (s) { s->b->tab_width = 4; s->indent_tabs_mode = 0; /* XXX: should come from mode.default_wrap */ s->wrap = WRAP_WORD; } return 0; } static ModeDef mkd_mode = { .name = "markdown", .extensions = "mkd|md|markdown", .mode_init = mkd_mode_init, .colorize_func = mkd_colorize_line, }; static int litcoffee_mode_init(EditState *s, EditBuffer *b, int flags) { if (s) { s->b->tab_width = 4; s->indent_tabs_mode = 0; /* XXX: should come from mode.default_wrap */ s->wrap = WRAP_WORD; s->mode->colorize_flags = mkd_add_lang("coffee"); } return 0; } static ModeDef litcoffee_mode = { .name = "LitCoffee", .extensions = "litcoffee", .mode_init = litcoffee_mode_init, .colorize_func = mkd_colorize_line, }; static int mkd_init(void) { qe_register_mode(&mkd_mode, MODEF_SYNTAX); qe_register_cmd_table(mkd_commands, &mkd_mode); qe_register_mode(&litcoffee_mode, MODEF_SYNTAX); qe_register_cmd_table(mkd_commands, &litcoffee_mode); return 0; } qe_module_init(mkd_init);