Skip to content

Commit 804c6c6

Browse files
add highlight package
1 parent ea732cd commit 804c6c6

13 files changed

Lines changed: 401 additions & 2 deletions

File tree

packages/lowlight/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 MrWangJustToDo
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

packages/lowlight/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./dist/types";

packages/lowlight/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"use strict";
2+
3+
if (process.env.NODE_ENV === "production") {
4+
module.exports = require("./dist/cjs/index.production");
5+
} else {
6+
module.exports = require("./dist/cjs/index.development");
7+
}

packages/lowlight/package.json

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{
2+
"name": "@git-diff-view/lowlight",
3+
"description": "@git-diff-view/lowlight",
4+
"author": "MrWangJustToDo",
5+
"license": "MIT",
6+
"version": "0.0.8",
7+
"main": "index.js",
8+
"types": "index.d.ts",
9+
"files": [
10+
"dist",
11+
"index.js",
12+
"index.d.ts"
13+
],
14+
"repository": {
15+
"type": "git",
16+
"url": "git+https://github.com/MrWangJustToDo/git-diff-view.git",
17+
"directory": "packages/lowlight"
18+
},
19+
"homepage": "https://github.com/MrWangJustToDo/git-diff-view",
20+
"exports": {
21+
".": {
22+
"require": "./index.js",
23+
"types": "./index.d.ts",
24+
"import": "./dist/esm/index.mjs"
25+
},
26+
"./cjsIndex.js": "./index.js",
27+
"./esmIndex.js": "./dist/esm/index.mjs",
28+
"./package.json": "./package.json"
29+
},
30+
"buildOptions": {
31+
"input": "./src/index.ts",
32+
"output": [
33+
{
34+
"dir": "./dist",
35+
"entryFileNames": "cjs/index.js",
36+
"format": "cjs",
37+
"type": true,
38+
"multiple": true,
39+
"sourcemap": true
40+
},
41+
{
42+
"dir": "./dist",
43+
"entryFileNames": "esm/index.mjs",
44+
"format": "esm",
45+
"sourcemap": true
46+
}
47+
]
48+
},
49+
"keywords": [
50+
"highlight",
51+
"virtual dom highlight"
52+
],
53+
"dependencies": {
54+
"highlight.js": "^11.9.0",
55+
"lowlight": "^3.1.0"
56+
}
57+
}

packages/lowlight/readme.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
## Pure Logic for Diff View Component
2+
3+
## Usage
4+
```tsx
5+
const file = new DiffFile(
6+
data?.oldFile?.fileName || "",
7+
data?.oldFile?.content || "",
8+
data?.newFile?.fileName || "",
9+
data?.newFile?.content || "",
10+
data?.hunks || [],
11+
data?.oldFile?.fileLang || "",
12+
data?.newFile?.fileLang || ""
13+
);
14+
15+
file.init();
16+
17+
file.buildSplitDiffLines();
18+
19+
file.buildUnifiedDiffLines();
20+
21+
// get All the bundle
22+
const bundle = file.getBundle();
23+
24+
// merge bundle
25+
const mergeFile = DiffFile.createInstance(data || {}, bundle);
26+
27+
// used for @git-diff-view/react and @git-diff-view/vue
28+
<DiffView diffFile={mergeFile} />
29+
30+
<DiffView :diffFile="mergeFile" />
31+
32+
```

packages/lowlight/src/global.d.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { File } from ".";
2+
3+
declare global {
4+
const __DEV__: boolean;
5+
const __VERSION__: string;
6+
7+
interface globalThis {
8+
__diff_cache__: Map<string, File>[];
9+
}
10+
11+
namespace NodeJS {
12+
interface ProcessEnv {
13+
NODE_ENV: "development" | "production" | "test";
14+
}
15+
}
16+
}
17+
18+
export {};

