Skip to content

Commit f61e4e8

Browse files
committed
feat: support json and jinja in code editor
feat: support line wrapping in code editor refactor: lazy load code editor language support fix: disable grammarly in code editor refactor: reduce code editor linter delay fix: stop linting python with jshint
1 parent 503cdf6 commit f61e4e8

File tree

5 files changed

+247
-76
lines changed

5 files changed

+247
-76
lines changed

.eslintrc.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
/** @type {import('eslint').Linter.Config} */
22
module.exports = {
33
extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"],
4+
rules: {
5+
"@typescript-eslint/no-unused-vars": "warn",
6+
"no-empty": "off",
7+
},
48
};

app/components/CodeEditor.tsx

Lines changed: 95 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,78 @@
1-
import { javascript } from "@codemirror/lang-javascript";
2-
import { python } from "@codemirror/lang-python";
3-
import { indentUnit } from "@codemirror/language";
4-
import { lintGutter, linter } from "@codemirror/lint";
5-
import { dracula } from "@uiw/codemirror-theme-dracula";
6-
import CodeMirror from "@uiw/react-codemirror";
7-
import type { LintOptions } from "jshint";
8-
import { JSHINT as jshint } from "jshint";
1+
import CodeMirror, {
2+
type Extension,
3+
type ReactCodeMirrorRef,
4+
} from "@uiw/react-codemirror";
5+
import { useEffect, useRef, useState } from "react";
96
import type { OnChange } from "~/app";
107
import { InputLabel, useGooeyStringInput } from "~/gooeyInput";
8+
import { EditorView } from "@codemirror/view";
9+
import { vscodeLightInit as theme } from "@uiw/codemirror-theme-vscode";
1110

