@moritzrs/micromark-extension-ofm-wikilink
Version:
micromark extension to support OFM wikilinks
512 lines (511 loc) • 15.4 kB
JavaScript
// ASCI Codes
const SPACE = 32;
const EXCLAMATION_MARK = 33;
const NUMBER_SIGN = 35;
const LEFT_SQUARE_BRACKET = 91;
const RIGHT_SQUARE_BRACKET = 93;
const VERTICAL_BAR = 124;
/**
* Create an extension for `micromark` to enable OFM wikilink syntax.
*/
export function ofmWikilink() {
return {
text: {
[LEFT_SQUARE_BRACKET]: {
name: "ofmWikilink",
tokenize: tokenize,
},
[EXCLAMATION_MARK]: {
name: "ofmWikilink",
tokenize: tokenize,
},
},
};
}
/**
* A tokenizer for Obsidian wikilink syntax.
*/
function tokenize(effects, ok, nok) {
return start;
/**
* Start of wikilink
*
* ```markdown
* > | ![[__path__#__hash__|__alias__]]
* ^^
* ```
*/
function start(code) {
if (code === EXCLAMATION_MARK) {
effects.enter("ofmWikilink");
effects.enter("ofmWikilinkEmbeddingMarker");
effects.consume(code);
effects.exit("ofmWikilinkEmbeddingMarker");
return after_embedding_marker;
}
effects.enter("ofmWikilink");
effects.enter("ofmWikilinkOpenMarker");
effects.consume(code);
return after_first_open;
}
/**
* After the embedding marker `!`
*
* ```markdown
* > | ![[__path__#__hash__|__alias__]]
* ^
* ```
*/
function after_embedding_marker(code) {
if (code !== LEFT_SQUARE_BRACKET)
return nok(code);
effects.enter("ofmWikilinkOpenMarker");
effects.consume(code);
return after_first_open;
}
/**
* After the first open square bracket `[`
*
* ```markdown
* > | [[__path__#__hash__|__alias__]]
* ^
* ```
*/
function after_first_open(code) {
if (code !== LEFT_SQUARE_BRACKET)
return nok(code);
effects.consume(code);
effects.exit("ofmWikilinkOpenMarker");
return after_second_open;
}
/**
* After the second open square bracket `[`
*
* ```markdown
* > | [[__path__#__hash__|__alias__]]
* ^
* ```
*/
function after_second_open(code) {
if (code === SPACE) {
effects.enter("ofmWikilinkWhiteSpace");
effects.consume(code);
return open_whitespace;
}
if (code === NUMBER_SIGN) {
effects.enter("ofmWikilinkHashMarker");
effects.consume(code);
effects.exit("ofmWikilinkHashMarker");
return after_hash_marker;
}
if (code === VERTICAL_BAR) {
effects.enter("ofmWikilinkAliasMarker");
effects.consume(code);
effects.exit("ofmWikilinkAliasMarker");
return after_alias_marker;
}
if (code === RIGHT_SQUARE_BRACKET) {
effects.enter("ofmWikilinkCloseMarker");
effects.consume(code);
return after_first_close;
}
if (code && code > SPACE) {
effects.enter("ofmWikilinkPath");
effects.consume(code);
return path;
}
return nok(code);
}
/**
* Inside first group of whitespace
*
* ```markdown
* > | [[__path__#__hash__|__alias__]]
* ^^
* ```
*/
function open_whitespace(code) {
if (code === SPACE) {
effects.consume(code);
return open_whitespace;
}
if (code === NUMBER_SIGN) {
effects.exit("ofmWikilinkWhiteSpace");
effects.enter("ofmWikilinkHashMarker");
effects.consume(code);
effects.exit("ofmWikilinkHashMarker");
return after_hash_marker;
}
if (code === VERTICAL_BAR) {
effects.exit("ofmWikilinkWhiteSpace");
effects.enter("ofmWikilinkAliasMarker");
effects.consume(code);
effects.exit("ofmWikilinkAliasMarker");
return after_alias_marker;
}
if (code === RIGHT_SQUARE_BRACKET) {
effects.exit("ofmWikilinkWhiteSpace");
effects.enter("ofmWikilinkCloseMarker");
effects.consume(code);
return after_first_close;
}
if (code && code > SPACE) {
effects.exit("ofmWikilinkWhiteSpace");
effects.enter("ofmWikilinkPath");
effects.consume(code);
return path;
}
return nok(code);
}
/**
* Inside the path
*
* ```markdown
* > | [[__path__#__hash__|__alias__]]
* ^^^^
* ```
*/
function path(code) {
if (code === NUMBER_SIGN) {
effects.exit("ofmWikilinkPath");
effects.enter("ofmWikilinkHashMarker");
effects.consume(code);
effects.exit("ofmWikilinkHashMarker");
return after_hash_marker;
}
if (code === VERTICAL_BAR) {
effects.exit("ofmWikilinkPath");
effects.enter("ofmWikilinkAliasMarker");
effects.consume(code);
effects.exit("ofmWikilinkAliasMarker");
return after_alias_marker;
}
if (code === RIGHT_SQUARE_BRACKET) {
effects.exit("ofmWikilinkPath");
effects.enter("ofmWikilinkCloseMarker");
effects.consume(code);
return after_first_close;
}
if (code === SPACE) {
return effects.attempt(create_whitespace_tokenizer("ofmWikilinkPath", true, true), after_content_whitespace, function (code) {
effects.consume(code);
return path;
})(code);
}
if (code && code > SPACE) {
effects.consume(code);
return path;
}
return nok(code);
}
/**
* Inside the whitespace after the path
*
* ```markdown
* > | [[__path__#__hash__|__alias__]]
* ^
* ```
*/
function after_content_whitespace(code) {
if (code === NUMBER_SIGN) {
effects.enter("ofmWikilinkHashMarker");
effects.consume(code);
effects.exit("ofmWikilinkHashMarker");
return after_hash_marker;
}
if (code === VERTICAL_BAR) {
effects.enter("ofmWikilinkAliasMarker");
effects.consume(code);
effects.exit("ofmWikilinkAliasMarker");
return after_alias_marker;
}
if (code === RIGHT_SQUARE_BRACKET) {
effects.enter("ofmWikilinkCloseMarker");
effects.consume(code);
return after_first_close;
} /* v8 ignore next */
return nok(code);
}
/**
* After the hash marker `#`
*
* ```markdown
* > | [[__path__#__hash__|__alias__]]
* ^
* ```
*/
function after_hash_marker(code) {
if (code === SPACE) {
effects.enter("ofmWikilinkWhiteSpace");
effects.consume(code);
return hash_whitespace;
}
if (code === VERTICAL_BAR) {
effects.enter("ofmWikilinkAliasMarker");
effects.consume(code);
effects.exit("ofmWikilinkAliasMarker");
return after_alias_marker;
}
if (code === RIGHT_SQUARE_BRACKET) {
effects.enter("ofmWikilinkCloseMarker");
effects.consume(code);
return after_first_close;
}
if (code && code > SPACE) {
effects.enter("ofmWikilinkHash");
effects.consume(code);
return hash;
}
return nok(code);
}
/**
* Inside the second group of whitespace
*
* ```markdown
* > | [[__path__#__hash__|__alias__]]
* ^^
* ```
*/
function hash_whitespace(code) {
if (code === SPACE) {
effects.consume(code);
return hash_whitespace;
}
if (code === VERTICAL_BAR) {
effects.exit("ofmWikilinkWhiteSpace");
effects.enter("ofmWikilinkAliasMarker");
effects.consume(code);
effects.exit("ofmWikilinkAliasMarker");
return after_alias_marker;
}
if (code === RIGHT_SQUARE_BRACKET) {
effects.exit("ofmWikilinkWhiteSpace");
effects.enter("ofmWikilinkCloseMarker");
effects.consume(code);
return after_first_close;
}
if (code && code > SPACE) {
effects.exit("ofmWikilinkWhiteSpace");
effects.enter("ofmWikilinkHash");
effects.consume(code);
return hash;
}
return nok(code);
}
/**
* Inside the hash
*
* ```markdown
* > | [[__path__#__hash__|__alias__]]
* ^^^^
* ```
*/
function hash(code) {
if (code === VERTICAL_BAR) {
effects.exit("ofmWikilinkHash");
effects.enter("ofmWikilinkAliasMarker");
effects.consume(code);
effects.exit("ofmWikilinkAliasMarker");
return after_alias_marker;
}
if (code === RIGHT_SQUARE_BRACKET) {
effects.exit("ofmWikilinkHash");
effects.enter("ofmWikilinkCloseMarker");
effects.consume(code);
return after_first_close;
}
if (code === SPACE) {
return effects.attempt(create_whitespace_tokenizer("ofmWikilinkHash", false, true), after_hash_whitespace, function (code) {
effects.consume(code);
return hash;
})(code);
}
if (code && code > SPACE) {
effects.consume(code);
return hash;
}
return nok(code);
}
/**
* Inside the whitespace after the path
*
* ```markdown
* > | [[__path__#__hash__|__alias__]]
* ^
* ```
*/
function after_hash_whitespace(code) {
if (code === VERTICAL_BAR) {
effects.enter("ofmWikilinkAliasMarker");
effects.consume(code);
effects.exit("ofmWikilinkAliasMarker");
return after_alias_marker;
}
if (code === RIGHT_SQUARE_BRACKET) {
effects.enter("ofmWikilinkCloseMarker");
effects.consume(code);
return after_first_close;
} /* v8 ignore next */
return nok(code);
}
/**
* After the alias marker `|`
*
* ```markdown
* > | [[__path__#__hash__|__alias__]]
* ^
* ```
*/
function after_alias_marker(code) {
if (code === SPACE) {
effects.enter("ofmWikilinkWhiteSpace");
effects.consume(code);
return alias_whitespace;
}
if (code === RIGHT_SQUARE_BRACKET) {
effects.enter("ofmWikilinkCloseMarker");
effects.consume(code);
return after_first_close;
}
if (code && code > SPACE) {
effects.enter("ofmWikilinkAlias");
effects.consume(code);
return alias;
}
return nok(code);
}
/**
* Inside the third group of whitespace
*
* ```markdown
* > | [[__path__#__hash__|__alias__]]
* ^^
* ```
*/
function alias_whitespace(code) {
if (code === SPACE) {
effects.consume(code);
return alias_whitespace;
}
if (code === RIGHT_SQUARE_BRACKET) {
effects.exit("ofmWikilinkWhiteSpace");
effects.enter("ofmWikilinkCloseMarker");
effects.consume(code);
return after_first_close;
}
if (code && code > SPACE) {
effects.exit("ofmWikilinkWhiteSpace");
effects.enter("ofmWikilinkAlias");
effects.consume(code);
return alias;
}
return nok(code);
}
/**
* Inside the alias
*
* ```markdown
* > | [[__path__#__hash__|__alias__]]
* ^^^^^
* ```
*/
function alias(code) {
if (code === RIGHT_SQUARE_BRACKET) {
effects.exit("ofmWikilinkAlias");
effects.enter("ofmWikilinkCloseMarker");
effects.consume(code);
return after_first_close;
}
if (code === SPACE) {
return effects.attempt(create_whitespace_tokenizer("ofmWikilinkAlias", false, false), after_alias_whitespace, function (code) {
effects.consume(code);
return alias;
})(code);
}
if (code && code > SPACE) {
effects.consume(code);
return alias;
}
return nok(code);
}
/**
* Inside the whitespace after the path
*
* ```markdown
* > | [[__path__#__hash__|__alias__]]
* ^
* ```
*/
function after_alias_whitespace(code) {
if (code === RIGHT_SQUARE_BRACKET) {
effects.enter("ofmWikilinkCloseMarker");
effects.consume(code);
return after_first_close;
} /* v8 ignore next */
return nok(code);
}
/**
* After the first close square bracket `]`
*
* ```markdown
* > | [[__path__#__hash__|__alias__]]
* ^
* ```
*/
function after_first_close(code) {
if (code !== RIGHT_SQUARE_BRACKET)
return nok(code);
effects.consume(code);
effects.exit("ofmWikilinkCloseMarker");
effects.exit("ofmWikilink");
return ok(code);
}
}
function create_whitespace_tokenizer(exit_type, allow_number_sign, allow_vertical_bar) {
return {
tokenize: function (effects, ok, nok) {
return start;
/**
* Start of potential suffix whitespace
*
* ```markdown
* > | [[__path__#__hash__|__alias__]]
* ^ ^ ^
* ```
*/
function start(code) {
effects.exit(exit_type);
effects.enter("ofmWikilinkWhiteSpace");
effects.consume(code);
return white_space;
}
/**
* Inside potential suffix whitespace
*
* ```markdown
* > | [[__path__#__hash__|__alias__]]
* ^ ^ ^
* ```
*/
function white_space(code) {
if (code === SPACE) {
effects.consume(code);
return white_space;
}
if (allow_number_sign && code === NUMBER_SIGN) {
effects.exit("ofmWikilinkWhiteSpace");
return ok(code);
}
if (allow_vertical_bar && code === VERTICAL_BAR) {
effects.exit("ofmWikilinkWhiteSpace");
return ok(code);
}
if (code === RIGHT_SQUARE_BRACKET) {
effects.exit("ofmWikilinkWhiteSpace");
return ok(code);
}
return nok(code);
}
},
};
}