packages/lowlight/src/index.ts

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { createLowlight, all } from "lowlight";
2+
3+
import { processAST, type SyntaxLine } from "./processAST";
4+
5+
const lowlight = createLowlight(all);
6+
7+
lowlight.register("vue", function hljsDefineVue(hljs) {
8+
return {
9+
subLanguage: "xml",
10+
contains: [
11+
hljs.COMMENT("<!--", "-->", {
12+
relevance: 10,
13+
}),
14+
{
15+
begin: /^(\s*)(<script>)/gm,
16+
end: /^(\s*)(<\/script>)/gm,
17+
subLanguage: "javascript",
18+
excludeBegin: true,
19+
excludeEnd: true,
20+
},
21+
{
22+
begin: /^(?:\s*)(?:<script\s+lang=(["'])ts\1>)/gm,
23+
end: /^(\s*)(<\/script>)/gm,
24+
subLanguage: "typescript",
25+
excludeBegin: true,
26+
excludeEnd: true,
27+
},
28+
{
29+
begin: /^(\s*)(<style(\s+scoped)?>)/gm,
30+
end: /^(\s*)(<\/style>)/gm,
31+
subLanguage: "css",
32+
excludeBegin: true,
33+
excludeEnd: true,
34+
},
35+
{
36+
begin: /^(?:\s*)(?:<style(?:\s+scoped)?\s+lang=(["'])(?:s[ca]ss)\1(?:\s+scoped)?>)/gm,
37+
end: /^(\s*)(<\/style>)/gm,
38+
subLanguage: "scss",
39+
excludeBegin: true,
40+
excludeEnd: true,
41+
},
42+
{
43+
begin: /^(?:\s*)(?:<style(?:\s+scoped)?\s+lang=(["'])stylus\1(?:\s+scoped)?>)/gm,
44+
end: /^(\s*)(<\/style>)/gm,
45+
subLanguage: "stylus",
46+
excludeBegin: true,
47+
excludeEnd: true,
48+
},
49+
],
50+
};
51+
});
52+
53+
export type AST = ReturnType<typeof lowlight.highlight>;
54+
55+
export const highlighter = lowlight as typeof lowlight & {
56+
maxLineToIgnoreSyntax: number;
57+
autoDetectLang: boolean;
58+
setMaxLineToIgnoreSyntax: (v: number) => void;
59+
setAutoDetectLang: (v: boolean) => void;
60+
ignoreSyntaxHighlightList: (string | RegExp)[];
61+
setIgnoreSyntaxHighlightList: (v: (string | RegExp)[]) => void;
62+
getAST: (raw: string, fileName?: string, lang?: string) => AST;
63+
processAST: (ast: AST) => { syntaxFileObject: Record<number, SyntaxLine>; syntaxFileLineNumber: number };
64+
};
65+
66+
let _autoDetectLang = true;
67+
68+
let _maxLineToIgnoreSyntax = 2000;
69+
70+
const _ignoreSyntaxHighlightList: (string | RegExp)[] = [];
71+
72+
Object.defineProperty(highlighter, "maxLineToIgnoreSyntax", {
73+
get: () => _maxLineToIgnoreSyntax,
74+
});
75+
76+
Object.defineProperty(highlighter, "setMaxLineToIgnoreSyntax", {
77+
value: (v: number) => {
78+
_maxLineToIgnoreSyntax = v;
79+
},
80+
});
81+
82+
Object.defineProperty(highlighter, "autoDetectLang", {
83+
get: () => _autoDetectLang,
84+
});
85+
86+
Object.defineProperty(highlighter, "setAutoDetectLang", {
87+
value: (v: boolean) => {
88+
_autoDetectLang = v;
89+
},
90+
});
91+
92+
Object.defineProperty(highlighter, "ignoreSyntaxHighlightList", {
93+
get: () => _ignoreSyntaxHighlightList,
94+
});
95+
96+
Object.defineProperty(highlighter, "setIgnoreSyntaxHighlightList", {
97+
value: (v: (string | RegExp)[]) => {
98+
_ignoreSyntaxHighlightList.length = 0;
99+
_ignoreSyntaxHighlightList.push(...v);
100+
},
101+
});
102+
103+
Object.defineProperty(highlighter, "getAST", {
104+
value: (raw: string, fileName?: string, lang?: string) => {
105+
let hasRegisteredLang = true;
106+
107+
if (!highlighter.registered(lang)) {
108+
__DEV__ && console.warn(`not support current lang: ${lang} yet`);
109+
hasRegisteredLang = false;
110+
}
111+
112+
if (
113+
fileName &&
114+
highlighter.ignoreSyntaxHighlightList.some((item) =>
115+
item instanceof RegExp ? item.test(fileName) : fileName === item
116+
)
117+
) {
118+
__DEV__ &&
119+
console.warn(
120+
`ignore syntax for current file, because the fileName is in the ignoreSyntaxHighlightList: ${fileName}`
121+
);
122+
return;
123+
}
124+
125+
if (hasRegisteredLang) {
126+
return highlighter.highlight(lang, raw);
127+
} else {
128+
return highlighter.highlightAuto(raw);
129+
}
130+
},
131+
});
132+
133+
Object.defineProperty(highlighter, "processAST", {
134+
value: (ast: AST) => {
135+
return processAST(ast);
136+
},
137+
});
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import type { AST } from ".";
2+
3+
export type SyntaxNode = {
4+
type: string;
5+
value: string;
6+
lineNumber: number;
7+
startIndex: number;
8+
endIndex: number;
9+
properties?: { className?: string[] };
10+
children?: SyntaxNode[];
11+
};
12+
13+
export type SyntaxLine = {
14+
value: string;
15+
lineNumber: number;
16+
valueLength: number;
17+
nodeList: { node: SyntaxNode; wrapper?: SyntaxNode }[];
18+
};
19+
20+
export const processAST = (ast: AST) => {
21+
let lineNumber = 1;
22+
23+
const syntaxObj: Record<number, SyntaxLine> = {};
24+
25+
const loopAST = (nodes: SyntaxNode[], wrapper?: SyntaxNode) => {
26+
nodes.forEach((node) => {
27+
if (node.type === "text") {
28+
if (node.value.indexOf("\n") === -1) {
29+
const valueLength = node.value.length;
30+
if (!syntaxObj[lineNumber]) {
31+
node.startIndex = 0;
32+
node.endIndex = valueLength - 1;
33+
const item = {
34+
value: node.value,
35+
lineNumber,
36+
valueLength,
37+
nodeList: [{ node, wrapper }],
38+
};
39+
syntaxObj[lineNumber] = item;
40+
} else {
41+
node.startIndex = syntaxObj[lineNumber].valueLength;
42+
node.endIndex = node.startIndex + valueLength - 1;
43+
syntaxObj[lineNumber].value += node.value;
44+
syntaxObj[lineNumber].valueLength += valueLength;
45+
syntaxObj[lineNumber].nodeList.push({ node, wrapper });
46+
}
47+
node.lineNumber = lineNumber;
48+
return;
49+
}
50+
51+
const lines = node.value.split("\n");
52+
node.children = node.children || [];
53+
for (let i = 0; i < lines.length; i++) {
54+
const _value = i === lines.length - 1 ? lines[i] : lines[i] + "\n";
55+
const _lineNumber = i === 0 ? lineNumber : ++lineNumber;
56+
const _valueLength = _value.length;
57+
const _node: SyntaxNode = {
58+
type: "text",
59+
value: _value,
60+
startIndex: Infinity,
61+
endIndex: Infinity,
62+
lineNumber: _lineNumber,
63+
};
64+
if (!syntaxObj[_lineNumber]) {
65+
_node.startIndex = 0;
66+
_node.endIndex = _valueLength - 1;
67+
const item = {
68+
value: _value,
69+
lineNumber: _lineNumber,
70+
valueLength: _valueLength,
71+
nodeList: [{ node: _node, wrapper }],
72+
};
73+
syntaxObj[_lineNumber] = item;
74+
} else {
75+
_node.startIndex = syntaxObj[_lineNumber].valueLength;
76+
_node.endIndex = _node.startIndex + _valueLength - 1;
77+
syntaxObj[_lineNumber].value += _value;
78+
syntaxObj[_lineNumber].valueLength += _valueLength;
79+
syntaxObj[_lineNumber].nodeList.push({ node: _node, wrapper });
80+
}
81+
node.children.push(_node);
82+
}
83+
84+
node.lineNumber = lineNumber;
85+
86+
return;
87+
}
88+
if (node.children) {
89+
loopAST(node.children, node);
90+
91+
node.lineNumber = lineNumber;
92+
}
93+
});
94+
};
95+
96+
loopAST(ast.children as SyntaxNode[]);
97+
98+
return { syntaxFileObject: syntaxObj, syntaxFileLineNumber: lineNumber };
99+
};

packages/lowlight/tsconfig.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"compilerOptions": {
3+
"rootDir": "src",
4+
"target": "ES2015",
5+
"moduleResolution": "Bundler",
6+
"stripInternal": true
7+
},
8+
"include": ["./src"],
9+
"exclude": ["node_modules"]
10+
}

0 commit comments

Comments
 (0)