Skip to content

Commit d229438

Browse files
authored
Merge pull request #11 from code-hike/annotations
Annotations
2 parents 4855ee0 + 198900c commit d229438

14 files changed

+2501
-1233
lines changed

.changeset/violet-paws-deny.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@code-hike/lighter": minor
3+
---
4+
5+
Add annotations

lib/dist/index.cjs.js

+835-579
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/dist/index.d.ts

+75-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,55 @@
11
type LanguageAlias = "abap" | "actionscript-3" | "ada" | "apache" | "apex" | "apl" | "applescript" | "asm" | "astro" | "awk" | "ballerina" | "bat" | "batch" | "berry" | "be" | "bibtex" | "bicep" | "blade" | "c" | "cadence" | "cdc" | "clarity" | "clojure" | "clj" | "cmake" | "cobol" | "codeql" | "ql" | "coffee" | "cpp" | "crystal" | "csharp" | "c#" | "cs" | "css" | "cue" | "d" | "dart" | "diff" | "docker" | "dream-maker" | "elixir" | "elm" | "erb" | "erlang" | "erl" | "fish" | "fsharp" | "f#" | "fs" | "gherkin" | "git-commit" | "git-rebase" | "glsl" | "gnuplot" | "go" | "graphql" | "groovy" | "hack" | "haml" | "handlebars" | "hbs" | "haskell" | "hs" | "hcl" | "hlsl" | "html" | "http" | "imba" | "ini" | "java" | "javascript" | "js" | "jinja-html" | "json" | "json5" | "jsonc" | "jsonnet" | "jssm" | "fsl" | "jsx" | "julia" | "kotlin" | "latex" | "less" | "liquid" | "lisp" | "logo" | "lua" | "make" | "makefile" | "markdown" | "md" | "marko" | "matlab" | "mdx" | "mermaid" | "nginx" | "nim" | "nix" | "objective-c" | "objc" | "objective-cpp" | "ocaml" | "pascal" | "perl" | "php" | "plsql" | "postcss" | "powershell" | "ps" | "ps1" | "prisma" | "prolog" | "proto" | "pug" | "jade" | "puppet" | "purescript" | "python" | "py" | "r" | "raku" | "perl6" | "razor" | "rel" | "riscv" | "rst" | "ruby" | "rb" | "rust" | "rs" | "sas" | "sass" | "scala" | "scheme" | "scss" | "shaderlab" | "shader" | "shellscript" | "shell" | "bash" | "sh" | "zsh" | "smalltalk" | "solidity" | "sparql" | "sql" | "ssh-config" | "stata" | "stylus" | "styl" | "svelte" | "swift" | "system-verilog" | "tasl" | "tcl" | "tex" | "toml" | "tsx" | "turtle" | "twig" | "typescript" | "ts" | "v" | "vb" | "cmd" | "verilog" | "vhdl" | "viml" | "vim" | "vimscript" | "vue-html" | "vue" | "wasm" | "wenyan" | "文言" | "xml" | "xsl" | "yaml" | "yml" | "zenscript";
22
type LanguageName = "abap" | "actionscript-3" | "ada" | "apache" | "apex" | "apl" | "applescript" | "asm" | "astro" | "awk" | "ballerina" | "bat" | "berry" | "bibtex" | "bicep" | "blade" | "c" | "cadence" | "clarity" | "clojure" | "cmake" | "cobol" | "codeql" | "coffee" | "cpp" | "crystal" | "csharp" | "css" | "cue" | "d" | "dart" | "diff" | "docker" | "dream-maker" | "elixir" | "elm" | "erb" | "erlang" | "fish" | "fsharp" | "gherkin" | "git-commit" | "git-rebase" | "glsl" | "gnuplot" | "go" | "graphql" | "groovy" | "hack" | "haml" | "handlebars" | "haskell" | "hcl" | "hlsl" | "html" | "http" | "imba" | "ini" | "java" | "javascript" | "jinja-html" | "json" | "json5" | "jsonc" | "jsonnet" | "jssm" | "jsx" | "julia" | "kotlin" | "latex" | "less" | "liquid" | "lisp" | "logo" | "lua" | "make" | "markdown" | "marko" | "matlab" | "mdx" | "mermaid" | "nginx" | "nim" | "nix" | "objective-c" | "objective-cpp" | "ocaml" | "pascal" | "perl" | "php" | "plsql" | "postcss" | "powershell" | "prisma" | "prolog" | "proto" | "pug" | "puppet" | "purescript" | "python" | "r" | "raku" | "razor" | "rel" | "riscv" | "rst" | "ruby" | "rust" | "sas" | "sass" | "scala" | "scheme" | "scss" | "shaderlab" | "shellscript" | "smalltalk" | "solidity" | "sparql" | "sql" | "ssh-config" | "stata" | "stylus" | "svelte" | "swift" | "system-verilog" | "tasl" | "tcl" | "tex" | "toml" | "tsx" | "turtle" | "twig" | "typescript" | "v" | "vb" | "verilog" | "vhdl" | "viml" | "vue-html" | "vue" | "wasm" | "wenyan" | "xml" | "xsl" | "yaml" | "zenscript";
33

