allow syntax highlighting in diffs

This commit is contained in:
Sebastian Sdorra
2020-01-22 12:08:28 +01:00
parent f12b25bd1f
commit fd15c68ca0
11 changed files with 175 additions and 38 deletions

View File

@@ -7,8 +7,9 @@ import simpleDiff from "../__resources__/Diff.simple";
import hunksDiff from "../__resources__/Diff.hunks";
import binaryDiff from "../__resources__/Diff.binary";
import Button from "../buttons/Button";
import { DiffEventContext } from "./DiffTypes";
import { DiffEventContext, File } from "./DiffTypes";
import Toast from "../toast/Toast";
import { getPath } from "./diffs";
const diffFiles = parser.parse(simpleDiff);
@@ -57,4 +58,16 @@ storiesOf("Diff", module)
.add("Binaries", () => {
const binaryDiffFiles = parser.parse(binaryDiff);
return <Diff diff={binaryDiffFiles} />;
})
.add("SyntaxHighlighting", () => {
const filesWithLanguage = diffFiles.map((file: File) => {
const ext = getPath(file).split(".")[1];
if (ext === "tsx") {
file.language = "typescript";
} else {
file.language = ext;
}
return file;
});
return <Diff diff={filesWithLanguage} />;
});

View File

@@ -3,11 +3,12 @@ import { withTranslation, WithTranslation } from "react-i18next";
import classNames from "classnames";
import styled from "styled-components";
// @ts-ignore
import { Diff as DiffComponent, getChangeKey, Hunk, Decoration } from "react-diff-view";
import { getChangeKey, Hunk, Decoration } from "react-diff-view";
import { Button, ButtonGroup } from "../buttons";
import Tag from "../Tag";
import Icon from "../Icon";
import { ChangeEvent, Change, File, Hunk as HunkType, DiffObjectProps } from "./DiffTypes";
import TokenizedDiffView from "./TokenizedDiffView";
const EMPTY_ANNOTATION_FACTORY = {};
@@ -57,33 +58,6 @@ const ChangeTypeTag = styled(Tag)`
margin-left: 0.75rem;
`;
const ModifiedDiffComponent = styled(DiffComponent)`
/* align line numbers */
& .diff-gutter {
text-align: right;
}
/* column sizing */
> colgroup .diff-gutter-col {
width: 3.25rem;
}
/* prevent following content from moving down */
> .diff-gutter:empty:hover::after {
font-size: 0.7rem;
}
/* smaller font size for code */
& .diff-line {
font-size: 0.75rem;
}
/* comment padding for sidebyside view */
&.split .diff-widget-content .is-indented-line {
padding-left: 3.25rem;
}
/* comment padding for combined view */
&.unified .diff-widget-content .is-indented-line {
padding-left: 6.5rem;
}
`;
class DiffFile extends React.Component<Props, State> {
static defaultProps: Partial<Props> = {
defaultCollapse: false,
@@ -264,9 +238,9 @@ class DiffFile extends React.Component<Props, State> {
body = (
<div className="panel-block is-paddingless">
{fileAnnotations}
<ModifiedDiffComponent className={viewType} viewType={viewType} hunks={file.hunks} diffType={file.type}>
<TokenizedDiffView className={viewType} viewType={viewType} file={file}>
{(hunks: HunkType[]) => this.concat(hunks.map(this.renderHunk))}
</ModifiedDiffComponent>
</TokenizedDiffView>
</div>
);
}

View File

@@ -18,6 +18,7 @@ export type File = {
oldPath: string;
oldRevision?: string;
type: FileChangeType;
language?: string;
// TODO does this property exists?
isBinary?: boolean;
};

View File

@@ -0,0 +1,29 @@
/* eslint-disable no-restricted-globals */
// @ts-ignore we have no types for react-diff-view
import { tokenize } from "react-diff-view";
import refractor from "./refractorAdapter";
self.addEventListener("message", ({ data: { id, payload } }) => {
const { hunks, language } = payload;
const options = {
highlight: language !== "text",
language: language,
refractor
};
try {
const tokens = tokenize(hunks, options);
const payload = {
success: true,
tokens: tokens
};
// @ts-ignore seems to use wrong typing
self.postMessage({ id, payload });
} catch (ex) {
const payload = {
success: false,
reason: ex.message
};
// @ts-ignore seems to use wrong typing
self.postMessage({ id, payload });
}
});

View File

@@ -0,0 +1,62 @@
import React, { FC } from "react";
import styled from "styled-components";
// @ts-ignore we have no typings for react-diff-view
import { Diff, useTokenizeWorker } from "react-diff-view";
// @ts-ignore we use webpack worker-loader to load the web worker
import TokenizeWorker from "./Tokenize.worker";
import { File } from "./DiffTypes";
// styling for the diff tokens
// this must be aligned with th style, which is used in the SyntaxHighlighter component
import "highlight.js/styles/arduino-light.css";
const DiffView = styled(Diff)`
/* align line numbers */
& .diff-gutter {
text-align: right;
}
/* column sizing */
> colgroup .diff-gutter-col {
width: 3.25rem;
}
/* prevent following content from moving down */
> .diff-gutter:empty:hover::after {
font-size: 0.7rem;
}
/* smaller font size for code */
& .diff-line {
font-size: 0.75rem;
}
/* comment padding for sidebyside view */
&.split .diff-widget-content .is-indented-line {
padding-left: 3.25rem;
}
/* comment padding for combined view */
&.unified .diff-widget-content .is-indented-line {
padding-left: 6.5rem;
}
`;
// WebWorker which creates tokens for syntax highlighting
const tokenize = new TokenizeWorker();
type Props = {
file: File;
viewType: "split" | "unified";
className?: string;
};
const TokenizedDiffView: FC<Props> = ({ file, viewType, className, children }) => {
const { tokens } = useTokenizeWorker(tokenize, {
hunks: file.hunks,
language: file.language || "text"
});
return (
<DiffView className={className} viewType={viewType} tokens={tokens} hunks={file.hunks} diffType={file.type}>
{children}
</DiffView>
);
};
export default TokenizedDiffView;

View File

@@ -0,0 +1,14 @@
import lowlight from "lowlight";
// adapter to let lowlight look like refractor
// this is required because react-diff-view does only support refractor,
// but we want same highlighting as in the source code browser.
const refractorAdapter = {
...lowlight,
highlight: (value: string, language: string) => {
return lowlight.highlight(language, value).value;
}
};
export default refractorAdapter;