Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document how to use Prism Plugins / add Prism diff-highlight plugin #3318

Closed
rrousselGit opened this issue Aug 21, 2020 · 5 comments
Closed
Labels
external This issue is caused by an external dependency and not Docusaurus.

Comments

@rrousselGit
Copy link

πŸ“š Documentation

Prism comes with the ability to use prism plugins to add extra functionalities – such as diff-highlight to combine the syntax highlighting of a language with the diff feature.

It is currently unclear how to enable prism plugins.

I've tried playing with swizzle to extract the code-highlighting renderer, but failed to implement this feature.

Cross-reference:

Have you read the Contributing Guidelines on issues?

Yes

@rrousselGit rrousselGit added documentation The issue is related to the documentation of Docusaurus status: needs triage This issue has not been triaged by maintainers labels Aug 21, 2020
@slorber
Copy link
Collaborator

slorber commented Aug 21, 2020

We use react-prism-renderer and it does not seem to support the ability to add such plugin, so unfortunately we can't support this unless they add support, but that seems not planned:
FormidableLabs/prism-react-renderer#2

@slorber slorber closed this as completed Aug 21, 2020
@Josh-Cena Josh-Cena added external This issue is caused by an external dependency and not Docusaurus. and removed documentation The issue is related to the documentation of Docusaurus status: needs triage This issue has not been triaged by maintainers labels Mar 27, 2022
@Repraance
Copy link

Repraance commented Jan 25, 2024

I found a way to implement diff-highlight using magic comments.

The problem is the line number is wrong, but we can hide it.

Example:

image

markdown

```tsx
// diff-remove
<a href={`/${lng}/some-path`} />
// diff-remove
location.href = `${lng}/some-path`

// diff-add
<a href={'/some-path'} />
// diff-add
location.href = `/some-path`
``` 

Implement

For docusaurus.config.js, set

const config = {
  prism: {
    magicComments: [
      {
        className: 'code-block-diff-add-line',
        line: 'diff-add'
      },
      {
        className: 'code-block-diff-remove-line',
        line: 'diff-remove'
      }
    ]
  }
}

And add custom css

.code-block-diff-add-line {
  background-color: #ccffd8;
  display: block;
  margin: 0 -40px;
  padding: 0 40px;
}

.code-block-diff-add-line::before {
  position: absolute;
  left: 8px;
  padding-right: 8px;
  content: '+';
}

.code-block-diff-remove-line {
  background-color: #ffebe9;
  display: block;
  margin: 0 -40px;
  padding: 0 40px;
}

.code-block-diff-remove-line::before {
  position: absolute;
  left: 8px;
  padding-right: 8px;
  content: '-';
}

/**
 * use magic comments to mark diff blocks
 */
pre code:has(.code-block-diff-add-line) {
  padding-left: 40px!important;
}

pre code:has(.code-block-diff-remove-line) {
  padding-left: 40px!important;
}

@thefat32
Copy link

thefat32 commented Apr 19, 2024

Implemented diff-highlight compatible with docusaurus (prism-react-renderer)

You need to swizzle in your docusaurus repo:

yarn swizzle @docusaurus/theme-classic prism-include-languages
  • docusaurus.config.ts (Example for using diff-ts)
    prism: {
      theme: prismThemes.github,
      darkTheme: prismThemes.dracula,
      additionalLanguages: ["diff", "diff-ts"],
    },
  • prism-include-languages.ts
import siteConfig from "@generated/docusaurus.config";
import type * as PrismNamespace from "prismjs";
import { diffHighlight } from "./prism-diff-highlight";
import "./prism-diff-highlight.css";

const DIFF_LANGUAGE_REGEX = /^diff-([\w-]+)/i;

export default function prismIncludeLanguages(
  PrismObject: typeof PrismNamespace
): void {
  const {
    themeConfig: { prism },
  } = siteConfig;
  const { additionalLanguages } = prism as { additionalLanguages: string[] };

  // Prism components work on the Prism instance on the window, while prism-
  // react-renderer uses its own Prism instance. We temporarily mount the
  // instance onto window, import components to enhance it, then remove it to
  // avoid polluting global namespace.
  // You can mutate PrismObject: registering plugins, deleting languages... As
  // long as you don't re-assign it
  globalThis.Prism = PrismObject;

  additionalLanguages.forEach((lang) => {
    const langMatch = DIFF_LANGUAGE_REGEX.exec(lang);
    if (!langMatch) {
      require(`prismjs/components/prism-${lang}`); // not a language specific diff
    } else {
      if (!PrismObject.languages.diff) {
        console.error(
          "prism-include-languages:",
          "You need to import 'diff' language first to use 'diff-xxxx' languages"
        );
      }
      PrismObject.languages[lang] = PrismObject.languages.diff;
    }
  });

  diffHighlight(PrismObject);

  delete globalThis.Prism;
}
  • prism-diff-highlight.ts
import { EnvConfig, PrismLib } from "prism-react-renderer";
import { TokenStream, Token } from "prismjs";

const LANGUAGE_REGEX = /^diff-([\w-]+)/i;

const tokenStreamToString = (tokenStream: TokenStream): string => {
  const result: string[] = [];
  const stack: TokenStream[] = [tokenStream];

  while (stack.length > 0) {
    const item = stack.pop();

    if (typeof item === "string") {
      result.push(item);
    } else if (Array.isArray(item)) {
      for (let i = item.length - 1; i >= 0; i--) {
        stack.push(item[i]);
      }
    } else {
      // If it's a Token, convert it to a string and push it
      stack.push(item.content);
    }
  }

  return result.join("");
};

