remarkable icon indicating copy to clipboard operation
remarkable copied to clipboard

Extensible way to extend terminal characters in text parser

Open knubie opened this issue 3 years ago • 4 comments

To implement some inline plugins we need to be able to add characters to the isTerminatorChar function in /lib/rules_inline/text.js. If your plugin's parser relies on a character that's not in that list (e.g. #), then the parser will never run.

One solution is to override the function with a different function, but that would conflict with other plugins that need to add their own character(s) to the list.

knubie avatar Jun 02 '21 12:06 knubie

I also just ran into this issue. Use case here is if you're trying to do something like adding a hashtag mention plugin. Plenty of workarounds, but feels weird to require a break on @ when the real character you want is #

jpribyl avatar Jun 22 '21 05:06 jpribyl

I ran into this problem trying to implement iaWriter-style image links (just beginning with a slash, like '/myImage.jpeg'). I went with the workaround suggested by @knubie, which works, but feels bad:

const md = new Remarkable();

const parse = function(state: Remarkable.StateInline) {
  // Parse...
};

// Part 1 of the hack (taken from text.js and adjusted)
function isTerminatorChar(ch: number) {
  switch (ch) {
    case 0x0A/* \n */:
    case 0x5C/* \ */:
    case 0x2F/* / */:  // Added this line to make '/' a terminator character
    case 0x60/* ` */:
    case 0x2A/* * */:
    case 0x5F/* _ */:
    case 0x5E/* ^ */:
    case 0x5B/* [ */:
    case 0x5D/* ] */:
    case 0x21/* ! */:
    case 0x26/* & */:
    case 0x3C/* < */:
    case 0x3E/* > */:
    case 0x7B/* { */:
    case 0x7D/* } */:
    case 0x24/* $ */:
    case 0x25/* % */:
    case 0x40/* @ */:
    case 0x7E/* ~ */:
    case 0x2B/* + */:
    case 0x3D/* = */:
    case 0x3A/* : */:
      return true;
    default:
      return false;
  }
}

// Part 2 of the hack (taken from text.js, unchanged)
function text(state: Remarkable.StateInline, silent: boolean) {
  var pos = state.pos;
  while (pos < state.posMax && !isTerminatorChar(state.src.charCodeAt(pos))) {
    pos++;
  }
  if (pos === state.pos) { return false; }
  if (!silent) { state.pending += state.src.slice(state.pos, pos); }
  state.pos = pos;
  return true;
};

const myPlugin = function(md: Remarkable) {
  const options = {};
  md.inline.ruler.at('text', text, options)  // Replace default text parser with adjusted one
  md.inline.ruler.push('myPlugin', parse, options);
};

md.use(myPlugin);

Edit: Added correct type annotations.

TimLaue91 avatar Nov 09 '21 15:11 TimLaue91

function text(state: {[key: string]: any}, silent: boolean) {

Holeymoley... that looks like TypeScript - can we do that these days? Have I missed something?

DiscoNova avatar Nov 10 '21 10:11 DiscoNova

I'm afraid I don't understand your question.

~~If you mean the incrdibly general type annotation: I didn't find a matching type definition in the Remarkable package and I was too lazy to spell out the properties of the state object. I'm also very new to Typescript.~~

Edit: Added correct type annotation.

TimLaue91 avatar Nov 10 '21 11:11 TimLaue91