Switch to Monaco Editor#3857
Conversation
There was a problem hiding this comment.
Code Review
This pull request integrates the Monaco editor into the workspace as a feature-flagged alternative to the Ace editor. It adds the MonacoEditor component, a custom theme for visual parity, and necessary environment setup for workers. Review feedback suggests performance optimizations using useMemo and useCallback to prevent unnecessary re-renders, removing a hardcoded DOM ID to avoid conflicts, and expanding worker configuration to support advanced language features like IntelliSense.
| @@ -0,0 +1,73 @@ | |||
| import { Card } from '@blueprintjs/core'; | |||
| import MonacoReactEditor from '@monaco-editor/react'; | |||
| import { useCallback } from 'react'; | |||
| const handleChange = useCallback( | ||
| (value: string | undefined, event: unknown) => { | ||
| const newValue = value ?? ''; | ||
| props.handleEditorValueChange(props.editorTabIndex, newValue); | ||
| props.handleUpdateHasUnsavedChanges?.(true); | ||
| props.onChange?.(newValue, event); | ||
| }, | ||
| [props] | ||
| ); |
There was a problem hiding this comment.
The useCallback hook uses props as a dependency. Since props is a new object created on every render in the parent EditorContainer, this callback will be recreated on every render, defeating the purpose of memoization. It is better to destructure the specific functions and values needed from props and use them as dependencies.
| const handleChange = useCallback( | |
| (value: string | undefined, event: unknown) => { | |
| const newValue = value ?? ''; | |
| props.handleEditorValueChange(props.editorTabIndex, newValue); | |
| props.handleUpdateHasUnsavedChanges?.(true); | |
| props.onChange?.(newValue, event); | |
| }, | |
| [props] | |
| ); | |
| const { handleEditorValueChange, editorTabIndex, handleUpdateHasUnsavedChanges, onChange } = props; | |
| const handleChange = useCallback( | |
| (value: string | undefined, event: unknown) => { | |
| const newValue = value ?? ''; | |
| handleEditorValueChange(editorTabIndex, newValue); | |
| handleUpdateHasUnsavedChanges?.(true); | |
| onChange?.(newValue, event); | |
| }, | |
| [handleEditorValueChange, editorTabIndex, handleUpdateHasUnsavedChanges, onChange] | |
| ); |
|
|
||
| return ( | ||
| <Card className="Editor"> | ||
| <div className="row editor-react-ace" data-testid="Editor" id="editor-react-ace"> |
There was a problem hiding this comment.
Using a hardcoded id="editor-react-ace" can lead to duplicate IDs in the DOM if multiple editor tabs are rendered simultaneously. Consider removing the ID if it's not strictly required for external scripts or global CSS targeting.
| <div className="row editor-react-ace" data-testid="Editor" id="editor-react-ace"> | |
| <div className="row editor-react-ace" data-testid="Editor"> |
| options={{ | ||
| fontFamily: "'Inconsolata', 'Consolas', monospace", | ||
| fontSize: 17, | ||
| folding: false, | ||
| glyphMargin: false, | ||
| hover: { enabled: false }, | ||
| lineHeight: 17, | ||
| lineNumbersMinChars: 4, | ||
| minimap: { enabled: false }, | ||
| readOnly: props.sessionDetails?.readOnly ?? false, | ||
| renderLineHighlight: 'none', | ||
| scrollBeyondLastLine: false, | ||
| }} |
There was a problem hiding this comment.
The options object is created inline on every render. This can cause the MonacoReactEditor component to perform unnecessary internal updates. Consider memoizing this object using useMemo.
| options={{ | |
| fontFamily: "'Inconsolata', 'Consolas', monospace", | |
| fontSize: 17, | |
| folding: false, | |
| glyphMargin: false, | |
| hover: { enabled: false }, | |
| lineHeight: 17, | |
| lineNumbersMinChars: 4, | |
| minimap: { enabled: false }, | |
| readOnly: props.sessionDetails?.readOnly ?? false, | |
| renderLineHighlight: 'none', | |
| scrollBeyondLastLine: false, | |
| }} | |
| options={useMemo(() => ({ | |
| fontFamily: "'Inconsolata', 'Consolas', monospace", | |
| fontSize: 17, | |
| folding: false, | |
| glyphMargin: false, | |
| hover: { enabled: false }, | |
| lineHeight: 17, | |
| lineNumbersMinChars: 4, | |
| minimap: { enabled: false }, | |
| readOnly: props.sessionDetails?.readOnly ?? false, | |
| renderLineHighlight: 'none', | |
| scrollBeyondLastLine: false, | |
| }), [props.sessionDetails?.readOnly])} |
| (self as MonacoEnvironmentGlobal).MonacoEnvironment = { | ||
| getWorker: () => { | ||
| return new Worker(new URL('monaco-editor/esm/vs/editor/editor.worker.js', import.meta.url), { | ||
| type: 'module' | ||
| }); | ||
| } | ||
| }; |
There was a problem hiding this comment.
| }; | ||
|
|
||
| monaco.editor.defineTheme(SOURCE_MONACO_THEME, sourceTheme); | ||
| monaco.editor.setTheme(SOURCE_MONACO_THEME); |
There was a problem hiding this comment.
Setting the theme globally using monaco.editor.setTheme is redundant because the MonacoEditor component explicitly sets the theme via the theme prop. This global call should be removed to avoid unintended side effects and maintain consistency with the component-based configuration.
References
- Remove explicit function calls that are made redundant by a more general, encompassing mechanism.
Description
Type of change
How to test
Checklist