mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-02 11:35:57 +01:00
Fix freezing browser if syntax highlighting large files (#2010)
We unified syntax highlighting and extracted it into a low level module. Syntax highlighting from now on takes place in a web worker, so that the user interface stays responsive. The web worker stops automatically if the number of nodes which should be highlighted exceeds the defined limit we believe the browser can handle. Co-authored-by: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Co-authored-by: Konstantin Schaper <konstantin.schaper@cloudogu.com>
This commit is contained in:
committed by
GitHub
parent
0e930b8671
commit
a0737a2c90
225
scm-ui/ui-syntaxhighlighting/src/SyntaxHighlighter.stories.tsx
Normal file
225
scm-ui/ui-syntaxhighlighting/src/SyntaxHighlighter.stories.tsx
Normal file
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import React, { FC, Fragment, useEffect, useState } from "react";
|
||||
import SyntaxHighlighter, { Props } from "./SyntaxHighlighter";
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
|
||||
import HttpServerGo from "./__resources__/HttpServer.go";
|
||||
import HttpServerJava from "./__resources__/HttpServer.java";
|
||||
import HttpServerJs from "./__resources__/HttpServer.js";
|
||||
import HttpServerPy from "./__resources__/HttpServer.py";
|
||||
import HttpServerWithMarkerJs from "./__resources__/HttpServerWithMarker.js";
|
||||
import LogbackXml from "./__resources__/Logback.xml";
|
||||
import ReadmeMd from "./__resources__/Readme.md";
|
||||
|
||||
export default {
|
||||
title: "SyntaxHighlighter",
|
||||
component: SyntaxHighlighter,
|
||||
argTypes: {
|
||||
lineWrapper: {
|
||||
table: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
markerConfig: {
|
||||
table: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
renderer: {
|
||||
table: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof SyntaxHighlighter>;
|
||||
|
||||
const Template: ComponentStory<typeof SyntaxHighlighter> = (args: Props) => <SyntaxHighlighter {...args} />;
|
||||
|
||||
export const Go = Template.bind({});
|
||||
Go.args = {
|
||||
language: "go",
|
||||
value: HttpServerGo,
|
||||
};
|
||||
|
||||
export const Java = Template.bind({});
|
||||
Java.args = {
|
||||
language: "java",
|
||||
value: HttpServerJava,
|
||||
};
|
||||
|
||||
export const JavaScript = Template.bind({});
|
||||
JavaScript.args = {
|
||||
language: "javascript",
|
||||
value: HttpServerJs,
|
||||
};
|
||||
|
||||
export const Python = Template.bind({});
|
||||
Python.args = {
|
||||
language: "python",
|
||||
value: HttpServerPy,
|
||||
};
|
||||
|
||||
const LineWrapper: FC<{ lineNumber: number }> = ({ lineNumber, children }) => (
|
||||
<div>
|
||||
<span style={{ width: "2rem", display: "inline-block" }}>{lineNumber}</span>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const LineNumbers = Template.bind({});
|
||||
LineNumbers.args = {
|
||||
language: "go",
|
||||
value: HttpServerGo,
|
||||
lineWrapper: LineWrapper,
|
||||
};
|
||||
|
||||
export const LargeCss: ComponentStory<typeof SyntaxHighlighter> = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [content, setContent] = useState<string>();
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
fetch("/large.css")
|
||||
.then((r) => r.text())
|
||||
.then((text) => setContent(text))
|
||||
.finally(() => setIsLoading(false));
|
||||
}, []);
|
||||
|
||||
if (isLoading || !content) {
|
||||
return <p>Loading ...</p>;
|
||||
}
|
||||
|
||||
return <SyntaxHighlighter language="css" value={content} lineWrapper={LineWrapper} />;
|
||||
};
|
||||
|
||||
export const HugeCss: ComponentStory<typeof SyntaxHighlighter> = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [content, setContent] = useState<string>();
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
fetch("/next.css")
|
||||
.then((r) => r.text())
|
||||
.then((text) => setContent(text))
|
||||
.finally(() => setIsLoading(false));
|
||||
}, []);
|
||||
|
||||
if (isLoading || !content) {
|
||||
return <p>Loading ...</p>;
|
||||
}
|
||||
|
||||
return <SyntaxHighlighter language="css" value={content} lineWrapper={LineWrapper} />;
|
||||
};
|
||||
|
||||
const Mark: FC = ({ children }) => <mark>{children}</mark>;
|
||||
|
||||
const markerConfig = {
|
||||
start: "<|[[--",
|
||||
end: "--]]|>",
|
||||
wrapper: Mark,
|
||||
};
|
||||
|
||||
export const Marker = Template.bind({});
|
||||
Marker.args = {
|
||||
language: "javascript",
|
||||
value: HttpServerWithMarkerJs,
|
||||
markerConfig,
|
||||
};
|
||||
|
||||
export const MarkerWithLineNumbers = Template.bind({});
|
||||
MarkerWithLineNumbers.args = {
|
||||
language: "javascript",
|
||||
value: HttpServerWithMarkerJs,
|
||||
lineWrapper: LineWrapper,
|
||||
markerConfig,
|
||||
};
|
||||
|
||||
export const MarkerWithLineNumbersAndLimit = Template.bind({});
|
||||
MarkerWithLineNumbersAndLimit.args = {
|
||||
language: "javascript",
|
||||
value: HttpServerWithMarkerJs,
|
||||
nodeLimit: 10,
|
||||
lineWrapper: LineWrapper,
|
||||
markerConfig: {
|
||||
start: "<|[[--",
|
||||
end: "--]]|>",
|
||||
wrapper: Mark,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithRenderer = Template.bind({});
|
||||
WithRenderer.args = {
|
||||
language: "javascript",
|
||||
value: HttpServerJs,
|
||||
renderer: ({ children }) => (
|
||||
<table>
|
||||
<tbody>
|
||||
{children.map((child, i) => (
|
||||
<tr key={i}>
|
||||
<td style={{ border: "1px solid black" }}>{i + 1}</td>
|
||||
<td style={{ border: "1px solid black" }}>{child}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
),
|
||||
};
|
||||
|
||||
export const WithRendererWithLimit = Template.bind({});
|
||||
WithRendererWithLimit.args = {
|
||||
language: "javascript",
|
||||
value: HttpServerJs,
|
||||
nodeLimit: 10,
|
||||
renderer: ({ children }) => (
|
||||
<table style={{ border: 1 }}>
|
||||
<tbody>
|
||||
{children.map((child, i) => (
|
||||
<tr key={i}>
|
||||
<td style={{ border: "1px solid black" }}>{i + 1}</td>
|
||||
<td style={{ border: "1px solid black" }}>{child}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
),
|
||||
};
|
||||
|
||||
export const XmlAsMarkup = Template.bind({});
|
||||
XmlAsMarkup.args = {
|
||||
language: "xml",
|
||||
value: LogbackXml,
|
||||
};
|
||||
|
||||
export const RenderAsFragment: ComponentStory<typeof SyntaxHighlighter> = () => (
|
||||
<>
|
||||
<h2>This is rendered as fragment without code or pre tags</h2>
|
||||
<SyntaxHighlighter value={HttpServerGo} language="go" as={Fragment} />
|
||||
</>
|
||||
);
|
||||
|
||||
export const MarkdownWithFrontmatter = Template.bind({});
|
||||
MarkdownWithFrontmatter.args = {
|
||||
language: "markdown",
|
||||
value: ReadmeMd,
|
||||
};
|
||||
254
scm-ui/ui-syntaxhighlighting/src/SyntaxHighlighter.tsx
Normal file
254
scm-ui/ui-syntaxhighlighting/src/SyntaxHighlighter.tsx
Normal file
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import React, { ComponentType, FC, ReactChild, ReactNode, useMemo } from "react";
|
||||
import useSyntaxHighlighting from "./useSyntaxHighlighting";
|
||||
import type { RefractorElement } from "refractor";
|
||||
import type { Element } from "hast";
|
||||
import type { MarkerBounds, RefractorNode } from "./types";
|
||||
import { SplitAndReplace } from "@scm-manager/ui-text";
|
||||
|
||||
type LineWrapperType = ComponentType<{ lineNumber: number }>;
|
||||
type MarkerConfig = MarkerBounds & {
|
||||
wrapper: ComponentType;
|
||||
};
|
||||
type RendererType = ComponentType<{ children: ReactChild[] }>;
|
||||
|
||||
type Replacement = {
|
||||
textToReplace: string;
|
||||
replacement: ReactNode;
|
||||
replaceAll: boolean;
|
||||
};
|
||||
|
||||
const DEFAULT_RENDERER = React.Fragment;
|
||||
const DEFAULT_LINE_WRAPPER: LineWrapperType = ({ children }) => <>{children}</>;
|
||||
|
||||
export type Props = {
|
||||
value: string;
|
||||
language?: string;
|
||||
lineWrapper?: LineWrapperType;
|
||||
markerConfig?: MarkerConfig;
|
||||
nodeLimit?: number;
|
||||
renderer?: RendererType;
|
||||
as?: ComponentType;
|
||||
};
|
||||
|
||||
function mapWithDepth(depth: number, lineWrapper?: LineWrapperType, markerReplacement?: ComponentType) {
|
||||
return function mapChildrenWithDepth(child: RefractorNode, i: number) {
|
||||
return mapChild(child, i, depth, lineWrapper, markerReplacement);
|
||||
};
|
||||
}
|
||||
|
||||
const isRefractorElement = (node: RefractorNode): node is RefractorElement => "tagName" in node;
|
||||
|
||||
function mapChild(
|
||||
childNode: RefractorNode,
|
||||
i: number,
|
||||
depth: number,
|
||||
LineWrapper?: LineWrapperType,
|
||||
MarkerReplacement?: ComponentType
|
||||
): ReactChild {
|
||||
if (isRefractorElement(childNode)) {
|
||||
const child = childNode as Element;
|
||||
const className =
|
||||
child.properties &&
|
||||
(Array.isArray(child.properties.className) ? child.properties.className.join(" ") : child.properties.className);
|
||||
|
||||
if (child.properties) {
|
||||
if (LineWrapper) {
|
||||
const line = child.properties["data-line-number"];
|
||||
if (line) {
|
||||
return (
|
||||
<LineWrapper key={`line-${depth}-${i}`} lineNumber={Number(line)}>
|
||||
{childNode.children && childNode.children.map(mapWithDepth(depth + 1, LineWrapper, MarkerReplacement))}
|
||||
</LineWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
if (MarkerReplacement) {
|
||||
const isMarked = child.properties["data-marked"];
|
||||
if (isMarked) {
|
||||
return (
|
||||
<MarkerReplacement key={`marker-${depth}-${i}`}>
|
||||
{childNode.children && childNode.children.map(mapWithDepth(depth + 1, LineWrapper, MarkerReplacement))}
|
||||
</MarkerReplacement>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return React.createElement(
|
||||
child.tagName,
|
||||
Object.assign({ key: `fract-${depth}-${i}` }, child.properties, { className }),
|
||||
childNode.children && childNode.children.map(mapWithDepth(depth + 1, LineWrapper, MarkerReplacement))
|
||||
);
|
||||
}
|
||||
|
||||
return <React.Fragment key={`content-${depth}-${i}`}>{childNode.value}</React.Fragment>;
|
||||
}
|
||||
|
||||
function escapeRegExp(str: string) {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
|
||||
}
|
||||
|
||||
const stripAndReplace = (value: string, { start: preTag, end: postTag, wrapper: Wrapper }: MarkerConfig) => {
|
||||
const strippedValue = value
|
||||
.replace(new RegExp(escapeRegExp(preTag), "g"), "")
|
||||
.replace(new RegExp(escapeRegExp(postTag), "g"), "");
|
||||
let content = value;
|
||||
|
||||
const result: string[] = [];
|
||||
while (content.length > 0) {
|
||||
const start = content.indexOf(preTag);
|
||||
const end = content.indexOf(postTag);
|
||||
if (start >= 0 && end > 0) {
|
||||
const item = content.substring(start + preTag.length, end);
|
||||
if (!result.includes(item)) {
|
||||
result.push(item);
|
||||
}
|
||||
content = content.substring(end + postTag.length);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result.sort((a, b) => b.length - a.length);
|
||||
|
||||
return {
|
||||
strippedValue,
|
||||
replacements: result.map<Replacement>((textToReplace) => ({
|
||||
textToReplace,
|
||||
replacement: <Wrapper>{textToReplace}</Wrapper>,
|
||||
replaceAll: true,
|
||||
})),
|
||||
};
|
||||
};
|
||||
|
||||
const markContent = (value: string, markerConfig?: MarkerConfig) =>
|
||||
markerConfig === undefined ? { strippedValue: value, replacements: [] } : stripAndReplace(value, markerConfig);
|
||||
|
||||
const createFallbackContent = (
|
||||
value: string,
|
||||
lineWrapper?: LineWrapperType,
|
||||
renderer?: RendererType,
|
||||
replacements: Replacement[] = []
|
||||
): ReactNode => {
|
||||
if (lineWrapper || renderer) {
|
||||
const Renderer = renderer ?? DEFAULT_RENDERER;
|
||||
const LineWrapper = lineWrapper ?? DEFAULT_LINE_WRAPPER;
|
||||
return (
|
||||
<Renderer>
|
||||
{value.split("\n").map((line, i) => (
|
||||
<LineWrapper key={i} lineNumber={i + 1}>
|
||||
{replacements.length ? (
|
||||
<SplitAndReplace key={`fract-${i}`} text={line} replacements={replacements} textWrapper={(s) => s} />
|
||||
) : (
|
||||
line
|
||||
)}
|
||||
</LineWrapper>
|
||||
))}
|
||||
</Renderer>
|
||||
);
|
||||
}
|
||||
return <SplitAndReplace key="fract" text={value} replacements={replacements} textWrapper={(s) => s} />;
|
||||
};
|
||||
|
||||
const useMarkedContent = (value: string, markerConfig?: MarkerConfig) => {
|
||||
return useMemo(() => {
|
||||
const markedContent = markContent(value, markerConfig);
|
||||
return {
|
||||
...markedContent,
|
||||
markedTexts: markedContent.replacements.length
|
||||
? markedContent.replacements.map((replacement) => replacement.textToReplace)
|
||||
: undefined,
|
||||
};
|
||||
}, [value, markerConfig]);
|
||||
};
|
||||
|
||||
const useFallbackContent = (
|
||||
strippedValue: string,
|
||||
replacements: Replacement[],
|
||||
lineWrapper?: LineWrapperType,
|
||||
renderer?: RendererType
|
||||
) => {
|
||||
return useMemo(
|
||||
() => createFallbackContent(strippedValue, lineWrapper, renderer, replacements),
|
||||
[strippedValue, lineWrapper, renderer, replacements]
|
||||
);
|
||||
};
|
||||
|
||||
type RootWrapperProps = {
|
||||
language: string;
|
||||
as?: ComponentType;
|
||||
};
|
||||
|
||||
const RootWrapper: FC<RootWrapperProps> = ({ language, children, as: AsComponent }) => {
|
||||
if (!AsComponent) {
|
||||
return (
|
||||
<pre className={language ? `language-${language}` : ""}>
|
||||
<code>{children}</code>
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
|
||||
return <AsComponent>{children}</AsComponent>;
|
||||
};
|
||||
|
||||
const SyntaxHighlighter = ({
|
||||
value,
|
||||
lineWrapper,
|
||||
language = "text",
|
||||
markerConfig,
|
||||
nodeLimit = 10000,
|
||||
renderer,
|
||||
as,
|
||||
}: Props) => {
|
||||
const { strippedValue, replacements, markedTexts } = useMarkedContent(value, markerConfig);
|
||||
const { isLoading, tree, error } = useSyntaxHighlighting({
|
||||
value: strippedValue,
|
||||
language,
|
||||
nodeLimit,
|
||||
groupByLine: !!lineWrapper || !!renderer,
|
||||
markedTexts: markedTexts,
|
||||
});
|
||||
const fallbackContent = useFallbackContent(strippedValue, replacements, lineWrapper, renderer);
|
||||
const Renderer = renderer ?? DEFAULT_RENDERER;
|
||||
|
||||
// we do not expose the error for now, because we have no idea how to display it
|
||||
if (isLoading || !tree || error) {
|
||||
return (
|
||||
<RootWrapper as={as} language={language}>
|
||||
<code>{fallbackContent}</code>
|
||||
</RootWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RootWrapper as={as} language={language}>
|
||||
<Renderer>{tree?.map(mapWithDepth(0, lineWrapper, markerConfig?.wrapper))}</Renderer>
|
||||
</RootWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default SyntaxHighlighter;
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
export default `package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Welcome to my website!")
|
||||
})
|
||||
|
||||
fs := http.FileServer(http.Dir("static/"))
|
||||
http.Handle("/static/", http.StripPrefix("/static/", fs))
|
||||
|
||||
http.ListenAndServe(":80", nil)
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
export default `package com.example;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
|
||||
public class Test {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
|
||||
server.createContext("/test", new MyHandler());
|
||||
server.setExecutor(null); // creates a default executor
|
||||
server.start();
|
||||
}
|
||||
|
||||
static class MyHandler implements HttpHandler {
|
||||
@Override
|
||||
public void handle(HttpExchange t) throws IOException {
|
||||
String response = "This is the response";
|
||||
t.sendResponseHeaders(200, response.length());
|
||||
OutputStream os = t.getResponseBody();
|
||||
os.write(response.getBytes());
|
||||
os.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
export default `var http = require('http');
|
||||
|
||||
http.createServer(function (req, res) {
|
||||
res.writeHead(200, {'Content-Type': 'text/plain'});
|
||||
res.write('Hello World!');
|
||||
res.end();
|
||||
}).listen(8080);
|
||||
`;
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
export default `from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer
|
||||
|
||||
PORT_NUMBER = 8080
|
||||
|
||||
class myHandler(BaseHTTPRequestHandler):
|
||||
|
||||
def do_GET(self):
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type','text/html')
|
||||
self.end_headers()
|
||||
self.wfile.write("Hello World !")
|
||||
return
|
||||
|
||||
try:
|
||||
server = HTTPServer(('', PORT_NUMBER), myHandler)
|
||||
print 'Started httpserver on port ' , PORT_NUMBER
|
||||
server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
print '^C received, shutting down the web server'
|
||||
server.socket.close()
|
||||
`;
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
export default `var <|[[--http--]]|> = require('<|[[--http--]]|>');
|
||||
|
||||
<|[[--http--]]|>.createServer(function (req, res) {
|
||||
res.writeHead(200, {'Content-Type': 'text/plain'});
|
||||
res.write('Hello World!');
|
||||
res.end();
|
||||
}).listen(8080);
|
||||
`;
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
export default `<configuration>
|
||||
|
||||
<jmxConfigurator />
|
||||
|
||||
<define name="LOGDIR" class="sonia.scm.ScmLogFilePropertyDefiner" />
|
||||
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>/scm-manager.log</file>
|
||||
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
|
||||
<fileNamePattern>/scm-manager-%i.log</fileNamePattern>
|
||||
<minIndex>1</minIndex>
|
||||
<maxIndex>10</maxIndex>
|
||||
</rollingPolicy>
|
||||
|
||||
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
|
||||
<maxFileSize>10MB</maxFileSize>
|
||||
</triggeringPolicy>
|
||||
|
||||
<append>true</append>
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-10X{transaction_id}] %-5level %logger - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
</configuration>
|
||||
`;
|
||||
31
scm-ui/ui-syntaxhighlighting/src/__resources__/Readme.md.ts
Normal file
31
scm-ui/ui-syntaxhighlighting/src/__resources__/Readme.md.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
export default `---
|
||||
title: README
|
||||
filename: README.md
|
||||
---
|
||||
# README
|
||||
This is a readme file!
|
||||
`;
|
||||
26
scm-ui/ui-syntaxhighlighting/src/index.ts
Normal file
26
scm-ui/ui-syntaxhighlighting/src/index.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
export { default as SyntaxHighlighter } from "./SyntaxHighlighter";
|
||||
export { default as useSyntaxHighlightingWorker } from "./useSyntaxHighlightingWorker";
|
||||
194
scm-ui/ui-syntaxhighlighting/src/syntax-highlighting.module.css
Normal file
194
scm-ui/ui-syntaxhighlighting/src/syntax-highlighting.module.css
Normal file
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
/* Generated with http://k88hudson.github.io/syntax-highlighting-theme-generator/www */
|
||||
/* http://k88hudson.github.io/react-markdocs */
|
||||
|
||||
/**
|
||||
* @author k88hudson
|
||||
*
|
||||
* Based on prism.js default theme for JavaScript, CSS and HTML
|
||||
* Based on dabblet (http://dabblet.com)
|
||||
* @author Lea Verou
|
||||
*/
|
||||
|
||||
/*********************************************************
|
||||
* General
|
||||
*/
|
||||
|
||||
pre[class*="language-"],
|
||||
code[class*="language-"] {
|
||||
color: var(--sh-base-color);
|
||||
font-size: 1rem;
|
||||
text-shadow: none;
|
||||
font-family: var(--sh-font-family);
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
line-height: 1.5;
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::selection,
|
||||
code[class*="language-"]::selection,
|
||||
pre[class*="language-"]::-moz-selection,
|
||||
code[class*="language-"]::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: var(--sh-selected-color);
|
||||
}
|
||||
|
||||
@media print {
|
||||
|
||||
pre[class*="language-"],
|
||||
code[class*="language-"] {
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
background: var(--sh-block-background);
|
||||
}
|
||||
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: .1em .3em;
|
||||
border-radius: .3em;
|
||||
color: var(--sh-inline-code-color);
|
||||
background: var(--sh-inline-code-background);
|
||||
}
|
||||
|
||||
/*********************************************************
|
||||
* Tokens
|
||||
*/
|
||||
|
||||
.namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: var(--sh-comment-color);
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: var(--sh-punctuation-color);
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: var(--sh-property-color);
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: var(--sh-selector-color);
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string {
|
||||
color: var(--sh-operator-color);
|
||||
background: var(--sh-operator-bg);
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword {
|
||||
color: var(--sh-keyword-color);
|
||||
}
|
||||
|
||||
.token.function {
|
||||
color: var(--sh-function-color);
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important,
|
||||
.token.variable {
|
||||
color: var(--sh-variable-color);
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold,
|
||||
.token.title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
/*********************************************************
|
||||
* Line highlighting
|
||||
*/
|
||||
|
||||
pre[data-line] {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
pre[class*="language-"] > code[class*="language-"] {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.line-highlight {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: inherit 0;
|
||||
margin-top: 1em;
|
||||
background: var(--sh-highlight-background);
|
||||
box-shadow: inset 5px 0 0 var(--sh-highlight-accent);
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
line-height: inherit;
|
||||
white-space: pre;
|
||||
}
|
||||
90
scm-ui/ui-syntaxhighlighting/src/types.ts
Normal file
90
scm-ui/ui-syntaxhighlighting/src/types.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import type { RefractorElement, Text } from "refractor/lib/core";
|
||||
|
||||
export type Message<T> = { data: T };
|
||||
export type MessageData<T extends string, P> = { id: string; type: T; payload: P };
|
||||
|
||||
export type RefractorNode = RefractorElement | Text;
|
||||
export type Theme = Record<string, string>;
|
||||
|
||||
export type MarkerBounds = {
|
||||
start: string;
|
||||
end: string;
|
||||
};
|
||||
|
||||
export type LoadThemeRequest = MessageData<"theme", Theme>;
|
||||
|
||||
export type HighlightingRequest = MessageData<
|
||||
"highlight",
|
||||
{
|
||||
language: string;
|
||||
value: string;
|
||||
nodeLimit: number;
|
||||
groupByLine: boolean;
|
||||
markedTexts?: string[];
|
||||
}
|
||||
>;
|
||||
|
||||
export type TokenizeRequest = MessageData<
|
||||
"tokenize",
|
||||
{
|
||||
language: string;
|
||||
hunks: unknown;
|
||||
}
|
||||
>;
|
||||
|
||||
export type Request = LoadThemeRequest | HighlightingRequest | TokenizeRequest;
|
||||
export type RequestMessage = Message<Request>;
|
||||
|
||||
export type SuccessResponse = MessageData<"success", { tree: Array<RefractorNode> }>;
|
||||
|
||||
export type FailureResponse = MessageData<"failure", { reason: string }>;
|
||||
|
||||
export type Response = SuccessResponse | FailureResponse;
|
||||
export type ResponseMessage = Message<Response>;
|
||||
|
||||
export function isRefractorElement(node: RefractorNode): node is RefractorElement {
|
||||
return (node as RefractorElement).tagName !== undefined;
|
||||
}
|
||||
|
||||
export type TokenizeSuccessResponse = {
|
||||
id: string;
|
||||
payload: {
|
||||
success: true;
|
||||
tokens: {
|
||||
old: Array<RefractorNode>;
|
||||
new: Array<RefractorNode>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export type TokenizeFailureResponse = {
|
||||
id: string;
|
||||
payload: {
|
||||
success: false;
|
||||
reason: string;
|
||||
};
|
||||
};
|
||||
96
scm-ui/ui-syntaxhighlighting/src/useSyntaxHighlighting.ts
Normal file
96
scm-ui/ui-syntaxhighlighting/src/useSyntaxHighlighting.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import { nanoid } from "nanoid";
|
||||
import type { HighlightingRequest, RefractorNode, Response, ResponseMessage, SuccessResponse } from "./types";
|
||||
import useSyntaxHighlightingWorker from "./useSyntaxHighlightingWorker";
|
||||
|
||||
const isSuccess = (response: Response): response is SuccessResponse => {
|
||||
return response.type === "success";
|
||||
};
|
||||
|
||||
export type UseSyntaxHighlightingOptions = {
|
||||
value: string;
|
||||
language: string;
|
||||
nodeLimit: number;
|
||||
groupByLine?: boolean;
|
||||
markedTexts?: string[];
|
||||
};
|
||||
|
||||
const useSyntaxHighlighting = ({
|
||||
value,
|
||||
language,
|
||||
nodeLimit,
|
||||
groupByLine = false,
|
||||
markedTexts,
|
||||
}: UseSyntaxHighlightingOptions) => {
|
||||
const worker = useSyntaxHighlightingWorker();
|
||||
const job = useRef(nanoid());
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [tree, setTree] = useState<Array<RefractorNode> | undefined>(undefined);
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
setError(undefined);
|
||||
setTree(undefined);
|
||||
|
||||
const payload: HighlightingRequest["payload"] = {
|
||||
value,
|
||||
language,
|
||||
nodeLimit,
|
||||
groupByLine,
|
||||
markedTexts,
|
||||
};
|
||||
|
||||
worker.postMessage({
|
||||
id: job.current,
|
||||
payload,
|
||||
} as HighlightingRequest);
|
||||
|
||||
const listener = ({ data }: ResponseMessage) => {
|
||||
if (data.id === job.current) {
|
||||
if (isSuccess(data)) {
|
||||
setTree(data.payload.tree);
|
||||
} else {
|
||||
setError(data.payload.reason);
|
||||
}
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
worker.addEventListener("message", listener);
|
||||
return () => worker.removeEventListener("message", listener);
|
||||
}, [value, language, nodeLimit, groupByLine, worker, markedTexts]);
|
||||
|
||||
return {
|
||||
tree,
|
||||
isLoading,
|
||||
error,
|
||||
};
|
||||
};
|
||||
|
||||
export default useSyntaxHighlighting;
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
// WebWorker which creates tokens for syntax highlighting
|
||||
// @ts-ignore typescript does not know the css module syntax
|
||||
import theme from "./syntax-highlighting.module.css";
|
||||
import type { LoadThemeRequest } from "./types";
|
||||
|
||||
const worker = new Worker(
|
||||
// @ts-ignore typscript does not know the import.meta object
|
||||
new URL("./worker/SyntaxHighlighter.worker.ts", import.meta.url),
|
||||
{
|
||||
name: "SyntaxHighlighter",
|
||||
type: "module",
|
||||
}
|
||||
);
|
||||
|
||||
worker.postMessage({ type: "theme", payload: theme } as LoadThemeRequest);
|
||||
|
||||
const useSyntaxHighlightingWorker = (): Worker => worker;
|
||||
export default useSyntaxHighlightingWorker;
|
||||
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import groupByLines from "./groupByLines";
|
||||
// @ts-ignore we have no types for react-diff-view
|
||||
import { tokenize } from "react-diff-view";
|
||||
import createRefractor, { RefractorAdapter } from "./refractorAdapter";
|
||||
import type {
|
||||
FailureResponse,
|
||||
HighlightingRequest,
|
||||
LoadThemeRequest,
|
||||
RefractorNode,
|
||||
Request,
|
||||
RequestMessage,
|
||||
SuccessResponse,
|
||||
Theme,
|
||||
TokenizeFailureResponse,
|
||||
TokenizeRequest,
|
||||
TokenizeSuccessResponse,
|
||||
} from "../types";
|
||||
import { isRefractorElement } from "../types";
|
||||
|
||||
// the WorkerGlobalScope is assigned to self
|
||||
// see https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/self
|
||||
declare const self: Worker;
|
||||
|
||||
let refractor: RefractorAdapter;
|
||||
|
||||
function initRefractor(theme: Theme) {
|
||||
refractor = createRefractor(theme);
|
||||
}
|
||||
|
||||
const countChildrenAndApplyMarkers = (node: RefractorNode, markedTexts?: string[]) => {
|
||||
if (isRefractorElement(node)) {
|
||||
return countAndMarkNodes(node.children, markedTexts);
|
||||
}
|
||||
|
||||
if (!markedTexts || markedTexts.length === 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
let content = node.value;
|
||||
const newChildren: RefractorNode[] = [];
|
||||
while (content.length) {
|
||||
let foundSomething = false;
|
||||
for (const markedText of markedTexts) {
|
||||
const start = content.indexOf(markedText);
|
||||
if (start >= 0) {
|
||||
foundSomething = true;
|
||||
const end = start + markedText.length;
|
||||
newChildren.push({ type: "text", value: content.substring(0, start) });
|
||||
newChildren.push({
|
||||
type: "element",
|
||||
tagName: "mark",
|
||||
properties: {
|
||||
"data-marked": true,
|
||||
},
|
||||
children: [{ type: "text", value: content.substring(start, end) }],
|
||||
});
|
||||
content = content.substring(end);
|
||||
}
|
||||
}
|
||||
if (!foundSomething) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (content.length) {
|
||||
newChildren.push({ type: "text", value: content });
|
||||
}
|
||||
|
||||
if (newChildren.length > 0) {
|
||||
const el = node as RefractorNode;
|
||||
el.type = "element";
|
||||
el.tagName = "span";
|
||||
el.children = newChildren;
|
||||
return newChildren.length + 1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
};
|
||||
const countAndMarkNodes = (nodes: RefractorNode[], markedTexts?: string[]): number =>
|
||||
nodes.reduce((count, node) => count + countChildrenAndApplyMarkers(node, markedTexts), 0);
|
||||
|
||||
const doHighlighting = (
|
||||
worker: Worker,
|
||||
{ id, payload: { value, language, nodeLimit, groupByLine, markedTexts } }: HighlightingRequest
|
||||
) => {
|
||||
try {
|
||||
let tree = refractor.highlight(value, language).children;
|
||||
if (groupByLine) {
|
||||
try {
|
||||
tree = groupByLines(tree, nodeLimit);
|
||||
} catch (e) {
|
||||
const payload: FailureResponse["payload"] = {
|
||||
reason: e instanceof Error ? e.message : `node limit of ${nodeLimit} reached.`,
|
||||
};
|
||||
worker.postMessage({ id, type: "failure", payload } as FailureResponse);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeLimit > 0) {
|
||||
const count = countAndMarkNodes(tree, markedTexts);
|
||||
if (count > nodeLimit) {
|
||||
const payload: FailureResponse["payload"] = {
|
||||
reason: `node limit of ${nodeLimit} reached. Total nodes ${count}.`,
|
||||
};
|
||||
worker.postMessage({ id, type: "failure", payload } as FailureResponse);
|
||||
} else {
|
||||
const payload: SuccessResponse["payload"] = {
|
||||
tree,
|
||||
};
|
||||
worker.postMessage({ id, type: "success", payload } as SuccessResponse);
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
const payload: FailureResponse["payload"] = {
|
||||
reason: String(ex),
|
||||
};
|
||||
worker.postMessage({ id, type: "failure", payload } as FailureResponse);
|
||||
}
|
||||
};
|
||||
|
||||
const isLoadThemeMessage = (message: Request): message is LoadThemeRequest => {
|
||||
return message.type === "theme";
|
||||
};
|
||||
|
||||
const isTokenizeMessage = (message: Request): message is TokenizeRequest => {
|
||||
return message.type === "tokenize";
|
||||
};
|
||||
|
||||
const TOKENIZE_NODE_LIMIT = 600;
|
||||
|
||||
const runTokenize = ({ id, payload }: TokenizeRequest) => {
|
||||
const { hunks, language } = payload;
|
||||
|
||||
const options = {
|
||||
highlight: language !== "text",
|
||||
language: language,
|
||||
refractor: {
|
||||
...refractor,
|
||||
highlight: (value: string, lang: string) => {
|
||||
return refractor.highlight(value, lang).children;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const doTokenization = (worker: Worker) => {
|
||||
try {
|
||||
const tokens: { old: RefractorNode[]; new: RefractorNode[] } = tokenize(hunks, options);
|
||||
const tokensCount = countAndMarkNodes(tokens.old) + countAndMarkNodes(tokens.new);
|
||||
if (tokensCount > TOKENIZE_NODE_LIMIT) {
|
||||
const response: TokenizeFailureResponse = {
|
||||
id,
|
||||
payload: {
|
||||
success: false,
|
||||
reason: `Node limit (${TOKENIZE_NODE_LIMIT}) reached. Current nodes: ${tokensCount}`,
|
||||
},
|
||||
};
|
||||
worker.postMessage(response);
|
||||
} else {
|
||||
const response: TokenizeSuccessResponse = {
|
||||
id,
|
||||
payload: {
|
||||
success: true,
|
||||
tokens,
|
||||
},
|
||||
};
|
||||
worker.postMessage(response);
|
||||
}
|
||||
} catch (ex) {
|
||||
const response: TokenizeFailureResponse = {
|
||||
id,
|
||||
payload: {
|
||||
success: false,
|
||||
reason: String(ex),
|
||||
},
|
||||
};
|
||||
worker.postMessage(response);
|
||||
}
|
||||
};
|
||||
|
||||
const createTokenizer = (worker: Worker) => () => doTokenization(worker);
|
||||
|
||||
if (options.highlight) {
|
||||
refractor.loadLanguage(language, createTokenizer(self));
|
||||
}
|
||||
};
|
||||
|
||||
self.addEventListener("message", ({ data }: RequestMessage) => {
|
||||
if (isLoadThemeMessage(data)) {
|
||||
initRefractor(data.payload);
|
||||
} else if (isTokenizeMessage(data)) {
|
||||
runTokenize(data);
|
||||
} else if (data.payload.language !== "text") {
|
||||
refractor.loadLanguage(data.payload.language, () => doHighlighting(self, data));
|
||||
}
|
||||
});
|
||||
30
scm-ui/ui-syntaxhighlighting/src/worker/dependencies.ts
Normal file
30
scm-ui/ui-syntaxhighlighting/src/worker/dependencies.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
const dependencies: { [lang: string]: string[] } = {
|
||||
// Markdown depends on yaml because of frontmatter
|
||||
markdown: ["yaml"],
|
||||
};
|
||||
|
||||
export default dependencies;
|
||||
76
scm-ui/ui-syntaxhighlighting/src/worker/flatten.ts
Normal file
76
scm-ui/ui-syntaxhighlighting/src/worker/flatten.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
/*eslint-disable */
|
||||
import type { Element } from "hast";
|
||||
import { isRefractorElement, RefractorNode } from "../types";
|
||||
|
||||
function flatten(
|
||||
nodes: RefractorNode[],
|
||||
nodeLimit = Number.MAX_SAFE_INTEGER,
|
||||
className: string[] = [],
|
||||
totalNodes = nodes.length
|
||||
): FlatNodes {
|
||||
const result: FlatNodes = [];
|
||||
for (const node of nodes) {
|
||||
if (isRefractorElement(node)) {
|
||||
const subNodes = flatten(
|
||||
node.children,
|
||||
nodeLimit,
|
||||
[...className, ...(((node as Element).properties?.className as string[]) ?? [])],
|
||||
totalNodes
|
||||
);
|
||||
totalNodes += subNodes.length;
|
||||
result.push(...subNodes);
|
||||
} else if (className.length) {
|
||||
result.push({
|
||||
type: "element",
|
||||
tagName: "span",
|
||||
properties: { className: Array.from(new Set(className)) },
|
||||
children: [node],
|
||||
});
|
||||
} else {
|
||||
result.push(node);
|
||||
}
|
||||
}
|
||||
if (totalNodes > nodeLimit) {
|
||||
throw new Error(`Node limit (${nodeLimit}) reached. Current nodes ${totalNodes}`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export default flatten;
|
||||
|
||||
export type FlatNodes = FlatNode[];
|
||||
export type FlatNode = FlatElement | FlatText;
|
||||
export type FlatElement = {
|
||||
type: "element";
|
||||
tagName: "span";
|
||||
properties: { className: string[] };
|
||||
children: [FlatText];
|
||||
};
|
||||
export type FlatText = {
|
||||
type: "text";
|
||||
value: string;
|
||||
};
|
||||
99
scm-ui/ui-syntaxhighlighting/src/worker/groupByLines.ts
Normal file
99
scm-ui/ui-syntaxhighlighting/src/worker/groupByLines.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
/*eslint-disable */
|
||||
import flatten, { FlatElement, FlatNode, FlatNodes, FlatText } from "./flatten";
|
||||
import type { RefractorNode } from "../types";
|
||||
|
||||
function groupByLines(nodes: RefractorNode[], nodeLimit?: number): Array<LineElement> {
|
||||
return group(flatten(nodes, nodeLimit));
|
||||
}
|
||||
|
||||
export default groupByLines;
|
||||
|
||||
export type LineElement = {
|
||||
type: "element";
|
||||
tagName: "span";
|
||||
properties: {
|
||||
className: ["line"];
|
||||
["data-line-number"]: number;
|
||||
};
|
||||
children: FlatNodes;
|
||||
};
|
||||
export { FlatNodes, FlatNode, FlatElement, FlatText };
|
||||
|
||||
function group(nodes: FlatNodes): Array<LineElement> {
|
||||
const lineElements: Array<LineElement> = [];
|
||||
let currentLine = createLineElement(1);
|
||||
for (const node of nodes) {
|
||||
const lines = splitByLines(node);
|
||||
const last = lines.length - 1;
|
||||
lines.forEach((line, index) => {
|
||||
if (line.type !== "text" || line.value !== "") {
|
||||
currentLine.children.push(line);
|
||||
}
|
||||
if (index !== last) {
|
||||
lineElements.push(currentLine);
|
||||
currentLine = createLineElement(lineElements.length + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (currentLine.children.length > 0) {
|
||||
lineElements.push(currentLine);
|
||||
}
|
||||
return lineElements;
|
||||
}
|
||||
|
||||
function createLineElement(lineNumber: number): LineElement {
|
||||
return {
|
||||
type: "element",
|
||||
tagName: "span",
|
||||
properties: {
|
||||
className: ["line"],
|
||||
["data-line-number"]: lineNumber,
|
||||
},
|
||||
children: [],
|
||||
};
|
||||
}
|
||||
|
||||
function splitByLines(node: FlatNode): FlatNodes {
|
||||
if (node.type === "text") {
|
||||
return splitTextByLines(node);
|
||||
} else {
|
||||
const texts = splitTextByLines(node.children[0]);
|
||||
return texts.map<FlatElement>((text) => ({ ...node, children: [text] }));
|
||||
}
|
||||
}
|
||||
|
||||
function splitTextByLines(text: FlatText): Array<FlatText> {
|
||||
if (text.value.length === 0) {
|
||||
return [text];
|
||||
}
|
||||
const values = text.value.split("\n");
|
||||
const last = values.length - 1;
|
||||
return values.map<FlatText>((value, index) => ({
|
||||
type: "text",
|
||||
value: value + (index === last ? "" : "\n"),
|
||||
}));
|
||||
}
|
||||
93
scm-ui/ui-syntaxhighlighting/src/worker/mapping.json
Normal file
93
scm-ui/ui-syntaxhighlighting/src/worker/mapping.json
Normal file
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"atom": "markup",
|
||||
"html": "markup",
|
||||
"mathml": "markup",
|
||||
"rss": "markup",
|
||||
"ssml": "markup",
|
||||
"svg": "markup",
|
||||
"xml": "markup",
|
||||
"js": "javascript",
|
||||
"g4": "antlr4",
|
||||
"ino": "arduino",
|
||||
"adoc": "asciidoc",
|
||||
"cs": "csharp",
|
||||
"dotnet": "csharp",
|
||||
"avs": "avisynth",
|
||||
"avdl": "avro-idl",
|
||||
"shell": "bash",
|
||||
"shortcode": "bbcode",
|
||||
"rbnf": "bnf",
|
||||
"oscript": "bsl",
|
||||
"cfc": "cfscript",
|
||||
"coffee": "coffeescript",
|
||||
"conc": "concurnas",
|
||||
"rb": "ruby",
|
||||
"jinja2": "django",
|
||||
"dns-zone": "dns-zone-file",
|
||||
"dockerfile": "docker",
|
||||
"gv": "dot",
|
||||
"eta": "ejs",
|
||||
"xls": "excel-formula",
|
||||
"xlsx": "excel-formula",
|
||||
"gamemakerlanguage": "gml",
|
||||
"gni": "gn",
|
||||
"go-mod": "go-module",
|
||||
"yml": "yaml",
|
||||
"md": "markdown",
|
||||
"hbs": "handlebars",
|
||||
"hs": "haskell",
|
||||
"webmanifest": "json",
|
||||
"url": "uri",
|
||||
"idr": "idris",
|
||||
"gitignore": "ignore",
|
||||
"hgignore": "ignore",
|
||||
"npmignore": "ignore",
|
||||
"ts": "typescript",
|
||||
"n4jsd": "n4js",
|
||||
"kt": "kotlin",
|
||||
"kts": "kotlin",
|
||||
"kum": "kumir",
|
||||
"context": "latex",
|
||||
"tex": "latex",
|
||||
"ly": "lilypond",
|
||||
"elisp": "lisp",
|
||||
"emacs": "lisp",
|
||||
"emacs-lisp": "lisp",
|
||||
"moon": "moonscript",
|
||||
"nani": "naniscript",
|
||||
"objc": "objectivec",
|
||||
"qasm": "openqasm",
|
||||
"objectpascal": "pascal",
|
||||
"px": "pcaxis",
|
||||
"pcode": "peoplecode",
|
||||
"mscript": "powerquery",
|
||||
"pq": "powerquery",
|
||||
"pbfasm": "purebasic",
|
||||
"purs": "purescript",
|
||||
"py": "python",
|
||||
"qs": "qsharp",
|
||||
"rkt": "racket",
|
||||
"razor": "cshtml",
|
||||
"rpy": "renpy",
|
||||
"robot": "robotframework",
|
||||
"sh-session": "shell-session",
|
||||
"shellsession": "shell-session",
|
||||
"smlnj": "sml",
|
||||
"sol": "solidity",
|
||||
"sln": "solution-file",
|
||||
"trig": "turtle",
|
||||
"rq": "sparql",
|
||||
"t4": "t4-cs",
|
||||
"trickle": "tremor",
|
||||
"troy": "tremor",
|
||||
"tsconfig": "typoscript",
|
||||
"uc": "unrealscript",
|
||||
"uscript": "unrealscript",
|
||||
"vb": "visual-basic",
|
||||
"vba": "visual-basic",
|
||||
"webidl": "web-idl",
|
||||
"mathematica": "wolfram",
|
||||
"nb": "wolfram",
|
||||
"wl": "wolfram",
|
||||
"xeoracube": "xeora"
|
||||
}
|
||||
27
scm-ui/ui-syntaxhighlighting/src/worker/prismConfig.ts
Normal file
27
scm-ui/ui-syntaxhighlighting/src/worker/prismConfig.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
/*eslint-disable */
|
||||
// @ts-ignore
|
||||
self.Prism = { disableWorkerMessageHandler: true };
|
||||
88
scm-ui/ui-syntaxhighlighting/src/worker/refractorAdapter.ts
Normal file
88
scm-ui/ui-syntaxhighlighting/src/worker/refractorAdapter.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import "./prismConfig";
|
||||
import { refractor } from "refractor/lib/core";
|
||||
// @ts-ignore e have no types fpr json import
|
||||
import aliases from "./mapping.json";
|
||||
import dependencies from "./dependencies";
|
||||
|
||||
type RunHookEnv = {
|
||||
classes: string[];
|
||||
};
|
||||
|
||||
export type RefractorAdapter = typeof refractor & {
|
||||
isLanguageRegistered: (lang: string) => boolean;
|
||||
loadLanguage: (lang: string, callback: () => void) => void;
|
||||
};
|
||||
|
||||
const createAdapter = (theme: { [key: string]: string }): RefractorAdapter => {
|
||||
const isLanguageRegistered = (lang: string) => {
|
||||
const registeredLanguages = refractor.listLanguages();
|
||||
return registeredLanguages.includes(lang);
|
||||
};
|
||||
|
||||
const loadLanguageWithDependencies = async (alias: string) => {
|
||||
const lang = aliases[alias] || alias;
|
||||
const deps = dependencies[lang] || [];
|
||||
for (const dep of deps) {
|
||||
await loadLanguageWithDependencies(dep);
|
||||
}
|
||||
|
||||
if (isLanguageRegistered(lang)) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
try {
|
||||
const loadedLanguage = await import(/* webpackChunkName: "sh-lang-[request]" */ `refractor/lang/${lang}.js`);
|
||||
refractor.register(loadedLanguage.default);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`failed to load refractor language ${lang}: ${e}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const loadLanguage = async (alias: string, callback: () => void) => {
|
||||
await loadLanguageWithDependencies(alias);
|
||||
callback();
|
||||
};
|
||||
|
||||
// @ts-ignore hooks are not in the type definition
|
||||
const originalRunHook = refractor.hooks.run;
|
||||
// @ts-ignore hooks are not in the type definition
|
||||
refractor.hooks.run = (name: string, env: RunHookEnv) => {
|
||||
originalRunHook.apply(name, env);
|
||||
if (env.classes) {
|
||||
env.classes = env.classes.map((className) => theme[className] || className);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
isLanguageRegistered,
|
||||
loadLanguage,
|
||||
...refractor,
|
||||
};
|
||||
};
|
||||
|
||||
export default createAdapter;
|
||||
Reference in New Issue
Block a user