export function diffHighlight(Prism: PrismLib) {
  Prism.hooks.add("after-tokenize", function (env: EnvConfig) {
    let diffLanguage;
    let diffGrammar;
    const language = env.language;
    if (language !== "diff") {
      const langMatch = LANGUAGE_REGEX.exec(language);
      if (!langMatch) {
        return; // not a language specific diff
      }

      diffLanguage = langMatch[1];
      diffGrammar = Prism.languages[diffLanguage];
      if (!diffGrammar) {
        console.error(
          "prism-diff-highlight:",
          `You need to add language '${diffLanguage}' to use '${language}'`
        );
        return;
      }
    } else return;

    const newTokens = [];
    env.tokens.forEach((token) => {
      if (typeof token === "string") {
        newTokens.push(...Prism.tokenize(token, diffGrammar));
      } else if (token.type === "unchanged") {
        newTokens.push(
          ...Prism.tokenize(tokenStreamToString(token), diffGrammar)
        );
      } else if (["deleted-sign", "inserted-sign"].includes(token.type)) {
        token.alias = [
          token.type === "deleted-sign"
            ? "diff-highlight-deleted"
            : "diff-highlight-inserted",
        ];
        // diff parser always return "deleted" and "inserted" lines with content of type array
        if (token.content.length > 1) {
          const newTokenContent: Array<string | Token> = [];
          // preserve prefixes and don't parse them again
          // subTokens from diff parser are of type Token
          (token.content as Array<string | Token>).forEach(
            (subToken: Token) => {
              if (subToken.type === "prefix") {
                newTokenContent.push(subToken);
              } else {
                newTokenContent.push(
                  ...Prism.tokenize(tokenStreamToString(subToken), diffGrammar)
                );
              }
            }
          );
          token.content = newTokenContent;
        }
        newTokens.push(token);
      } else if (token.type === "coord") {
        newTokens.push(token);
      }
    });
    console.log(newTokens);
    env.tokens = newTokens;
  });
}
  • prism-diff-highlight.css
code .token.diff-highlight-deleted {
	background-color: rgba(255, 0, 0, .1);
}

code .token.diff-highlight-inserted {
	background-color: rgba(0, 255, 128, .1);
}

code .token.coord {
	font-weight: 700;
}
  • Result:

image

You can add whatever other language you want in additionalLanguages:

      additionalLanguages: ["diff", "diff-ts", "powershell", "diff-powershell"],

image

NOTE FOR DEVS/INTERESTED PEOPLE:

Prism plugins do not work with prism-react-renderer because they rely in other hooks that are not called. They only call hooks "before-tokenize" and "after-tokenize". This reimplementation uses "after-tokenize" to edit tokens generated by prism-react-renderer tokenizer.

In theory every plugin could be re-implemented using those hooks to work with prism-react-renderer

EDIT: Code Update using typescript
EDIT2: Fix edge cases
EDIT3: Add support for "coord" token
EDIT4: Don't use recursion in tokenStreamToString

@uc-kywong
Copy link

image

I think you can write you own component to display diff like github.

$ npm install --save react-diff-view

I am using version 3.2.1. (https://www.npmjs.com/package/react-diff-view)


Create your component

src/components/CustomDiff/CustomDiff.js

import {Decoration, Diff, Hunk, parseDiff} from "react-diff-view";
import 'react-diff-view/style/index.css';
import './CustomDiff.css';


export default function CustomDiff({diffText}) {
  const files = parseDiff(diffText);
  return (
    <div>
      {files.map((
        {hunks, newPath, oldPath}, i) =>
        <Diff
          renderToken={(token) => token.content}
          key={i}
          viewType="split"
          diffType=""
          hunks={hunks}
        >
          {
            hunks => hunks.map(hunk => {
              return <>
                <Decoration key={'decoration-' + hunk.content}>
                  {`${oldPath}`}
                </Decoration>
                <Decoration key={'decoration-' + hunk.content}>
                  {`${newPath} - ${hunk.content}`}
                </Decoration>
                <Hunk key={hunk.content} hunk={hunk} />
              </>
            }
          )}
        </Diff>
      )}
    </div>
  );
}

Css for your component.

src/components/CustomDiff/CustomDiff.css

:root {
    --ifm-table-border-width: 0px;
}

table {
    display: table;
}

.diff-code {
    font-size: 85%;
}

How to use it in markdown?

import CustomDiff from "@site/src/components/CustomDiff/CustomDiff";


<CustomDiff diffText={`
diff --git a/package.json b/package.json
--- a/package.json	(revision c38f44524532d3a87986bfa4b9b8b82d8f0e29a0)
+++ b/package.json	(revision 93e71e98e457dae88403cdf204a8f4a85f60cc42)
@@ -1,6 +1,6 @@
 {
   "name": "SSO-Admin-UI",
-  "version": "1.0.0",
+  "version": "2.0.0-alpha.0",
   "license": "MIT",
   "scripts": {
     "start": "ng serve -o -c=dev --port=8500 --baseHref=/",
`}
/>

StuckiSimon added a commit to StuckiSimon/strahl that referenced this issue Aug 15, 2024
@mrousavy
Copy link

@Repraance nice, this works really well! Only problem I have with this CSS is that the margin/padding trick makes it scrollable...
Screenshot 2024-08-30 at 15 00 52

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
external This issue is caused by an external dependency and not Docusaurus.
Projects
None yet
Development

No branches or pull requests

7 participants