slidevjs / slidev

Presentation Slides for Developers
https://sli.dev
MIT License
33.49k stars 1.37k forks source link

Symbol range, not line highlighting #1364

Open asm0dey opened 8 months ago

asm0dey commented 8 months ago

Is your feature request related to a problem? Please describe.

Sometimes I find myself in a situation when I need to highlight only a part of a line, not the whole line, for example:

val names = repository.query("SELECT * FROM x") ■{ x ->■
■  x.get("name")■
■}■

I have fenced the interesting parts with sign.

Describe the solution you'd like

I think that the current syntax on step-by-step code animation can be extended to support it in a backwards-compatible way, by introducing additional syntax like

{1[0-44]|1[45-51],2,3[0-2]|all}

Parser for expressions like this still will be relatively simple, for example peggyjs grammar will go like this:

Steps = steps:Items|1..,"|"|

Items = items:Item|1..,","|

Item = WordRange / Range / Integer

WordRange = line:Integer ranges:CharRanges

CharRanges = "[" ranges:Range|1..,","| "]"

Range = start:Integer "-" end:Integer

Integer "integer" = [0-9]+ { return parseInt(text(), 10); }

It parses correctly even more complex expressions like

2,3-5,6[1-3,5-7]|22|7[4-8]

Describe alternatives you've considered

You have answered why it doesn't make sense to implement it with glow in your comment https://github.com/slidevjs/slidev/issues/1358#issuecomment-1974886245

asm0dey commented 8 months ago

chevrotrain parser is a bit longer (and should be much faster)

(function animationParser() {
  // ----------------- Lexer -----------------
  const createToken = chevrotain.createToken;
  const Lexer = chevrotain.Lexer;
  const CstParser = chevrotain.CstParser;

  const num = createToken({ name: "num", pattern: /\d+/ });
  const dash = createToken({ name: "dash", pattern: /-/ });
  const leftSquare = createToken({ name: "leftSquare", pattern: /\[/ });
  const rightSquare = createToken({ name: "rightSquare", pattern: /\]/ });
  const comma = createToken({ name: "comma", pattern: /,/ });
  const pipe = createToken({ name: "pipe", pattern: /\|/ });

  let allTokens = [
    num,
    dash,
    leftSquare,
    rightSquare,
    comma,
    pipe
  ];

  let AnimationLexer = new Lexer(allTokens);

  class AnimationParse extends CstParser {
    constructor() {
      super(allTokens);

      const $ = this;

      $.RULE("range", () => {
        $.CONSUME1(num)
        $.CONSUME(dash)
        $.CONSUME2(num)
      });

      $.RULE("charRanges", () => {
        $.CONSUME(leftSquare)
        $.AT_LEAST_ONE_SEP({
          SEP: comma,
          DEF: () => {
            $.SUBRULE($.range);
          },
        });
        $.CONSUME(rightSquare)
      })

      $.RULE("wordRange", () => {
        $.CONSUME(num)
        $.SUBRULE($.charRanges)
      });

      $.RULE("item", () => {
        $.OR([
          { ALT: () => $.SUBRULE($.wordRange) },
          { ALT: () => $.SUBRULE($.range) },
          { ALT: () => $.CONSUME(num) },
        ]);
      });

      $.RULE("items", () => {
        $.AT_LEAST_ONE_SEP({
          SEP: comma,
          DEF: () => {
            $.SUBRULE($.item)
          },
        });
      });

      $.RULE("steps", () => {
        $.AT_LEAST_ONE_SEP({
          SEP: pipe,
          DEF: () => $.SUBRULE($.items)
        });
      });

      this.performSelfAnalysis();
    }
  }

  return {
    lexer: AnimationLexer,
    parser: AnimationParse,
    defaultRule: "steps"
  }
}());