diff --git a/README.md b/README.md index 9e2c49a..812a11b 100644 --- a/README.md +++ b/README.md @@ -32,16 +32,13 @@ Bundle: `bun run build` -**NOTE**: The following tasks use Deno as it excels at these and Bun does not -currently provide such functionality: - Format: -`deno fmt` +`bunx oxfmt` Lint: -`deno lint src/ tests/` +`bunx oxlint index.ts src/ tests/` ## Functional Tests diff --git a/bun.lock b/bun.lock index 3e38a43..4fc8808 100644 --- a/bun.lock +++ b/bun.lock @@ -9,7 +9,7 @@ "@codemirror/lint": "6.9.2", "@lezer/common": "1.5.0", "@lezer/highlight": "1.2.3", - "@mpeggroup/mpeg-sdl-parser": "4.1.2", + "@mpeggroup/mpeg-sdl-parser": "4.1.4", "@types/wicg-file-system-access": "2023.10.7", "@uiw/codemirror-theme-vscode": "4.25.10", "@uiw/react-codemirror": "4.25.10", @@ -77,7 +77,7 @@ "@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="], - "@mpeggroup/mpeg-sdl-parser": ["@mpeggroup/mpeg-sdl-parser@4.1.2", "", { "dependencies": { "@codemirror/language": "6.12.3", "@codemirror/state": "6.5.4", "@lezer/common": "1.5.0", "@lezer/generator": "1.8.0", "@lezer/highlight": "1.2.3", "@lezer/lr": "1.4.7", "prettier": "3.8.3" }, "peerDependencies": { "typescript": "6.0.3" } }, "sha512-GKTSu4cSFgpN3Hl7i5be5JIG4PPMemz0ylDGD27Ef6fBKp92yLTk/mYPUI38GR3D1+TR/2WBRNU+gx6Sn9TG2g=="], + "@mpeggroup/mpeg-sdl-parser": ["@mpeggroup/mpeg-sdl-parser@4.1.4", "", { "dependencies": { "@codemirror/language": "6.12.3", "@codemirror/state": "6.5.4", "@lezer/common": "1.5.0", "@lezer/generator": "1.8.0", "@lezer/highlight": "1.2.3", "@lezer/lr": "1.4.7", "prettier": "3.8.3" }, "peerDependencies": { "typescript": "6.0.3" } }, "sha512-xrnDQlEtUYIbz1yk1GUdWR1UdAMSawBMziYBgWIfggvuJz0uuZ7elZMug08mMeVv3GkVbX+WYlDtCRcKJgGy9g=="], "@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.3.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Omj20SuiHBOUjUBIyqtkNjSUIjOtEOJwmbix/ZyFH4BaQ6OZTaaRWIR4TjHVz0yadHgli6lLTiAh1uarnvD49A=="], diff --git a/html/index.html b/html/index.html index 49f5a03..a76cec9 100644 --- a/html/index.html +++ b/html/index.html @@ -1,4 +1,4 @@ - + diff --git a/package.json b/package.json index 1ed72e1..0c90b78 100644 --- a/package.json +++ b/package.json @@ -1,46 +1,33 @@ { "name": "@mpeggroup/mpeg-sdl-editor", + "version": "1.4.2", "description": "ISO/IEC 14496-34 Syntactic Description Language (MPEG SDL) web based editor", - "homepage": "https://github.com/MPEGGroup/mpeg-sdl-editor#readme", - "repository": { - "type": "git", - "url": "git+https://github.com/MPEGGroup/mpeg-sdl-editor.git" - }, - "license": "MIT", "keywords": [ "bun", + "editor", "mpeg", - "sdl", "parser", - "editor" + "sdl" ], - "type": "module", - "version": "1.4.2", - "scripts": { - "build": "bun build.ts" + "homepage": "https://github.com/MPEGGroup/mpeg-sdl-editor#readme", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/MPEGGroup/mpeg-sdl-editor.git" }, + "type": "module", "publishConfig": { "access": "public" }, - "devDependencies": { - "@happy-dom/global-registrator": "20.9.0", - "@testing-library/dom": "10.4.1", - "@testing-library/react": "16.3.2", - "@types/bun": "1.3.14", - "bun-plugin-tailwind": "0.1.2", - "happy-dom": "20.9.0" - }, - "peerDependencies": { - "typescript": "6.0.3", - "@types/react": "19.2.15", - "@types/react-dom": "19.2.3" + "scripts": { + "build": "bun build.ts" }, "dependencies": { "@codemirror/language": "6.12.1", "@codemirror/lint": "6.9.2", "@lezer/common": "1.5.0", "@lezer/highlight": "1.2.3", - "@mpeggroup/mpeg-sdl-parser": "4.1.2", + "@mpeggroup/mpeg-sdl-parser": "4.1.4", "@types/wicg-file-system-access": "2023.10.7", "@uiw/codemirror-theme-vscode": "4.25.10", "@uiw/react-codemirror": "4.25.10", @@ -49,6 +36,19 @@ "react-dom": "19.2.6", "tailwindcss": "4.3.0" }, + "devDependencies": { + "@happy-dom/global-registrator": "20.9.0", + "@testing-library/dom": "10.4.1", + "@testing-library/react": "16.3.2", + "@types/bun": "1.3.14", + "bun-plugin-tailwind": "0.1.2", + "happy-dom": "20.9.0" + }, + "peerDependencies": { + "@types/react": "19.2.15", + "@types/react-dom": "19.2.3", + "typescript": "6.0.3" + }, "overrides": { "@codemirror/language": "6.12.1", "@codemirror/lint": "6.9.2", diff --git a/src/App.tsx b/src/App.tsx index 2c644b7..44446ee 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,4 @@ -import { - SemanticError, - SemanticWarning, - SyntaxError, -} from "@mpeggroup/mpeg-sdl-parser"; +import { SemanticError, SemanticWarning, SyntaxError } from "@mpeggroup/mpeg-sdl-parser"; import { Editor } from "./components/Editor.tsx"; import type { EditorRef } from "./components/Editor.tsx"; import { Navbar } from "./components/Navbar.tsx"; @@ -36,12 +32,9 @@ function getInitialCodeFromHash(): string | null { } export function App() { - const defaultCode = - "// Start typing your SDL here... for completions\n"; + const defaultCode = "// Start typing your SDL here... for completions\n"; - const [code, setCodeInternal] = useState( - () => getInitialCodeFromHash() ?? defaultCode, - ); + const [code, setCodeInternal] = useState(() => getInitialCodeFromHash() ?? defaultCode); const setCode = useCallback((newCode: string) => { setCodeInternal(newCode); @@ -62,34 +55,29 @@ export function App() { const isMobile = useMobileDetection(); const [isSettingsShown, setSettingsShown] = useState(false); const { rulerWidth, setRulerWidth } = useRulerWidth(); - const { autoDisplayCompletions, setAutoDisplayCompletions } = - useAutoDisplayCompletions(); + const { autoDisplayCompletions, setAutoDisplayCompletions } = useAutoDisplayCompletions(); const { enableLinting, setEnableLinting } = useEnableLinting(); const { showSyntaxErrors, setShowSyntaxErrors } = useShowSyntaxErrors(); const { showSemanticErrors, setShowSemanticErrors } = useShowSemanticErrors(); - const { showSemanticWarnings, setShowSemanticWarnings } = - useShowSemanticWarnings(); - const [cursorPosition, setCursorPosition] = useState< - { line: number; col: number } - >({ line: 1, col: 1 }); + const { showSemanticWarnings, setShowSemanticWarnings } = useShowSemanticWarnings(); + const [cursorPosition, setCursorPosition] = useState<{ line: number; col: number }>({ + line: 1, + col: 1, + }); const editorRef = useRef(null); - const onCursorChange = useCallback( - (position: { line: number; col: number }) => { - setCursorPosition((prev) => { - // Only update if position actually changed - if (prev.line !== position.line || prev.col !== position.col) { - return position; - } - return prev; - }); - }, - [], - ); + const onCursorChange = useCallback((position: { line: number; col: number }) => { + setCursorPosition((prev) => { + // Only update if position actually changed + if (prev.line !== position.line || prev.col !== position.col) { + return position; + } + return prev; + }); + }, []); const handleShare = useCallback(async () => { const encoded = btoa(code); - const url = - `${globalThis.location.origin}${globalThis.location.pathname}#c=${encoded}`; + const url = `${globalThis.location.origin}${globalThis.location.pathname}#c=${encoded}`; try { await navigator.clipboard.writeText(url); showToast("URL copied!"); @@ -98,11 +86,7 @@ export function App() { } }, [code, showToast]); - const { - handleSave, - handleLoad, - handleCopy, - } = useFileOperations({ + const { handleSave, handleLoad, handleCopy } = useFileOperations({ code, setCode, showToast, @@ -113,9 +97,7 @@ export function App() { const characterCount = useMemo(() => code.length, [code]); const [syntaxErrors, setSyntaxErrors] = useState([]); const [semanticErrors, setSemanticErrors] = useState([]); - const [semanticWarnings, setSemanticWarnings] = useState( - [], - ); + const [semanticWarnings, setSemanticWarnings] = useState([]); const onSyntaxErrorChange = useCallback((newErrors: SyntaxError[]) => { setSyntaxErrors(newErrors); @@ -125,24 +107,15 @@ export function App() { setSemanticErrors(newErrors); }, []); - const onSemanticWarningChange = useCallback( - (newErrors: SemanticWarning[]) => { - setSemanticWarnings(newErrors); - }, - [], - ); + const onSemanticWarningChange = useCallback((newErrors: SemanticWarning[]) => { + setSemanticWarnings(newErrors); + }, []); - const syntaxErrorCount = useMemo(() => syntaxErrors.length, [ - syntaxErrors, - ]); + const syntaxErrorCount = useMemo(() => syntaxErrors.length, [syntaxErrors]); - const semanticErrorCount = useMemo(() => semanticErrors.length, [ - semanticErrors, - ]); + const semanticErrorCount = useMemo(() => semanticErrors.length, [semanticErrors]); - const semanticWarningCount = useMemo(() => semanticWarnings.length, [ - semanticWarnings, - ]); + const semanticWarningCount = useMemo(() => semanticWarnings.length, [semanticWarnings]); const { handlePrettify } = usePrettier({ code, @@ -251,8 +224,8 @@ export function App() { toastState.type === "success" ? "alert-success" : toastState.type === "warning" - ? "alert-warning" - : "alert-error" + ? "alert-warning" + : "alert-error" }`} > {toastState.message} diff --git a/src/codemirror/ruler.ts b/src/codemirror/ruler.ts index 90e0ce3..a70de2b 100644 --- a/src/codemirror/ruler.ts +++ b/src/codemirror/ruler.ts @@ -7,7 +7,7 @@ const baseTheme = EditorView.baseTheme({ }); const rulerWidth = Facet.define({ - combine: (values) => values.length ? Math.min(...values) : 10, + combine: (values) => (values.length ? Math.min(...values) : 10), }); const showRuler = ViewPlugin.fromClass( @@ -38,9 +38,7 @@ const showRuler = ViewPlugin.fromClass( const defaultCharacterWidth = view.defaultCharacterWidth; const gutterWidth = view.contentDOM.getBoundingClientRect().x; - this.rulerDiv.style.left = `${ - gutterWidth + (column * defaultCharacterWidth) + 6 - }px`; + this.rulerDiv.style.left = `${gutterWidth + column * defaultCharacterWidth + 6}px`; } destroy() { @@ -50,9 +48,5 @@ const showRuler = ViewPlugin.fromClass( ); export function ruler(column: number): Extension { - return [ - baseTheme, - rulerWidth.of(column), - showRuler, - ]; + return [baseTheme, rulerWidth.of(column), showRuler]; } diff --git a/src/components/DesktopPanels.tsx b/src/components/DesktopPanels.tsx index 4b2ac65..a23d722 100644 --- a/src/components/DesktopPanels.tsx +++ b/src/components/DesktopPanels.tsx @@ -44,8 +44,7 @@ export function DesktopPanels({ ? "bg-primary-content h-16" : "group-hover:bg-base-content/50 group-hover:h-16" }`} - > - + > )} @@ -53,10 +52,7 @@ export function DesktopPanels({ {/* Right panel - only show when not collapsed */} {isSettingsShown && ( -
+
{rightChild}
)} diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index 2e5c8e9..6a58a85 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -1,10 +1,4 @@ -import { - forwardRef, - useCallback, - useImperativeHandle, - useMemo, - useRef, -} from "react"; +import { forwardRef, useCallback, useImperativeHandle, useMemo, useRef } from "react"; import CodeMirror, { ViewUpdate } from "@uiw/react-codemirror"; import { codeFolding, @@ -23,11 +17,7 @@ import { vscodeLightStyle, } from "@uiw/codemirror-theme-vscode"; import { EditorView } from "@codemirror/view"; -import { - SemanticError, - SemanticWarning, - SyntaxError, -} from "@mpeggroup/mpeg-sdl-parser"; +import { SemanticError, SemanticWarning, SyntaxError } from "@mpeggroup/mpeg-sdl-parser"; export { ViewUpdate } from "@codemirror/view"; import { lintGutter } from "@codemirror/lint"; import { autocompletion } from "@codemirror/autocomplete"; @@ -72,7 +62,7 @@ function extractThemeStyleAttributes(themeStyle: TagStyle[]) { let attributes = ""; Object.keys(style).forEach((key) => { - if ((key !== "tag") && (key !== "class")) { + if (key !== "tag" && key !== "class") { const value = style[key as keyof typeof style]; if (typeof value === "string") { @@ -89,14 +79,14 @@ function extractThemeStyleAttributes(themeStyle: TagStyle[]) { style.tag.forEach((tag) => { const actualTag = tag as unknown as TagWithNameAndModified; - if (actualTag.name && (actualTag.modified.length === 0)) { + if (actualTag.name && actualTag.modified.length === 0) { styleAttributesByTagName.set(actualTag.name, attributes); } }); } else { const actualTag = style.tag as unknown as TagWithNameAndModified; - if (actualTag.name && (actualTag.modified.length === 0)) { + if (actualTag.name && actualTag.modified.length === 0) { styleAttributesByTagName.set(actualTag.name, attributes); } } @@ -105,21 +95,12 @@ function extractThemeStyleAttributes(themeStyle: TagStyle[]) { return styleAttributesByTagName; } -const lightStyleAttributesByTagName = extractThemeStyleAttributes( - vscodeLightStyle, -); -const darkStyleAttributesByTagName = extractThemeStyleAttributes( - vscodeDarkStyle, -); +const lightStyleAttributesByTagName = extractThemeStyleAttributes(vscodeLightStyle); +const darkStyleAttributesByTagName = extractThemeStyleAttributes(vscodeDarkStyle); -function getStyledCode( - sdlLanguageSupport: LanguageSupport, - code: string, - theme: string, -): string { - const styleAttributesByTagName = theme === "dark" - ? darkStyleAttributesByTagName - : lightStyleAttributesByTagName; +function getStyledCode(sdlLanguageSupport: LanguageSupport, code: string, theme: string): string { + const styleAttributesByTagName = + theme === "dark" ? darkStyleAttributesByTagName : lightStyleAttributesByTagName; let richText = ""; @@ -148,7 +129,10 @@ function getStyledCode( let span; if (spanStyleAttributes) { - span = "" + + span = + "" + p.innerHTML.replaceAll(" ", " ") + ""; } else { @@ -204,10 +188,7 @@ export const Editor = forwardRef( const sdlLanguageSupport = useMemo(() => sdl(), []); // Memoize static extensions that never change - const staticExtensions = useMemo(() => [ - codeFolding(), - foldGutter(), - ], []); + const staticExtensions = useMemo(() => [codeFolding(), foldGutter()], []); const rulerExtension = useMemo(() => ruler(rulerWidth), [rulerWidth]); @@ -216,44 +197,35 @@ export const Editor = forwardRef( [autoDisplayCompletions], ); - const stableOnSyntaxErrorChange = useCallback( - (errors: SyntaxError[]) => { - onSyntaxErrorChangeRef.current(errors); - }, - [], - ); + const stableOnSyntaxErrorChange = useCallback((errors: SyntaxError[]) => { + onSyntaxErrorChangeRef.current(errors); + }, []); - const stableOnSemanticErrorChange = useCallback( - (errors: SemanticError[]) => { - onSemanticErrorChangeRef.current(errors); - }, - [], - ); + const stableOnSemanticErrorChange = useCallback((errors: SemanticError[]) => { + onSemanticErrorChangeRef.current(errors); + }, []); - const stableOnSemanticWarningChange = useCallback( - (errors: SemanticWarning[]) => { - onSemanticWarningChangeRef.current(errors); - }, - [], - ); + const stableOnSemanticWarningChange = useCallback((errors: SemanticWarning[]) => { + onSemanticWarningChangeRef.current(errors); + }, []); // Memoize dynamic extensions that depend on props const dynamicExtensions = useMemo( () => enableLinting ? [ - // TODO: only include the lint gutter if showSyntaxErrors, showSemanticErrors, or showSemanticWarnings is true - lintGutter(), - // TODO: pass showSyntaxErrors, showSemanticErrors, showSemanticWarnings to sdlLinter - sdlLinter({ - onSyntaxErrorChange: stableOnSyntaxErrorChange, - onSemanticErrorChange: stableOnSemanticErrorChange, - onSemanticWarningChange: stableOnSemanticWarningChange, - showSyntaxErrors, - showSemanticErrors, - showSemanticWarnings, - }), - ] + // TODO: only include the lint gutter if showSyntaxErrors, showSemanticErrors, or showSemanticWarnings is true + lintGutter(), + // TODO: pass showSyntaxErrors, showSemanticErrors, showSemanticWarnings to sdlLinter + sdlLinter({ + onSyntaxErrorChange: stableOnSyntaxErrorChange, + onSemanticErrorChange: stableOnSemanticErrorChange, + onSemanticWarningChange: stableOnSemanticWarningChange, + showSyntaxErrors, + showSemanticErrors, + showSemanticWarnings, + }), + ] : [], [ enableLinting, @@ -266,19 +238,22 @@ export const Editor = forwardRef( ], ); - const extensions = useMemo(() => [ - ...staticExtensions, - rulerExtension, - autocompletionExtension, - sdlLanguageSupport, - ...dynamicExtensions, - ], [ - staticExtensions, - rulerExtension, - autocompletionExtension, - sdlLanguageSupport, - dynamicExtensions, - ]); + const extensions = useMemo( + () => [ + ...staticExtensions, + rulerExtension, + autocompletionExtension, + sdlLanguageSupport, + ...dynamicExtensions, + ], + [ + staticExtensions, + rulerExtension, + autocompletionExtension, + sdlLanguageSupport, + dynamicExtensions, + ], + ); // Memoize imperative methods const expandAll = useCallback(() => { @@ -308,41 +283,51 @@ export const Editor = forwardRef( }, []); // Expose methods via ref - useImperativeHandle(ref, () => ({ - expandAll, - collapseAll, - getStyledCode: () => { - return getStyledCode(sdlLanguageSupport, code, theme); + useImperativeHandle( + ref, + () => ({ + expandAll, + collapseAll, + getStyledCode: () => { + return getStyledCode(sdlLanguageSupport, code, theme); + }, + }), + [expandAll, collapseAll, sdlLanguageSupport, code, theme], + ); + + const onInternalCodeChange = useCallback( + (newCode: string) => { + onCodeChange(newCode); }, - }), [expandAll, collapseAll, sdlLanguageSupport, code, theme]); - - const onInternalCodeChange = useCallback((newCode: string) => { - onCodeChange(newCode); - }, [onCodeChange]); - - const onViewUpdate = useCallback((viewUpdate: ViewUpdate) => { - if (viewUpdate.state) { - const state = viewUpdate.state; - const selection = state.selection.main; - - if (selection) { - const head = selection.head; - const line = state.doc.lineAt(head); - const lineNumber = line.number; - const columnNumber = head - line.from + 1; - - // Only update if position actually changed - if ( - lastCursorPosition.current.line !== lineNumber || - lastCursorPosition.current.col !== columnNumber - ) { - const newPosition = { line: lineNumber, col: columnNumber }; - lastCursorPosition.current = newPosition; - onCursorChange(newPosition); + [onCodeChange], + ); + + const onViewUpdate = useCallback( + (viewUpdate: ViewUpdate) => { + if (viewUpdate.state) { + const state = viewUpdate.state; + const selection = state.selection.main; + + if (selection) { + const head = selection.head; + const line = state.doc.lineAt(head); + const lineNumber = line.number; + const columnNumber = head - line.from + 1; + + // Only update if position actually changed + if ( + lastCursorPosition.current.line !== lineNumber || + lastCursorPosition.current.col !== columnNumber + ) { + const newPosition = { line: lineNumber, col: columnNumber }; + lastCursorPosition.current = newPosition; + onCursorChange(newPosition); + } } } - } - }, [onCursorChange]); + }, + [onCursorChange], + ); return (
diff --git a/src/components/MobileDrawer.tsx b/src/components/MobileDrawer.tsx index 90baa9a..bcf86e8 100644 --- a/src/components/MobileDrawer.tsx +++ b/src/components/MobileDrawer.tsx @@ -8,9 +8,12 @@ interface MobileDrawerProps { children: React.ReactNode; } -export function MobileDrawer( - { theme, isSettingsShown, onToggleSettings, children }: MobileDrawerProps, -) { +export function MobileDrawer({ + theme, + isSettingsShown, + onToggleSettings, + children, +}: MobileDrawerProps) { return ( <> {isSettingsShown && ( @@ -32,12 +35,7 @@ export function MobileDrawer( }`} title="Close settings" > - +
-
- {children} -
+
{children}
diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 03c0690..efc200a 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -71,12 +71,7 @@ export function Navbar({
  • -
  • } diff --git a/src/components/ResizableLayout.tsx b/src/components/ResizableLayout.tsx index 3c61527..3a70a69 100644 --- a/src/components/ResizableLayout.tsx +++ b/src/components/ResizableLayout.tsx @@ -38,32 +38,28 @@ export function ResizableLayout({ return (
    - {isMobile - ? ( - <> -
    - {children[0]} -
    + {isMobile ? ( + <> +
    {children[0]}
    - - {children[1]} - - - ) - : ( - - )} + onToggleSettings={onToggleSettings} + > + {children[1]} + + + ) : ( + + )}
    ); } diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index 2ff2b4d..ba51c55 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -18,29 +18,25 @@ interface SettingsProps { onShowSemanticWarningsChange?: (value: boolean) => void; } -export function Settings( - { - theme, - onToggleTheme, - rulerWidth, - onRulerWidthChange, - autoDisplayCompletions, - onAutoDisplayCompletionsChange, - enableLinting, - onEnableLintingChange, - showSyntaxErrors, - onShowSyntaxErrorsChange, - showSemanticErrors, - onShowSemanticErrorsChange, - showSemanticWarnings, - onShowSemanticWarningsChange, - }: SettingsProps, -) { +export function Settings({ + theme, + onToggleTheme, + rulerWidth, + onRulerWidthChange, + autoDisplayCompletions, + onAutoDisplayCompletionsChange, + enableLinting, + onEnableLintingChange, + showSyntaxErrors, + onShowSyntaxErrorsChange, + showSemanticErrors, + onShowSemanticErrorsChange, + showSemanticWarnings, + onShowSemanticWarningsChange, +}: SettingsProps) { return (
    -

    - Settings -

    +

    Settings

    Theme @@ -50,25 +46,23 @@ export function Settings( title={theme === "dark" ? "Light theme" : "Dark theme"} className="btn btn-sm border border-base-content/30 rounded-lg bg-transparent hover:bg-base-content/10 hover:border-base-content/30 [&_svg]:w-5 [&_svg]:h-5" > - {theme === "dark" - ? ( - - - - ) - : ( - - - - )} + {theme === "dark" ? ( + + + + ) : ( + + + + )}
    @@ -86,9 +80,7 @@ export function Settings(
    - - Auto-Display Completions - + Auto-Display Completions - onShowSyntaxErrorsChange?.(e.target.checked)} + onChange={(e) => onShowSyntaxErrorsChange?.(e.target.checked)} /> - onShowSemanticErrorsChange?.(e.target.checked)} + onChange={(e) => onShowSemanticErrorsChange?.(e.target.checked)} /> - onShowSemanticWarningsChange?.(e.target.checked)} + onChange={(e) => onShowSemanticWarningsChange?.(e.target.checked)} /> Lines: {lineCount} Characters: {characterCount} - {enableLinting && showSyntaxErrors && ( - Syntax Errors: {syntaxErrorCount} - )} - {enableLinting && showSemanticErrors && ( - Semantic Errors: {semanticErrorCount} - )} + {enableLinting && showSyntaxErrors && Syntax Errors: {syntaxErrorCount}} + {enableLinting && showSemanticErrors && Semantic Errors: {semanticErrorCount}} {enableLinting && showSemanticWarnings && ( Semantic Warnings: {semanticWarningCount} )} diff --git a/src/components/SubNav.tsx b/src/components/SubNav.tsx index af22882..3a01f86 100644 --- a/src/components/SubNav.tsx +++ b/src/components/SubNav.tsx @@ -7,12 +7,7 @@ interface SubNavProps { onPrettify: () => void; } -export function SubNav({ - theme, - onCollapseAll, - onExpandAll, - onPrettify, -}: SubNavProps) { +export function SubNav({ theme, onCollapseAll, onExpandAll, onPrettify }: SubNavProps) { return (
    (() => { + const [autoDisplayCompletions, setAutoDisplayCompletionsInternal] = useState(() => { const stored = localStorage.getItem("autoDisplayCompletions"); if (stored !== null) { return stored === "true"; @@ -12,10 +10,7 @@ export function useAutoDisplayCompletions(defaultValue: boolean = true) { }); useEffect(() => { - localStorage.setItem( - "autoDisplayCompletions", - String(autoDisplayCompletions), - ); + localStorage.setItem("autoDisplayCompletions", String(autoDisplayCompletions)); }, [autoDisplayCompletions]); const setAutoDisplayCompletions = useCallback((value: boolean) => { diff --git a/src/hooks/useDragResize.ts b/src/hooks/useDragResize.ts index f7eb481..32b678b 100644 --- a/src/hooks/useDragResize.ts +++ b/src/hooks/useDragResize.ts @@ -19,63 +19,69 @@ export function useDragResize({ const [dragStartX, setDragStartX] = useState(0); const [dragStartPercentage, setDragStartPercentage] = useState(0); - const handleMouseDown = useCallback((e: React.MouseEvent) => { - if (!isSettingsShown) { - return; - } - - e.preventDefault(); - - const container = containerRef.current; - - if (!container) { - return; - } - - const containerRect = container.getBoundingClientRect(); - const mouseX = e.clientX - containerRect.left; - const currentPercentage = (mouseX / containerRect.width) * 100; - - setDragStartX(e.clientX); - setDragStartPercentage(currentPercentage); - setIsDragging(true); - }, [isSettingsShown, containerRef]); - - const handleMouseMove = useCallback((e: MouseEvent) => { - if (!isDragging || !containerRef.current || !isSettingsShown) { - return; - } - - const container = containerRef.current; - const containerRect = container.getBoundingClientRect(); - const containerWidth = containerRect.width; - - // Calculate the delta from where the drag started - const deltaX = e.clientX - dragStartX; - const deltaPercentage = (deltaX / containerWidth) * 100; - - // Calculate new percentage based on drag start position + delta - let newPercentage = dragStartPercentage + deltaPercentage; - - // Apply minimum width constraints - const minLeftPercentage = (minLeftWidth / containerWidth) * 100; - const minRightPercentage = (minRightWidth / containerWidth) * 100; - - // Clamp the percentage to valid bounds - newPercentage = Math.max(minLeftPercentage, newPercentage); - newPercentage = Math.min(100 - minRightPercentage, newPercentage); - - onSplitPercentageChange(newPercentage); - }, [ - isDragging, - dragStartX, - dragStartPercentage, - minLeftWidth, - minRightWidth, - isSettingsShown, - onSplitPercentageChange, - containerRef, - ]); + const handleMouseDown = useCallback( + (e: React.MouseEvent) => { + if (!isSettingsShown) { + return; + } + + e.preventDefault(); + + const container = containerRef.current; + + if (!container) { + return; + } + + const containerRect = container.getBoundingClientRect(); + const mouseX = e.clientX - containerRect.left; + const currentPercentage = (mouseX / containerRect.width) * 100; + + setDragStartX(e.clientX); + setDragStartPercentage(currentPercentage); + setIsDragging(true); + }, + [isSettingsShown, containerRef], + ); + + const handleMouseMove = useCallback( + (e: MouseEvent) => { + if (!isDragging || !containerRef.current || !isSettingsShown) { + return; + } + + const container = containerRef.current; + const containerRect = container.getBoundingClientRect(); + const containerWidth = containerRect.width; + + // Calculate the delta from where the drag started + const deltaX = e.clientX - dragStartX; + const deltaPercentage = (deltaX / containerWidth) * 100; + + // Calculate new percentage based on drag start position + delta + let newPercentage = dragStartPercentage + deltaPercentage; + + // Apply minimum width constraints + const minLeftPercentage = (minLeftWidth / containerWidth) * 100; + const minRightPercentage = (minRightWidth / containerWidth) * 100; + + // Clamp the percentage to valid bounds + newPercentage = Math.max(minLeftPercentage, newPercentage); + newPercentage = Math.min(100 - minRightPercentage, newPercentage); + + onSplitPercentageChange(newPercentage); + }, + [ + isDragging, + dragStartX, + dragStartPercentage, + minLeftWidth, + minRightWidth, + isSettingsShown, + onSplitPercentageChange, + containerRef, + ], + ); const handleMouseUp = useCallback(() => { setIsDragging(false); diff --git a/src/hooks/useFileOperations.ts b/src/hooks/useFileOperations.ts index 48424c7..b315755 100644 --- a/src/hooks/useFileOperations.ts +++ b/src/hooks/useFileOperations.ts @@ -9,9 +9,7 @@ interface UseFileOperationsProps { editorRef: React.RefObject; } -export function useFileOperations( - { code, setCode, showToast, editorRef }: UseFileOperationsProps, -) { +export function useFileOperations({ code, setCode, showToast, editorRef }: UseFileOperationsProps) { const handleSave = useCallback(async () => { try { if (globalThis.window.showSaveFilePicker) { diff --git a/src/hooks/useMobileDetection.ts b/src/hooks/useMobileDetection.ts index 95b95bc..af7e907 100644 --- a/src/hooks/useMobileDetection.ts +++ b/src/hooks/useMobileDetection.ts @@ -3,9 +3,7 @@ import { useEffect, useState } from "react"; export function useMobileDetection(breakpoint: number = 768) { const [isMobile, setIsMobile] = useState(() => { // Only check on client-side, return false for SSR - if ( - typeof globalThis !== "undefined" && globalThis.innerWidth !== undefined - ) { + if (typeof globalThis !== "undefined" && globalThis.innerWidth !== undefined) { return globalThis.innerWidth < breakpoint; } return false; @@ -13,9 +11,7 @@ export function useMobileDetection(breakpoint: number = 768) { useEffect(() => { // Only run on client-side - if ( - typeof globalThis === "undefined" || globalThis.innerWidth === undefined - ) { + if (typeof globalThis === "undefined" || globalThis.innerWidth === undefined) { return; } diff --git a/src/hooks/usePrettier.ts b/src/hooks/usePrettier.ts index 32cad66..c6e2f54 100644 --- a/src/hooks/usePrettier.ts +++ b/src/hooks/usePrettier.ts @@ -10,9 +10,7 @@ interface UsePrettierProps { rulerWidth: number; } -export function usePrettier( - { code, setCode, showToast, rulerWidth }: UsePrettierProps, -) { +export function usePrettier({ code, setCode, showToast, rulerWidth }: UsePrettierProps) { const prettifyInProgressRef = useRef(false); const handlePrettify = useCallback(async () => { diff --git a/src/hooks/useRulerWidth.ts b/src/hooks/useRulerWidth.ts index 741f06c..5d72abf 100644 --- a/src/hooks/useRulerWidth.ts +++ b/src/hooks/useRulerWidth.ts @@ -1,15 +1,6 @@ import { useCallback, useEffect, useState } from "react"; -export const RULER_WIDTH_OPTIONS = [ - 60, - 80, - 100, - 120, - 140, - 160, - 180, - 200, -] as const; +export const RULER_WIDTH_OPTIONS = [60, 80, 100, 120, 140, 160, 180, 200] as const; export type RulerWidth = (typeof RULER_WIDTH_OPTIONS)[number]; function isValidRulerWidth(value: number): value is RulerWidth { diff --git a/src/hooks/useShowSemanticWarnings.ts b/src/hooks/useShowSemanticWarnings.ts index c8c8c9f..b329b4d 100644 --- a/src/hooks/useShowSemanticWarnings.ts +++ b/src/hooks/useShowSemanticWarnings.ts @@ -1,9 +1,7 @@ import { useCallback, useEffect, useState } from "react"; export function useShowSemanticWarnings(defaultValue: boolean = true) { - const [showSemanticWarnings, setShowSemanticWarningsInternal] = useState< - boolean - >(() => { + const [showSemanticWarnings, setShowSemanticWarningsInternal] = useState(() => { const stored = localStorage.getItem("showSemanticWarnings"); if (stored !== null) { return stored === "true"; diff --git a/src/hooks/useShowSymanticErrors.ts b/src/hooks/useShowSymanticErrors.ts index 5939eb8..23bb808 100644 --- a/src/hooks/useShowSymanticErrors.ts +++ b/src/hooks/useShowSymanticErrors.ts @@ -1,15 +1,13 @@ import { useCallback, useEffect, useState } from "react"; export function useShowSemanticErrors(defaultValue: boolean = true) { - const [showSemanticErrors, setShowSemanticErrorsInternal] = useState( - () => { - const stored = localStorage.getItem("showSemanticErrors"); - if (stored !== null) { - return stored === "true"; - } - return defaultValue; - }, - ); + const [showSemanticErrors, setShowSemanticErrorsInternal] = useState(() => { + const stored = localStorage.getItem("showSemanticErrors"); + if (stored !== null) { + return stored === "true"; + } + return defaultValue; + }); useEffect(() => { localStorage.setItem("showSemanticErrors", String(showSemanticErrors)); diff --git a/src/hooks/useShowSyntaxErrors.ts b/src/hooks/useShowSyntaxErrors.ts index ab7b433..7575234 100644 --- a/src/hooks/useShowSyntaxErrors.ts +++ b/src/hooks/useShowSyntaxErrors.ts @@ -1,15 +1,13 @@ import { useCallback, useEffect, useState } from "react"; export function useShowSyntaxErrors(defaultValue: boolean = true) { - const [showSyntaxErrors, setShowSyntaxErrorsInternal] = useState( - () => { - const stored = localStorage.getItem("showSyntaxErrors"); - if (stored !== null) { - return stored === "true"; - } - return defaultValue; - }, - ); + const [showSyntaxErrors, setShowSyntaxErrorsInternal] = useState(() => { + const stored = localStorage.getItem("showSyntaxErrors"); + if (stored !== null) { + return stored === "true"; + } + return defaultValue; + }); useEffect(() => { localStorage.setItem("showSyntaxErrors", String(showSyntaxErrors)); diff --git a/src/hooks/useToast.ts b/src/hooks/useToast.ts index b6be5f3..4f154b8 100644 --- a/src/hooks/useToast.ts +++ b/src/hooks/useToast.ts @@ -1,11 +1,7 @@ import { useCallback, useEffect, useState } from "react"; export type ToastType = "success" | "warning" | "error"; -export type ShowToastFunction = ( - message: string, - type?: ToastType, - duration?: number, -) => void; +export type ShowToastFunction = (message: string, type?: ToastType, duration?: number) => void; interface ToastState { message: string; diff --git a/src/sdl/sdlComplete.ts b/src/sdl/sdlComplete.ts index fc0798a..a40f9c2 100644 --- a/src/sdl/sdlComplete.ts +++ b/src/sdl/sdlComplete.ts @@ -1,14 +1,8 @@ -import { - CompletionContext, - type CompletionResult, -} from "@codemirror/autocomplete"; +import { CompletionContext, type CompletionResult } from "@codemirror/autocomplete"; import type { SyntaxNode } from "@lezer/common"; import { syntaxTree } from "@codemirror/language"; import { foldNodeProp } from "@codemirror/language"; -import { - getPotentialSyntacticTokens, - TokenTypeId, -} from "@mpeggroup/mpeg-sdl-parser"; +import { getPotentialSyntacticTokens, TokenTypeId } from "@mpeggroup/mpeg-sdl-parser"; function isCommentCompletion(lastNode: SyntaxNode): boolean { return lastNode.type.id === TokenTypeId.Comment; @@ -37,10 +31,7 @@ function isBlockScopeCompletion(lastNode: SyntaxNode): boolean { return parentNode.type.prop(foldNodeProp) !== undefined; } -function getCompletionResult( - completionOptions: string[], - from: number, -): CompletionResult { +function getCompletionResult(completionOptions: string[], from: number): CompletionResult { return { from, options: completionOptions.map((label) => ({ label })), @@ -73,11 +64,8 @@ function sdlComplete(context: CompletionContext): CompletionResult | null { const completions = getPotentialSyntacticTokens(lastNode.cursor()); - if (completions && (completions.length > 0)) { - return getCompletionResult( - completions, - lastTag ? lastNode.from + lastTag.index : context.pos, - ); + if (completions && completions.length > 0) { + return getCompletionResult(completions, lastTag ? lastNode.from + lastTag.index : context.pos); } return null; diff --git a/src/sdl/sdlLinter.ts b/src/sdl/sdlLinter.ts index 9359f1b..ac7861a 100644 --- a/src/sdl/sdlLinter.ts +++ b/src/sdl/sdlLinter.ts @@ -1,10 +1,5 @@ import { syntaxTree } from "@codemirror/language"; -import { - type Diagnostic, - forEachDiagnostic, - linter, - setDiagnostics, -} from "@codemirror/lint"; +import { type Diagnostic, forEachDiagnostic, linter, setDiagnostics } from "@codemirror/lint"; import { type Extension } from "@codemirror/state"; import { EditorView, ViewPlugin, type ViewUpdate } from "@codemirror/view"; import { diff --git a/tailwind.config.ts b/tailwind.config.ts index 12deb12..1a35171 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,13 +1,9 @@ const tailwindConfig: import("tailwindcss").Config & { daisyui?: any } = { - content: [ - "./src/**/*.{html,js,jsx,ts,tsx}", - ], + content: ["./src/**/*.{html,js,jsx,ts,tsx}"], theme: { extend: {}, }, - plugins: [ - require("daisyui"), - ], + plugins: [require("daisyui")], daisyui: { themes: ["light", "dark"], }, diff --git a/tests/Editor_test.tsx b/tests/Editor_test.tsx index 5af62d4..0b1f6a4 100644 --- a/tests/Editor_test.tsx +++ b/tests/Editor_test.tsx @@ -21,9 +21,7 @@ describe("Editor component", () => { const textboxes = getAllByRole("textbox"); // Check that at least one textbox contains the expected content - const found = textboxes.some( - (el) => el.textContent && el.textContent.includes("int a;"), - ); + const found = textboxes.some((el) => el.textContent && el.textContent.includes("int a;")); expect(found).toBe(true); }); }); diff --git a/tsconfig.json b/tsconfig.json index 238655f..67fad3e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,6 +22,10 @@ // Some stricter flags (disabled by default) "noUnusedLocals": false, "noUnusedParameters": false, - "noPropertyAccessFromIndexSignature": false - } + "noPropertyAccessFromIndexSignature": false, + + // Type definitions + "types": ["bun", "node"] + }, + "exclude": ["dist"] }