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

@@ -20,6 +20,10 @@ module.exports = {
}
]
},
{
test: /\.worker\.(j|t)s$/,
use: { loader: "worker-loader" }
},
{
test: /\.(css|scss|sass)$/i,
use: [
@@ -38,8 +42,6 @@ module.exports = {
]
},
resolve: {
extensions: [
".ts", ".tsx", ".js", ".jsx", ".css", ".scss", ".json"
]
extensions: [".ts", ".tsx", ".js", ".jsx", ".css", ".scss", ".json"]
}
};

View File

@@ -26,6 +26,7 @@
"@types/enzyme": "^3.10.3",
"@types/fetch-mock": "^7.3.1",
"@types/jest": "^24.0.19",
"@types/lowlight": "^0.0.0",
"@types/query-string": "5",
"@types/react": "^16.9.9",
"@types/react-dom": "^16.9.2",
@@ -40,7 +41,8 @@
"raf": "^3.4.0",
"react-test-renderer": "^16.10.2",
"storybook-addon-i18next": "^1.2.1",
"typescript": "^3.7.2"
"typescript": "^3.7.2",
"worker-loader": "^2.0.0"
},
"dependencies": {
"@scm-manager/ui-extensions": "^2.0.0-SNAPSHOT",
@@ -48,6 +50,8 @@
"classnames": "^2.2.6",
"date-fns": "^2.4.1",
"event-source-polyfill": "^1.0.9",
"gitdiff-parser": "^0.1.2",
"lowlight": "^1.13.0",
"query-string": "5",
"react": "^16.8.6",
"react-diff-view": "^2.4.1",
@@ -56,8 +60,7 @@
"react-markdown": "^4.0.6",
"react-router-dom": "^5.1.2",
"react-select": "^2.1.2",
"react-syntax-highlighter": "^11.0.2",
"gitdiff-parser": "^0.1.2"
"react-syntax-highlighter": "^11.0.2"
},
"babel": {
"presets": [

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;

View File

@@ -24,6 +24,7 @@
"script-loader": "^0.7.2",
"style-loader": "^1.0.0",
"thread-loader": "^2.1.3",
"worker-loader": "^2.0.0",
"webpack": "^4.41.5",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.10.1"

View File

@@ -30,6 +30,10 @@ module.exports = [
systemjs: false
}
},
{
test: /\.worker\.(j|t)s$/,
use: { loader: "worker-loader" }
},
{
test: /\.(js|ts|jsx|tsx)$/i,
exclude: /node_modules/,

View File

@@ -2758,6 +2758,11 @@
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440"
integrity sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==
"@types/lowlight@^0.0.0":
version "0.0.0"
resolved "https://registry.yarnpkg.com/@types/lowlight/-/lowlight-0.0.0.tgz#eef9ff807ff29bbf9f2fe55d0488fb9fc9d89df6"
integrity sha1-7vn/gH/ym7+fL+VdBIj7n8nYnfY=
"@types/minimatch@*":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
@@ -7672,6 +7677,11 @@ highlight.js@~9.13.0:
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.13.1.tgz#054586d53a6863311168488a0f58d6c505ce641e"
integrity sha512-Sc28JNQNDzaH6PORtRLMvif9RSn1mYuOoX3omVjnb0+HbpPygU2ALBI0R/wsiqCb4/fcp07Gdo8g+fhtFrQl6A==
highlight.js@~9.16.0:
version "9.16.2"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.16.2.tgz#68368d039ffe1c6211bcc07e483daf95de3e403e"
integrity sha512-feMUrVLZvjy0oC7FVJQcSQRqbBq9kwqnYE4+Kj9ZjbHh3g+BisiPgF49NyQbVLNdrL/qqZr3Ca9yOKwgn2i/tw==
history@^4.10.1, history@^4.9.0:
version "4.10.1"
resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
@@ -9435,7 +9445,7 @@ loader-runner@^2.3.1, loader-runner@^2.4.0:
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==
loader-utils@1.2.3, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3:
loader-utils@1.2.3, loader-utils@^1.0.0, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
@@ -9602,6 +9612,14 @@ lower-case@^1.1.1:
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw=
lowlight@^1.13.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.13.0.tgz#9b4fd00559985e40e11c916ccab14c7c0cf4320d"
integrity sha512-bFXLa+UO1eM3zieFAcNqf6rTQ1D5ERFv64/euQbbH/LT3U9XXwH6tOrgUAGWDsQ1QgN3ZhgOcv8p3/S+qKGdTQ==
dependencies:
fault "^1.0.2"
highlight.js "~9.16.0"
lowlight@~1.11.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.11.0.tgz#1304d83005126d4e8b1dc0f07981e9b689ec2efc"
@@ -12989,6 +13007,14 @@ scheduler@^0.18.0:
loose-envify "^1.1.0"
object-assign "^4.1.1"
schema-utils@^0.4.0:
version "0.4.7"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187"
integrity sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==
dependencies:
ajv "^6.1.0"
ajv-keywords "^3.1.0"
schema-utils@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
@@ -14972,6 +14998,14 @@ worker-farm@^1.7.0:
dependencies:
errno "~0.1.7"
worker-loader@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-2.0.0.tgz#45fda3ef76aca815771a89107399ee4119b430ac"
integrity sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw==
dependencies:
loader-utils "^1.0.0"
schema-utils "^0.4.0"
worker-rpc@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/worker-rpc/-/worker-rpc-0.1.1.tgz#cb565bd6d7071a8f16660686051e969ad32f54d5"