From f07caae5b754665bba537fba7617c65bb7174cb9 Mon Sep 17 00:00:00 2001 From: Arthur Geel Date: Fri, 15 Nov 2024 13:45:51 +0100 Subject: [PATCH 1/3] Add syntax highlighting to console screen --- .../GeneredTaskfile/GeneratedTaskfile.tsx | 9 ++- .../Highlighter/Highlighter.tsx | 23 ++++++ .../GeneredTaskfile/Highlighter/index.ts | 1 + .../Highlighter/lineRenderers.tsx | 72 +++++++++++++++++++ .../GeneredTaskfile/Highlighter/styles.ts | 18 +++++ .../GeneredTaskfile/Highlighter/types.ts | 23 ++++++ 6 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 src/components/Generator/GeneredTaskfile/Highlighter/Highlighter.tsx create mode 100644 src/components/Generator/GeneredTaskfile/Highlighter/index.ts create mode 100644 src/components/Generator/GeneredTaskfile/Highlighter/lineRenderers.tsx create mode 100644 src/components/Generator/GeneredTaskfile/Highlighter/styles.ts create mode 100644 src/components/Generator/GeneredTaskfile/Highlighter/types.ts diff --git a/src/components/Generator/GeneredTaskfile/GeneratedTaskfile.tsx b/src/components/Generator/GeneredTaskfile/GeneratedTaskfile.tsx index cf8438e..66b6631 100644 --- a/src/components/Generator/GeneredTaskfile/GeneratedTaskfile.tsx +++ b/src/components/Generator/GeneredTaskfile/GeneratedTaskfile.tsx @@ -1,12 +1,15 @@ 'use client'; -import { ReactElement } from 'react'; +import { ReactElement, useState } from 'react'; import { taskfile } from '@/components/Generator/GeneredTaskfile/taskfile'; import { useFormContext } from 'react-hook-form'; import { GeneratorSettings } from '@/components/Generator/Generator'; import CopyToClipboard from '@/components/Generator/GeneredTaskfile/Copy'; +import { highlighter } from './Highlighter'; const GeneratedTaskfile = (): ReactElement => { + const [showHighlighting, setShowHighlighting] = useState(true); + const form = useFormContext(); const settings = form.watch(); @@ -16,7 +19,9 @@ const GeneratedTaskfile = (): ReactElement => { return ( <> navigator.clipboard.writeText(resultTaskfile)} /> -
{resultTaskfile}
+
 setShowHighlighting(!showHighlighting)}>
+				{showHighlighting ? highlighter(resultTaskfile) : resultTaskfile}
+			
); }; diff --git a/src/components/Generator/GeneredTaskfile/Highlighter/Highlighter.tsx b/src/components/Generator/GeneredTaskfile/Highlighter/Highlighter.tsx new file mode 100644 index 0000000..7bfed06 --- /dev/null +++ b/src/components/Generator/GeneredTaskfile/Highlighter/Highlighter.tsx @@ -0,0 +1,23 @@ +import { ReactElement } from 'react'; + +import { styles } from './styles'; +import { lineRenderers } from './lineRenderers'; + +export const highlighter = (code: string): ReactElement[] => { + const normalizedCode = code.endsWith('\n') ? code : code + '\n'; + const lines = normalizedCode.split('\n').slice(0, -1); + + return lines.map((line, index) => { + const renderer = lineRenderers.find((r) => r.test(line)); + + if (renderer) { + return renderer.render(line, index); + } + + return ( +
+ {line} +
+ ); + }); +}; diff --git a/src/components/Generator/GeneredTaskfile/Highlighter/index.ts b/src/components/Generator/GeneredTaskfile/Highlighter/index.ts new file mode 100644 index 0000000..b881459 --- /dev/null +++ b/src/components/Generator/GeneredTaskfile/Highlighter/index.ts @@ -0,0 +1 @@ +export * from './Highlighter'; diff --git a/src/components/Generator/GeneredTaskfile/Highlighter/lineRenderers.tsx b/src/components/Generator/GeneredTaskfile/Highlighter/lineRenderers.tsx new file mode 100644 index 0000000..ee4b8ea --- /dev/null +++ b/src/components/Generator/GeneredTaskfile/Highlighter/lineRenderers.tsx @@ -0,0 +1,72 @@ +import React from 'react'; + +import { LineRenderer, RendererType } from './types'; +import { styles } from './styles'; + +export const lineRenderers: LineRenderer[] = [ + { + type: RendererType.EmptyLines, + test: (line) => line.trim() === '', + render: (_, i) =>
 
, + }, + { + type: RendererType.FunctionDefinitions, + test: (line) => /^function\s+[a-zA-Z_:]+/.test(line), + render: (line, i) => { + const [, name, rest] = line.match(/^function\s+([a-zA-Z_:]+)(.*)/) || []; + return ( +
+ function + {name} + {rest} +
+ ); + }, + }, + { + type: RendererType.Comments, + test: (line) => line.trim().startsWith('#'), + render: (line, i) => ( +
+ {line} +
+ ), + }, + { + type: RendererType.Variables, + test: (line) => /^[A-Z_]+=.*/.test(line), + render: (line, i) => { + const [varName, ...rest] = line.split('='); + return ( +
+ {varName} + = + {rest.join('=')} +
+ ); + }, + }, + { + type: RendererType.Conditionals, + test: (line) => /^\s*if\s+|^\s*then\s+|^\s*else\s+|^\s*fi\s*/.test(line), + render: (line, i) => ( +
+ {line} +
+ ), + }, + { + type: RendererType.EchoStatements, + test: (line) => line.includes('echo'), + render: (line, i) => { + const parts = line.split('echo'); + return ( +
+ {parts[0]} + echo + {parts[1]} +
+ ); + }, + }, +]; diff --git a/src/components/Generator/GeneredTaskfile/Highlighter/styles.ts b/src/components/Generator/GeneredTaskfile/Highlighter/styles.ts new file mode 100644 index 0000000..8e9991e --- /dev/null +++ b/src/components/Generator/GeneredTaskfile/Highlighter/styles.ts @@ -0,0 +1,18 @@ +import { StylesConfig } from './types'; + +export const styles: StylesConfig = { + line: { + display: 'flex', + }, + colors: { + white: { color: '#fff' }, + purple: { color: '#a855f7' }, + yellow: { color: '#fde047' }, + green: { color: '#22c55e' }, + blue: { color: '#60a5fa' }, + pink: { color: '#ec4899' }, + lightYellow: { color: '#fef08a' }, + lightBlue: { color: '#93c5fd' }, + gray: { color: '#888589' }, + }, +}; diff --git a/src/components/Generator/GeneredTaskfile/Highlighter/types.ts b/src/components/Generator/GeneredTaskfile/Highlighter/types.ts new file mode 100644 index 0000000..0f7cf33 --- /dev/null +++ b/src/components/Generator/GeneredTaskfile/Highlighter/types.ts @@ -0,0 +1,23 @@ +import { ReactElement } from 'react'; + +type ColorName = 'white' | 'purple' | 'yellow' | 'green' | 'blue' | 'pink' | 'lightYellow' | 'lightBlue' | 'gray'; + +export type StylesConfig = { + line: { display: string }; + colors: Record; +}; + +export enum RendererType { + EmptyLines, + FunctionDefinitions, + Comments, + Variables, + Conditionals, + EchoStatements, +} + +export type LineRenderer = { + type: RendererType; + test: (line: string) => boolean; + render: (line: string, index: number) => ReactElement; +}; From 8135f9fb16e58cf80cd8b38ecbab0a6b6c7279e9 Mon Sep 17 00:00:00 2001 From: Arthur Geel Date: Fri, 15 Nov 2024 17:56:47 +0100 Subject: [PATCH 2/3] Always enable syntax highlighting, minor refactor - Extract styles into module.css - Remove obsolete types --- .../GeneredTaskfile/GeneratedTaskfile.tsx | 8 ++--- .../Highlighter/Highlighter.tsx | 5 +-- .../Highlighter/highlighter.module.css | 35 +++++++++++++++++++ .../Highlighter/lineRenderers.tsx | 31 ++++++++-------- .../GeneredTaskfile/Highlighter/styles.ts | 18 ---------- .../GeneredTaskfile/Highlighter/types.ts | 7 ---- 6 files changed, 56 insertions(+), 48 deletions(-) create mode 100644 src/components/Generator/GeneredTaskfile/Highlighter/highlighter.module.css delete mode 100644 src/components/Generator/GeneredTaskfile/Highlighter/styles.ts diff --git a/src/components/Generator/GeneredTaskfile/GeneratedTaskfile.tsx b/src/components/Generator/GeneredTaskfile/GeneratedTaskfile.tsx index 66b6631..202ccb1 100644 --- a/src/components/Generator/GeneredTaskfile/GeneratedTaskfile.tsx +++ b/src/components/Generator/GeneredTaskfile/GeneratedTaskfile.tsx @@ -1,6 +1,6 @@ 'use client'; -import { ReactElement, useState } from 'react'; +import { ReactElement } from 'react'; import { taskfile } from '@/components/Generator/GeneredTaskfile/taskfile'; import { useFormContext } from 'react-hook-form'; import { GeneratorSettings } from '@/components/Generator/Generator'; @@ -8,8 +8,6 @@ import CopyToClipboard from '@/components/Generator/GeneredTaskfile/Copy'; import { highlighter } from './Highlighter'; const GeneratedTaskfile = (): ReactElement => { - const [showHighlighting, setShowHighlighting] = useState(true); - const form = useFormContext(); const settings = form.watch(); @@ -19,9 +17,7 @@ const GeneratedTaskfile = (): ReactElement => { return ( <> navigator.clipboard.writeText(resultTaskfile)} /> -
 setShowHighlighting(!showHighlighting)}>
-				{showHighlighting ? highlighter(resultTaskfile) : resultTaskfile}
-			
+
{highlighter(resultTaskfile)}
); }; diff --git a/src/components/Generator/GeneredTaskfile/Highlighter/Highlighter.tsx b/src/components/Generator/GeneredTaskfile/Highlighter/Highlighter.tsx index 7bfed06..3211a5b 100644 --- a/src/components/Generator/GeneredTaskfile/Highlighter/Highlighter.tsx +++ b/src/components/Generator/GeneredTaskfile/Highlighter/Highlighter.tsx @@ -1,6 +1,7 @@ import { ReactElement } from 'react'; -import { styles } from './styles'; +import styles from './highlighter.module.css'; + import { lineRenderers } from './lineRenderers'; export const highlighter = (code: string): ReactElement[] => { @@ -15,7 +16,7 @@ export const highlighter = (code: string): ReactElement[] => { } return ( -
+
{line}
); diff --git a/src/components/Generator/GeneredTaskfile/Highlighter/highlighter.module.css b/src/components/Generator/GeneredTaskfile/Highlighter/highlighter.module.css new file mode 100644 index 0000000..1dcda92 --- /dev/null +++ b/src/components/Generator/GeneredTaskfile/Highlighter/highlighter.module.css @@ -0,0 +1,35 @@ +.line { + display: flex; +} + +.text-white { + color: #fff; +} + +.text-gray { + color: #888589; +} + +.text-yellow { + color: #fde047; +} + +.text-yellow-light { + color: #fef08a; +} + +.text-pink { + color: #ec4899; +} + +.text-purple { + color: #a855f7; +} + +.text-blue { + color: #60a5fa; +} + +.text-blue-light { + color: #93c5fd; +} diff --git a/src/components/Generator/GeneredTaskfile/Highlighter/lineRenderers.tsx b/src/components/Generator/GeneredTaskfile/Highlighter/lineRenderers.tsx index ee4b8ea..7c07dca 100644 --- a/src/components/Generator/GeneredTaskfile/Highlighter/lineRenderers.tsx +++ b/src/components/Generator/GeneredTaskfile/Highlighter/lineRenderers.tsx @@ -1,7 +1,8 @@ import React from 'react'; import { LineRenderer, RendererType } from './types'; -import { styles } from './styles'; + +import styles from './highlighter.module.css'; export const lineRenderers: LineRenderer[] = [ { @@ -15,10 +16,10 @@ export const lineRenderers: LineRenderer[] = [ render: (line, i) => { const [, name, rest] = line.match(/^function\s+([a-zA-Z_:]+)(.*)/) || []; return ( -
- function - {name} - {rest} +
+ function + {name} + {rest}
); }, @@ -27,7 +28,7 @@ export const lineRenderers: LineRenderer[] = [ type: RendererType.Comments, test: (line) => line.trim().startsWith('#'), render: (line, i) => ( -
+
{line}
), @@ -38,10 +39,10 @@ export const lineRenderers: LineRenderer[] = [ render: (line, i) => { const [varName, ...rest] = line.split('='); return ( -
- {varName} - = - {rest.join('=')} +
+ {varName} + = + {rest.join('=')}
); }, @@ -50,7 +51,7 @@ export const lineRenderers: LineRenderer[] = [ type: RendererType.Conditionals, test: (line) => /^\s*if\s+|^\s*then\s+|^\s*else\s+|^\s*fi\s*/.test(line), render: (line, i) => ( -
+
{line}
), @@ -61,10 +62,10 @@ export const lineRenderers: LineRenderer[] = [ render: (line, i) => { const parts = line.split('echo'); return ( -
- {parts[0]} - echo - {parts[1]} +
+ {parts[0]} + echo + {parts[1]}
); }, diff --git a/src/components/Generator/GeneredTaskfile/Highlighter/styles.ts b/src/components/Generator/GeneredTaskfile/Highlighter/styles.ts deleted file mode 100644 index 8e9991e..0000000 --- a/src/components/Generator/GeneredTaskfile/Highlighter/styles.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { StylesConfig } from './types'; - -export const styles: StylesConfig = { - line: { - display: 'flex', - }, - colors: { - white: { color: '#fff' }, - purple: { color: '#a855f7' }, - yellow: { color: '#fde047' }, - green: { color: '#22c55e' }, - blue: { color: '#60a5fa' }, - pink: { color: '#ec4899' }, - lightYellow: { color: '#fef08a' }, - lightBlue: { color: '#93c5fd' }, - gray: { color: '#888589' }, - }, -}; diff --git a/src/components/Generator/GeneredTaskfile/Highlighter/types.ts b/src/components/Generator/GeneredTaskfile/Highlighter/types.ts index 0f7cf33..41fa6a8 100644 --- a/src/components/Generator/GeneredTaskfile/Highlighter/types.ts +++ b/src/components/Generator/GeneredTaskfile/Highlighter/types.ts @@ -1,12 +1,5 @@ import { ReactElement } from 'react'; -type ColorName = 'white' | 'purple' | 'yellow' | 'green' | 'blue' | 'pink' | 'lightYellow' | 'lightBlue' | 'gray'; - -export type StylesConfig = { - line: { display: string }; - colors: Record; -}; - export enum RendererType { EmptyLines, FunctionDefinitions, From f72d0c15ee39bb026722b95c07448f265fd62a5e Mon Sep 17 00:00:00 2001 From: Arthur Geel Date: Fri, 15 Nov 2024 18:08:36 +0100 Subject: [PATCH 3/3] Prefer using Records over of keyed arrays --- .../Highlighter/Highlighter.tsx | 2 +- .../Highlighter/lineRenderers.tsx | 40 +++++++++++-------- .../GeneredTaskfile/Highlighter/types.ts | 16 -------- 3 files changed, 24 insertions(+), 34 deletions(-) delete mode 100644 src/components/Generator/GeneredTaskfile/Highlighter/types.ts diff --git a/src/components/Generator/GeneredTaskfile/Highlighter/Highlighter.tsx b/src/components/Generator/GeneredTaskfile/Highlighter/Highlighter.tsx index 3211a5b..c379269 100644 --- a/src/components/Generator/GeneredTaskfile/Highlighter/Highlighter.tsx +++ b/src/components/Generator/GeneredTaskfile/Highlighter/Highlighter.tsx @@ -9,7 +9,7 @@ export const highlighter = (code: string): ReactElement[] => { const lines = normalizedCode.split('\n').slice(0, -1); return lines.map((line, index) => { - const renderer = lineRenderers.find((r) => r.test(line)); + const renderer = Object.values(lineRenderers).find((r) => r.test(line)); if (renderer) { return renderer.render(line, index); diff --git a/src/components/Generator/GeneredTaskfile/Highlighter/lineRenderers.tsx b/src/components/Generator/GeneredTaskfile/Highlighter/lineRenderers.tsx index 7c07dca..f6d94ec 100644 --- a/src/components/Generator/GeneredTaskfile/Highlighter/lineRenderers.tsx +++ b/src/components/Generator/GeneredTaskfile/Highlighter/lineRenderers.tsx @@ -1,17 +1,27 @@ -import React from 'react'; - -import { LineRenderer, RendererType } from './types'; +import React, { ReactElement } from 'react'; import styles from './highlighter.module.css'; -export const lineRenderers: LineRenderer[] = [ - { - type: RendererType.EmptyLines, +enum RendererType { + EmptyLines, + FunctionDefinitions, + Comments, + Variables, + Conditionals, + EchoStatements, +} + +type LineRenderer = { + test: (line: string) => boolean; + render: (line: string, index: number) => ReactElement; +}; + +export const lineRenderers: Record = { + [RendererType.EmptyLines]: { test: (line) => line.trim() === '', render: (_, i) =>
 
, }, - { - type: RendererType.FunctionDefinitions, + [RendererType.FunctionDefinitions]: { test: (line) => /^function\s+[a-zA-Z_:]+/.test(line), render: (line, i) => { const [, name, rest] = line.match(/^function\s+([a-zA-Z_:]+)(.*)/) || []; @@ -24,8 +34,7 @@ export const lineRenderers: LineRenderer[] = [ ); }, }, - { - type: RendererType.Comments, + [RendererType.Comments]: { test: (line) => line.trim().startsWith('#'), render: (line, i) => (
@@ -33,8 +42,7 @@ export const lineRenderers: LineRenderer[] = [
), }, - { - type: RendererType.Variables, + [RendererType.Variables]: { test: (line) => /^[A-Z_]+=.*/.test(line), render: (line, i) => { const [varName, ...rest] = line.split('='); @@ -47,8 +55,7 @@ export const lineRenderers: LineRenderer[] = [ ); }, }, - { - type: RendererType.Conditionals, + [RendererType.Conditionals]: { test: (line) => /^\s*if\s+|^\s*then\s+|^\s*else\s+|^\s*fi\s*/.test(line), render: (line, i) => (
@@ -56,8 +63,7 @@ export const lineRenderers: LineRenderer[] = [
), }, - { - type: RendererType.EchoStatements, + [RendererType.EchoStatements]: { test: (line) => line.includes('echo'), render: (line, i) => { const parts = line.split('echo'); @@ -70,4 +76,4 @@ export const lineRenderers: LineRenderer[] = [ ); }, }, -]; +}; diff --git a/src/components/Generator/GeneredTaskfile/Highlighter/types.ts b/src/components/Generator/GeneredTaskfile/Highlighter/types.ts deleted file mode 100644 index 41fa6a8..0000000 --- a/src/components/Generator/GeneredTaskfile/Highlighter/types.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ReactElement } from 'react'; - -export enum RendererType { - EmptyLines, - FunctionDefinitions, - Comments, - Variables, - Conditionals, - EchoStatements, -} - -export type LineRenderer = { - type: RendererType; - test: (line: string) => boolean; - render: (line: string, index: number) => ReactElement; -};