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:
Matthias Thieroff
2022-04-25 16:34:40 +02:00
committed by GitHub
parent 0e930b8671
commit a0737a2c90
82 changed files with 24217 additions and 24879 deletions

View 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,
};

View 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;

View File

@@ -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)
}
`;

View File

@@ -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();
}
}
}
`;

View File

@@ -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);
`;

View File

@@ -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()
`;

View File

@@ -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);
`;

View File

@@ -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>
`;

View 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!
`;

View 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";

View 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;
}

View 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;
};
};

View 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;

View File

@@ -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;

View File

@@ -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));
}
});

View 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;

View 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;
};

View 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"),
}));
}

View 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"
}

View 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 };

View 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;