Skip to content

Commit 7e5318c

Browse files
committed
feat: Autodetect Syntax Language
1 parent a9aafc1 commit 7e5318c

3 files changed

Lines changed: 107 additions & 27 deletions

File tree

react-compiler.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export const REACT_COMPILER_ENABLED_DIRS = [
5656
"src/components/shared/ReactFlow/FlowCanvas/TaskNode/ArgumentsEditor/DynamicDataDropdown.tsx",
5757
"src/components/shared/ReactFlow/FlowCanvas/Multiselect",
5858
"src/components/shared/CodeViewer/CodeEditor.tsx",
59+
"src/components/shared/Dialogs/MultilineTextInputDialog.tsx",
5960
"src/components/shared/HighlightText.tsx",
6061
"src/components/shared/AnnouncementBanners.tsx",
6162

src/components/shared/Dialogs/MultilineTextInputDialog.tsx

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type ReactNode, useCallback, useEffect, useState } from "react";
1+
import { type ReactNode, useEffect, useState } from "react";
22

33
import { Button } from "@/components/ui/button";
44
import {
@@ -19,18 +19,14 @@ import {
1919
import { Textarea } from "@/components/ui/textarea";
2020
import { Paragraph } from "@/components/ui/typography";
2121
import { cn } from "@/lib/utils";
22+
import {
23+
detectLanguage,
24+
isLanguageOption,
25+
LANGUAGE_OPTIONS,
26+
} from "@/utils/detectLanguage";
2227

2328
import CodeEditor from "../CodeViewer/CodeEditor";
2429

25-
const LANGUAGE_OPTIONS = [
26-
{ value: "plaintext", label: "Plain Text" },
27-
{ value: "yaml", label: "YAML" },
28-
{ value: "python", label: "Python" },
29-
{ value: "javascript", label: "JavaScript" },
30-
{ value: "json", label: "JSON" },
31-
{ value: "sql", label: "SQL" },
32-
];
33-
3430
interface MultilineTextInputDialogProps {
3531
title: ReactNode;
3632
description?: string;
@@ -57,34 +53,39 @@ export const MultilineTextInputDialog = ({
5753
onConfirm,
5854
}: MultilineTextInputDialogProps) => {
5955
const [value, setValue] = useState(initialValue);
60-
const [selectedLanguage, setSelectedLanguage] = useState("plaintext");
56+
const [selectedLanguage, setSelectedLanguage] = useState(() =>
57+
detectLanguage(initialValue),
58+
);
6159

62-
const handleConfirm = useCallback(() => {
60+
const handleConfirm = () => {
6361
onConfirm(value);
64-
}, [value, onConfirm]);
62+
};
6563

66-
const handleCancel = useCallback(() => {
64+
const handleCancel = () => {
6765
setValue(initialValue);
6866
onCancel();
69-
}, [initialValue, onCancel]);
67+
};
7068

71-
const setCursorToEnd = useCallback(
72-
(ref: HTMLTextAreaElement | null) => {
73-
if (ref && open) {
74-
ref.focus();
75-
ref.setSelectionRange(ref.value.length, ref.value.length);
76-
}
77-
},
78-
[open],
79-
);
69+
const handleSelectValueChange = (v: string) => {
70+
if (isLanguageOption(v)) {
71+
setSelectedLanguage(v);
72+
}
73+
};
74+
75+
const setCursorToEnd = (ref: HTMLTextAreaElement | null) => {
76+
if (ref && open) {
77+
ref.focus();
78+
ref.setSelectionRange(ref.value.length, ref.value.length);
79+
}
80+
};
8081

8182
useEffect(() => {
8283
setValue(initialValue);
8384
}, [initialValue]);
8485

8586
useEffect(() => {
86-
setSelectedLanguage("plaintext");
87-
}, [highlightSyntax]);
87+
setSelectedLanguage(detectLanguage(initialValue));
88+
}, [initialValue]);
8889

8990
return (
9091
<Dialog open={open} onOpenChange={onCancel}>
@@ -97,7 +98,7 @@ export const MultilineTextInputDialog = ({
9798
{highlightSyntax && (
9899
<Select
99100
value={selectedLanguage}
100-
onValueChange={setSelectedLanguage}
101+
onValueChange={handleSelectValueChange}
101102
>
102103
<SelectTrigger className="w-40">
103104
<SelectValue />

src/utils/detectLanguage.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
export const LANGUAGE_OPTIONS = [
2+
{ value: "plaintext", label: "Plain Text" },
3+
{ value: "yaml", label: "YAML" },
4+
{ value: "python", label: "Python" },
5+
{ value: "javascript", label: "JavaScript" },
6+
{ value: "json", label: "JSON" },
7+
{ value: "sql", label: "SQL" },
8+
] as const;
9+
10+
type LanguageOption = (typeof LANGUAGE_OPTIONS)[number]["value"];
11+
12+
export function isLanguageOption(value: string): value is LanguageOption {
13+
return LANGUAGE_OPTIONS.some((opt) => opt.value === value);
14+
}
15+
16+
/**
17+
* Heuristically detects the language of a string from the supported Monaco
18+
* language set: json, sql, python, javascript, yaml, plaintext.
19+
*
20+
* Detection is ordered from most-certain to least-certain.
21+
*/
22+
export function detectLanguage(value: string): LanguageOption {
23+
const trimmed = value.trim();
24+
25+
if (!trimmed) return "plaintext";
26+
27+
// JSON — most definitive: valid parse + structural start character
28+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
29+
try {
30+
JSON.parse(trimmed);
31+
return "json";
32+
} catch {
33+
// not valid json, fall through
34+
}
35+
}
36+
37+
// SQL — distinctive opening keywords
38+
if (
39+
/^(SELECT|INSERT|UPDATE|DELETE|CREATE|ALTER|DROP|WITH|TRUNCATE|MERGE)\b/im.test(
40+
trimmed,
41+
)
42+
) {
43+
return "sql";
44+
}
45+
46+
// Python — function/class definitions, module imports, decorators, docstrings
47+
if (
48+
/^(def |async def |class \w+\s*[:(])/m.test(trimmed) ||
49+
/^from\s+\S+\s+import\s/m.test(trimmed) ||
50+
/^import\s+\w[\w., ]*$/m.test(trimmed) ||
51+
/^@\w+/m.test(trimmed) ||
52+
/^if\s+__name__\s*==\s*["']__main__["']/m.test(trimmed)
53+
) {
54+
return "python";
55+
}
56+
57+
// JavaScript — const/let/var declarations, arrow functions, CommonJS/ESM imports
58+
if (
59+
/^(const|let|var)\s+\w/m.test(trimmed) ||
60+
/^(export\s+(default\s+)?|import\s+.*\s+from\s+['"])/m.test(trimmed) ||
61+
/^function\s+\w/m.test(trimmed) ||
62+
/\brequire\s*\(/.test(trimmed) ||
63+
/=>/.test(trimmed)
64+
) {
65+
return "javascript";
66+
}
67+
68+
// YAML — document separator, key: value pairs, or list items
69+
if (
70+
/^---/.test(trimmed) ||
71+
/^\w[\w\s-]*:\s/m.test(trimmed) ||
72+
/^- /m.test(trimmed)
73+
) {
74+
return "yaml";
75+
}
76+
77+
return "plaintext";
78+
}

0 commit comments

Comments
 (0)