MarkEdit-app/MarkEdit

[Feature Request] Text highlighting

Closed this issue · 9 comments

I greatly appreciate MarkEdit's adherence to this spec.

100% GitHub Flavored Markdown. It's exactly 100%, not 80% or 120%. It means that we follow the spec closely, and we don't invent anything else.

Notwithstanding, much of today's knowledge work may benefit from the highlighting of certain phrases and passages. Rather than an arbitrary markdown "invention" (e.g. ^^ in Roam, == in many other editors), perhaps there's some other way to invoke the markText method within CodeMirror so that certain text might be styled as a highlight. Thank you for the consideration.

Thanks for the feedback. Honestly, I am not a big fan of adding features like this. However, this brings up an open question, should we have a way to tweak CodeMirror plugins, this is something I am interested in but don't have bandwidth to explore right now.

I understand your position. I don't know much about CodeMirror. I looked into it years ago but ended up building a project on Quill js. Let me know more about your thinking on tweaking CodeMirror plugins within the MarkEdit framework. I might be open to digging deeper.

While I am not sure if these are going to resolve the pain point you shared, I am making an extension system for MarkEdit with these changes:

I've tried a few scenarios and it works great, tweaking the markdown syntax might be harder though.

See also: #627.

With the new markedit-api system, we can easily decorate text like this:

image

I am using a fragile approach with regex though:

import { Decoration, EditorView, MatchDecorator, ViewPlugin } from '@codemirror/view';
import { MarkEdit } from 'markedit-api';

const textHighlight = ViewPlugin.fromClass(class {}, {
  provide: () => EditorView.decorations.of(editor => {
    const matcher = new MatchDecorator({
      regexp: /==.+==/g,
      boundary: /\S/,
      decoration: () => {
        return Decoration.mark({
          class: 'cm-md-textHighlight',
        });
      },
    });

    return matcher.createDeco(editor);
  }),
});

const theme = EditorView.baseTheme({
  '.cm-md-textHighlight': {
    background: 'yellow',
  },
});

MarkEdit.addExtension([theme, textHighlight]);

I don't know what's the best practice to precisely identify ==text== here, but I think the new extension system should unblock you.

OK, I've added some new API and figured out a way to accurately identify the desired pattern, by extending the Markdown parser:

import { Decoration, EditorView, ViewPlugin } from '@codemirror/view';
import { Range } from '@codemirror/state';
import { syntaxTree } from '@codemirror/language';
import { MarkdownConfig } from '@lezer/markdown';
import { MarkEdit } from 'markedit-api';

const highlightDelimiter = {
  resolve: 'TextHighlight',
  mark: 'TextHighlight',
};

const highlightConfig: MarkdownConfig = {
  defineNodes: ['TextHighlight', 'TextHighlightMark'],
  parseInline: [
    {
      name: 'TextHighlight',
      parse: (ctx, next, pos) => {
        if (next != 61 /* '=' */ || ctx.char(pos + 1) != 61) {
          return -1;
        } else {
          return ctx.addDelimiter(highlightDelimiter, pos, pos + 2, true, true);
        }
      },
      after: 'Emphasis',
    },
  ],
};

const highlightExtension = ViewPlugin.fromClass(class {}, {
  provide: () => EditorView.decorations.of(editor => {
    const ranges: Range<Decoration>[] = [];
    for (const { from, to } of editor.visibleRanges) {
      syntaxTree(editor.state).iterate({
        from, to,
        enter: node => {
          if (node.name !== 'TextHighlight') {
            return;
          }

          const mark = Decoration.mark({ class: 'cm-md-textHighlight' });
          ranges.push(mark.range(node.from, node.to));
        },
      });
    }

    return Decoration.set(ranges.sort((lhs, rhs) => lhs.from - rhs.from));
  }),
});

const higlightTheme = EditorView.baseTheme({
  '.cm-md-textHighlight': {
    background: 'rgba(255, 255, 0, 0.3)',
  },
});

MarkEdit.addExtension([higlightTheme, highlightExtension]);
MarkEdit.addMarkdownConfig(highlightConfig);

Full example is uploaded here: https://github.com/MarkEdit-app/MarkEdit-highlight.

Awesome work! I haven't had a chance to dig into the code yet, but this looks super-impressive.

The new release is really nice! One question on the highlighting: might it be possible to support typing == for selected text? Right now that simply replaces the selection. It would be great if typing = on selection worked the same as *, _ , [, etc.

What we need to do is basically copying some code from

export function interceptInputs() {
.

Example:

import { EditorSelection } from '@codemirror/state';
import { EditorView } from '@codemirror/view';

export const interceptInputs = EditorView.inputHandler.of((editor, _from, _to, insert) => {
  if (insert === '=' && hasSelection(editor)) {
    return wrapBlock(insert, editor);
  }

  return false;
});

function wrapBlock(mark: string, editor: EditorView) {
  editor.dispatch(editor.state.changeByRange(({ from, to }) => {
    const selection = editor.state.sliceDoc(from, to);
    const replacement = from === to ? mark : `${mark}${selection}${mark}`;
    const newPos = from + mark.length;
    return {
      range: EditorSelection.range(newPos, newPos + selection.length),
      changes: {
        from, to, insert: replacement,
      },
    };
  }));

  return true;
}

function hasSelection(editor: EditorView) {
  return [...editor.state.selection.ranges].some(range => !range.empty);
}

Please check the updated version: https://github.com/MarkEdit-app/MarkEdit-highlight.

Works perfectly!! Thank you!