4+
type LineNumber = number;
5+
type ColumnNumber = number;
6+
type MultiLineRange = {
7+
fromLineNumber: LineNumber;
8+
toLineNumber: LineNumber;
9+
};
10+
type InlineRange = {
11+
lineNumber: LineNumber;
12+
fromColumn: ColumnNumber;
13+
toColumn: ColumnNumber;
14+
};
15+
type CodeRange = MultiLineRange | InlineRange;
16+
17+
type Annotation = {
18+
name: string;
19+
query?: string;
20+
ranges: CodeRange[];
21+
};
22+
23+
type Token = {
24+
content: string;
25+
style: {
26+
color: string;
27+
fontStyle?: "italic";
28+
fontWeight?: "bold";
29+
textDecoration?: "underline" | "line-through";
30+
};
31+
};
32+
type TokenGroup = {
33+
annotationName: string;
34+
annotationQuery?: string;
35+
fromColumn: number;
36+
toColumn: number;
37+
tokens: Tokens;
38+
};
39+
type Tokens = (Token | TokenGroup)[];
40+
type Line = {
41+
lineNumber: number;
42+
tokens: Tokens;
43+
};
44+
type LineGroup = {
45+
annotationName: string;
46+
annotationQuery?: string;
47+
fromLineNumber: number;
48+
toLineNumber: number;
49+
lines: Lines;
50+
};
51+
type Lines = (Line | LineGroup)[];
52+
453
type RawTheme = {
554
name?: string;
655
type?: string;
@@ -24,6 +73,11 @@ type NamesTuple = typeof ALL_NAMES;
2473
type StringTheme = NamesTuple[number];
2574
type Theme = StringTheme | RawTheme;
2675

76+
declare class UnknownLanguageError extends Error {
77+
alias: string;
78+
constructor(alias: string);
79+
}
80+
2781
declare function highlight(code: string, alias: LanguageAlias, themeOrThemeName?: Theme): Promise<{
2882
background: string;
2983
foreground: string;
@@ -36,24 +90,31 @@ declare function highlight(code: string, alias: LanguageAlias, themeOrThemeName?
3690
tabBorder: string;
3791
activeTabBorder: string;
3892
colorScheme: "dark" | "light";
39-
lines: {
40-
content: string;
41-
style: {
42-
color: string;
43-
fontStyle?: "italic";
44-
fontWeight?: "bold";
45-
textDecoration?: "underline" | "line-through";
46-
};
47-
}[][];
93+
lines: Token[][];
94+
lang: LanguageName;
95+
}>;
96+
declare function extractAnnotations(code: string, alias: LanguageAlias, annotationNames?: string[]): Promise<{
97+
code: string;
98+
annotations: Annotation[];
99+
}>;
100+
declare function annotatedHighlight(code: string, alias: LanguageAlias, themeOrThemeName?: Theme, annotations?: Annotation[]): Promise<{
101+
background: string;
102+
foreground: string;
103+
lineNumberForeground: string;
104+
selectionBackground: string;
105+
editorBackground: string;
106+
editorGroupHeaderBackground: string;
107+
activeTabBackground: string;
108+
activeTabForeground: string;
109+
tabBorder: string;
110+
activeTabBorder: string;
111+
colorScheme: "dark" | "light";
112+
lines: Lines;
48113
lang: LanguageName;
49114
}>;
50-
declare class UnknownLanguageError extends Error {
51-
alias: string;
52-
constructor(alias: string);
53-
}
54115
declare class UnknownThemeError extends Error {
55116
theme: string;
56117
constructor(theme: string);
57118
}
58119

59-
export { LanguageAlias, RawTheme, StringTheme, Theme, UnknownLanguageError, UnknownThemeError, highlight };
120+
export { Annotation, LanguageAlias, Lines, RawTheme, StringTheme, Theme, UnknownLanguageError, UnknownThemeError, annotatedHighlight, extractAnnotations, highlight };

lib/dist/index.esm.mjs

+821-567
Large diffs are not rendered by default.

lib/src/annotations.inline.ts

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { InlineAnnotation, Token, TokenGroup } from "./annotations";
2+
3+
type TokenWrapper = {
4+
fromColumn: number;
5+
toColumn: number;
6+
token: Token;
7+
};
8+
9+
type FakeTokenGroup = {
10+
annotationName: string;
11+
annotationQuery?: string;
12+
fromColumn: number;
13+
toColumn: number;
14+
tokens: (TokenWrapper | FakeTokenGroup)[];
15+
};
16+
17+
export function annotateLine(line: Token[], annotations: InlineAnnotation[]) {
18+
let annotatedLine: (TokenWrapper | FakeTokenGroup)[] = [];
19+
let columnNumber = 1;
20+
line.forEach((token) => {
21+
annotatedLine.push({
22+
fromColumn: columnNumber,
23+
toColumn: columnNumber + token.content.length - 1,
24+
token,
25+
});
26+
});
27+
28+
annotations.forEach((annotation) => {
29+
annotatedLine = reannotateLine(annotatedLine, annotation);
30+
});
31+
32+
// remove the fake groups
33+
return annotatedLine.map((group) => removeFakeGroups(group));
34+
}
35+
36+
function removeFakeGroups(
37+
group: TokenWrapper | FakeTokenGroup
38+
): Token | TokenGroup {
39+
if ("tokens" in group) {
40+
return {
41+
annotationName: group.annotationName,
42+
annotationQuery: group.annotationQuery,
43+
fromColumn: group.fromColumn,
44+
toColumn: group.toColumn,
45+
tokens: group.tokens.map((group) => removeFakeGroups(group)),
46+
};
47+
} else {
48+
return group.token;
49+
}
50+
}
51+
52+
function reannotateLine(
53+
annotatedLine: (TokenWrapper | FakeTokenGroup)[],
54+
annotation: InlineAnnotation
55+
) {
56+
const { range, name, query } = annotation;
57+
const { fromColumn, toColumn } = range;
58+
const newAnnotatedLine: (TokenWrapper | FakeTokenGroup)[] = [];
59+
60+
let i = 0;
61+
while (i < annotatedLine.length && annotatedLine[i].toColumn < fromColumn) {
62+
newAnnotatedLine.push(annotatedLine[i]);
63+
i++;
64+
}
65+
66+
if (i === annotatedLine.length) {
67+
return annotatedLine;
68+
}
69+
70+
const newGroup: FakeTokenGroup = {
71+
annotationName: annotation.name,
72+
annotationQuery: annotation.query,
73+
fromColumn,
74+
toColumn,
75+
tokens: [],
76+
};
77+
const firstGroup = annotatedLine[i];
78+
if (firstGroup.fromColumn < fromColumn) {
79+
// we need to split the first group in two
80+
newGroup.tokens.push({
81+
...firstGroup,
82+
toColumn: fromColumn - 1,
83+
});
84+
newGroup.tokens.push({
85+
...firstGroup,
86+
fromColumn,
87+
});
88+
i++;
89+
}
90+
91+
while (i < annotatedLine.length && annotatedLine[i].toColumn < toColumn) {
92+
newGroup.tokens.push(annotatedLine[i]);
93+
i++;
94+
}
95+
96+
newAnnotatedLine.push(newGroup);
97+
98+
if (i === annotatedLine.length) {
99+
return newAnnotatedLine;
100+
}
101+
102+
const lastGroup = annotatedLine[i];
103+
if (lastGroup.toColumn > toColumn) {
104+
// we need to split the last group in two
105+
newGroup.tokens.push({
106+
...lastGroup,
107+
toColumn,
108+
});
109+
newGroup.tokens.push({
110+
...lastGroup,
111+
fromColumn: toColumn + 1,
112+
});
113+
i++;
114+
}
115+
116+
while (i < annotatedLine.length) {
117+
newAnnotatedLine.push(annotatedLine[i]);
118+
i++;
119+
}
120+
121+
return newAnnotatedLine;
122+
}

lib/src/annotations.multiline.ts

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { Line, LineGroup, Lines, MultilineAnnotation } from "./annotations";
2+
3+
type LineWrapper = {
4+
fromLineNumber: number;
5+
toLineNumber: number;
6+
line: Line;
7+
};
8+
9+
type FakeLineGroup = {
10+
annotationName: string;
11+
annotationQuery?: string;
12+
fromLineNumber: number;
13+
toLineNumber: number;
14+
lines: (LineWrapper | FakeLineGroup)[];
15+
};
16+
17+
export function annotateLines(
18+
lines: Line[],
19+
annotations: MultilineAnnotation[]
20+
): Lines {
21+
let annotatedLines: (LineWrapper | FakeLineGroup)[] = lines.map(
22+
(line, lineIndex) => ({
23+
fromLineNumber: lineIndex + 1,
24+
toLineNumber: lineIndex + 1,
25+
line,
26+
})
27+
);
28+
29+
annotations.forEach((annotation) => {
30+
annotatedLines = reannotateLines(annotatedLines, annotation);
31+
});
32+
33+
return annotatedLines.map((group) => removeFakeGroups(group));
34+
}
35+
36+
function removeFakeGroups(
37+
group: LineWrapper | FakeLineGroup
38+
): Line | LineGroup {
39+
if ("line" in group) {
40+
return {
41+
lineNumber: group.fromLineNumber,
42+
tokens: group.line.tokens,
43+
};
44+
} else {
45+
return {
46+
annotationName: group.annotationName,
47+
annotationQuery: group.annotationQuery,
48+
fromLineNumber: group.fromLineNumber,
49+
toLineNumber: group.toLineNumber,
50+
lines: group.lines.map((line) => removeFakeGroups(line)),
51+
};
52+
}
53+
}
54+
55+
function reannotateLines(
56+
annotatedLines: (LineWrapper | FakeLineGroup)[],
57+
annotation: MultilineAnnotation
58+
) {
59+
const { range, name, query } = annotation;
60+
const { fromLineNumber, toLineNumber } = range;
61+
const newAnnotatedLines: (LineWrapper | FakeLineGroup)[] = [];
62+
63+
let i = 0;
64+
while (
65+
i < annotatedLines.length &&
66+
annotatedLines[i].toLineNumber < fromLineNumber
67+
) {
68+
newAnnotatedLines.push(annotatedLines[i]);
69+
i++;
70+
}
71+
72+
if (i === annotatedLines.length) {
73+
return newAnnotatedLines;
74+
}
75+
76+
const newGroup: FakeLineGroup = {
77+
annotationName: name,
78+
annotationQuery: query,
79+
fromLineNumber,
80+
toLineNumber,
81+
lines: [],
82+
};
83+
84+
const firstGroup = annotatedLines[i];
85+
if (firstGroup.fromLineNumber < fromLineNumber) {
86+
newGroup.lines.push({
87+
...firstGroup,
88+
toLineNumber: fromLineNumber - 1,
89+
});
90+
newGroup.lines.push({
91+
...firstGroup,
92+
fromLineNumber,
93+
});
94+
i++;
95+
}
96+
97+
while (
98+
i < annotatedLines.length &&
99+
annotatedLines[i].toLineNumber <= toLineNumber
100+
) {
101+
newGroup.lines.push(annotatedLines[i]);
102+
i++;
103+
}
104+
105+
newAnnotatedLines.push(newGroup);
106+
107+
if (i === annotatedLines.length) {
108+
return newAnnotatedLines;
109+
}
110+
111+
const lastGroup = annotatedLines[i];
112+
if (lastGroup.toLineNumber > toLineNumber) {
113+
newAnnotatedLines.push({
114+
...lastGroup,
115+
toLineNumber,
116+
});
117+
newAnnotatedLines.push({
118+
...lastGroup,
119+
fromLineNumber: toLineNumber + 1,
120+
});
121+
i++;
122+
}
123+
124+
while (i < annotatedLines.length) {
125+
newAnnotatedLines.push(annotatedLines[i]);
126+
i++;
127+
}
128+
129+
return newAnnotatedLines;
130+
}

0 commit comments

Comments
 (0)