12-
const jsLinter = (lintOptions: LintOptions) => {
13-
return linter((view) => {
14-
const diagnostics: any = [];
15-
const codeText = view.state.doc.toJSON();
16-
jshint(codeText, lintOptions);
17-
const errors = jshint?.data()?.errors;
18-
19-
if (errors && errors.length > 0) {
20-
errors.forEach((error) => {
21-
const selectedLine = view.state.doc.line(error.line);
22-
23-
const diagnostic = {
24-
from: selectedLine.from,
25-
to: selectedLine.to,
26-
severity: "error",
27-
message: error.reason,
28-
};
29-
30-
diagnostics.push(diagnostic);
31-
});
32-
}
33-
return diagnostics;
34-
});
11+
const linterDelay = 300;
12+
13+
const codeEditorExtensions: Record<string, () => Promise<Extension[]>> = {
14+
async jinja() {
15+
const { jinja } = await import("@codemirror/lang-jinja");
16+
const { markdown } = await import("@codemirror/lang-markdown");
17+
18+
return [jinja({ base: markdown() })];
19+
},
20+
async json() {
21+
const { json, jsonParseLinter } = await import("@codemirror/lang-json");
22+
const { linter, lintGutter } = await import("@codemirror/lint");
23+
24+
return [
25+
lintGutter(),
26+
json(),
27+
linter(jsonParseLinter(), { delay: linterDelay }),
28+
];
29+
},
30+
async python() {
31+
const { python } = await import("@codemirror/lang-python");
32+
const { indentUnit } = await import("@codemirror/language");
33+
34+
return [python(), indentUnit.of(" ")];
35+
},
36+
async javascript() {
37+
const { javascript } = await import("@codemirror/lang-javascript");
38+
const { linter, lintGutter } = await import("@codemirror/lint");
39+
const { JSHINT } = (await import("jshint")).default;
40+
41+
let lintOptions = {
42+
esversion: 11,
43+
browser: true,
44+
lastsemic: true,
45+
asi: true,
46+
expr: true,
47+
};
48+
49+
return [
50+
lintGutter(),
51+
javascript(),
52+
linter(
53+
(view: EditorView) => {
54+
const diagnostics: any = [];
55+
const codeText = view.state.doc.toJSON();
56+
JSHINT(codeText, lintOptions);
57+
const errors = JSHINT.data()?.errors;
58+
errors?.forEach((error) => {
59+
const selectedLine = view.state.doc.line(error.line);
60+
61+
const diagnostic = {
62+
from: selectedLine.from,
63+
to: selectedLine.to,
64+
severity: "error",
65+
message: error.reason,
66+
};
67+
68+
diagnostics.push(diagnostic);
69+
});
70+
return diagnostics;
71+
},
72+
{ delay: linterDelay }
73+
),
74+
];
75+
},
3576
};
3677

3778
export default function CodeEditor({
@@ -59,6 +100,7 @@ export default function CodeEditor({
59100
defaultValue,
60101
args,
61102
});
103+
62104
const handleChange = (val: string) => {
63105
setValue(val);
64106
// trigger the onChange event for Root Form
@@ -68,20 +110,21 @@ export default function CodeEditor({
68110
});
69111
};
70112

71-
const lintOptions: LintOptions = {
72-
esversion: 11,
73-
browser: true,
74-
lastsemic: true,
75-
asi: true,
76-
expr: true,
77-
};
113+
let [extensions, setExtensions] = useState<Extension[]>([]);
114+
115+
useEffect(() => {
116+
codeEditorExtensions[language]?.call(null).then(setExtensions);
117+
}, [language]);
118+
119+
extensions.push(EditorView.lineWrapping);
120+
121+
const ref = useRef<ReactCodeMirrorRef>(null);
78122

79-
let extensions = [lintGutter()];
80-
if (language === "javascript") {
81-
extensions.push(javascript(), jsLinter(lintOptions));
82-
} else if (language === "python") {
83-
extensions.push(python(), indentUnit.of(" "));
84-
}
123+
useEffect(() => {
124+
ref.current?.editor
125+
?.querySelector(".cm-content")
126+
?.setAttribute("data-gramm", "false");
127+
}, [ref.current]);
85128

86129
return (
87130
<div className="gui-input code-editor-wrapper position-relative">
@@ -91,9 +134,6 @@ export default function CodeEditor({
91134
tooltipPlacement={tooltipPlacement}
92135
/>
93136
<textarea
94-
data-gramm="false"
95-
data-gramm_editor="false"
96-
data-enable-grammarly="false"
97137
ref={inputRef}
98138
name={name}
99139
value={value}
@@ -102,13 +142,17 @@ export default function CodeEditor({
102142
}}
103143
/>
104144
<CodeMirror
105-
data-gramm="false"
106-
data-gramm_editor="false"
107-
data-enable-grammarly="false"
108-
theme={dracula}
145+
ref={ref}
146+
theme={theme({
147+
settings: {
148+
fontFamily: "monospace",
149+
fontSize: "14px",
150+
},
151+
})}
109152
value={value}
110153
onChange={handleChange}
111154
extensions={extensions}
155+
basicSetup={{ foldGutter: false }}
112156
{...args}
113157
/>
114158
</div>

app/renderer.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ const CodeEditor = lazyImport(() => import("./components/CodeEditor"), {
5757
<div className="gui-input">
5858
<InputLabel label={props.label} />
5959
<div
60-
className="d-flex align-items-center justify-content-center bg-dark text-white"
60+
className="d-flex align-items-center justify-content-center"
6161
style={{
6262
height: (props.defaultValue || "").split("\n").length * 1.5 + "rem",
6363
...props.style,
@@ -476,7 +476,13 @@ function RenderedTreeNode({
476476
}
477477
case "plotly-chart": {
478478
const { chart } = props;
479-
return <Plot {...chart} style={{ width: "100%" }} config={{ displayModeBar: false }} />;
479+
return (
480+
<Plot
481+
{...chart}
482+
style={{ width: "100%" }}
483+
config={{ displayModeBar: false }}
484+
/>
485+
);
480486
}
481487
case "tooltip":
482488
const { content, placement } = props;

0 commit comments

Comments
 (0)