Skip to content

Commit a7ab371

Browse files
authored
Visual syntax highlighting (#9)
* Add syntax highlighting to console screen * Always enable syntax highlighting, minor refactor - Extract styles into module.css - Remove obsolete types * Prefer using Records over of keyed arrays
1 parent 1e35ca7 commit a7ab371

File tree

5 files changed

+141
-1
lines changed

5 files changed

+141
-1
lines changed

src/components/Generator/GeneredTaskfile/GeneratedTaskfile.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { taskfile } from '@/components/Generator/GeneredTaskfile/taskfile';
55
import { useFormContext } from 'react-hook-form';
66
import { GeneratorSettings } from '@/components/Generator/Generator';
77
import CopyToClipboard from '@/components/Generator/GeneredTaskfile/Copy';
8+
import { highlighter } from './Highlighter';
89

910
const GeneratedTaskfile = (): ReactElement => {
1011
const form = useFormContext<GeneratorSettings>();
@@ -16,7 +17,7 @@ const GeneratedTaskfile = (): ReactElement => {
1617
return (
1718
<>
1819
<CopyToClipboard onCopy={() => navigator.clipboard.writeText(resultTaskfile)} />
19-
<pre>{resultTaskfile}</pre>
20+
<pre>{highlighter(resultTaskfile)}</pre>
2021
</>
2122
);
2223
};
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { ReactElement } from 'react';
2+
3+
import styles from './highlighter.module.css';
4+
5+
import { lineRenderers } from './lineRenderers';
6+
7+
export const highlighter = (code: string): ReactElement[] => {
8+
const normalizedCode = code.endsWith('\n') ? code : code + '\n';
9+
const lines = normalizedCode.split('\n').slice(0, -1);
10+
11+
return lines.map((line, index) => {
12+
const renderer = Object.values(lineRenderers).find((r) => r.test(line));
13+
14+
if (renderer) {
15+
return renderer.render(line, index);
16+
}
17+
18+
return (
19+
<div key={index} className={styles['text-white']}>
20+
{line}
21+
</div>
22+
);
23+
});
24+
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
.line {
2+
display: flex;
3+
}
4+
5+
.text-white {
6+
color: #fff;
7+
}
8+
9+
.text-gray {
10+
color: #888589;
11+
}
12+
13+
.text-yellow {
14+
color: #fde047;
15+
}
16+
17+
.text-yellow-light {
18+
color: #fef08a;
19+
}
20+
21+
.text-pink {
22+
color: #ec4899;
23+
}
24+
25+
.text-purple {
26+
color: #a855f7;
27+
}
28+
29+
.text-blue {
30+
color: #60a5fa;
31+
}
32+
33+
.text-blue-light {
34+
color: #93c5fd;
35+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './Highlighter';
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import React, { ReactElement } from 'react';
2+
3+
import styles from './highlighter.module.css';
4+
5+
enum RendererType {
6+
EmptyLines,
7+
FunctionDefinitions,
8+
Comments,
9+
Variables,
10+
Conditionals,
11+
EchoStatements,
12+
}
13+
14+
type LineRenderer = {
15+
test: (line: string) => boolean;
16+
render: (line: string, index: number) => ReactElement;
17+
};
18+
19+
export const lineRenderers: Record<RendererType, LineRenderer> = {
20+
[RendererType.EmptyLines]: {
21+
test: (line) => line.trim() === '',
22+
render: (_, i) => <div key={i}>&nbsp;</div>,
23+
},
24+
[RendererType.FunctionDefinitions]: {
25+
test: (line) => /^function\s+[a-zA-Z_:]+/.test(line),
26+
render: (line, i) => {
27+
const [, name, rest] = line.match(/^function\s+([a-zA-Z_:]+)(.*)/) || [];
28+
return (
29+
<div key={i} className={styles.line}>
30+
<span className={styles['text-purple']}>function </span>
31+
<span className={styles['text-yellow']}>{name}</span>
32+
<span className={styles['text-white']}>{rest}</span>
33+
</div>
34+
);
35+
},
36+
},
37+
[RendererType.Comments]: {
38+
test: (line) => line.trim().startsWith('#'),
39+
render: (line, i) => (
40+
<div key={i} className={styles['text-gray']}>
41+
{line}
42+
</div>
43+
),
44+
},
45+
[RendererType.Variables]: {
46+
test: (line) => /^[A-Z_]+=.*/.test(line),
47+
render: (line, i) => {
48+
const [varName, ...rest] = line.split('=');
49+
return (
50+
<div key={i} className={styles.line}>
51+
<span className={styles['text-blue']}>{varName}</span>
52+
<span className={styles['text-white']}>=</span>
53+
<span className={styles['text-yellow-light']}>{rest.join('=')}</span>
54+
</div>
55+
);
56+
},
57+
},
58+
[RendererType.Conditionals]: {
59+
test: (line) => /^\s*if\s+|^\s*then\s+|^\s*else\s+|^\s*fi\s*/.test(line),
60+
render: (line, i) => (
61+
<div key={i} className={styles['text-pink']}>
62+
{line}
63+
</div>
64+
),
65+
},
66+
[RendererType.EchoStatements]: {
67+
test: (line) => line.includes('echo'),
68+
render: (line, i) => {
69+
const parts = line.split('echo');
70+
return (
71+
<div key={i} className={styles.line}>
72+
<span className={styles['text-white']}>{parts[0]}</span>
73+
<span className={styles['text-blue-light']}>echo</span>
74+
<span className={styles['text-yellow-light']}>{parts[1]}</span>
75+
</div>
76+
);
77+
},
78+
},
79+
};

0 commit comments

Comments
 (0)