Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 102 additions & 2 deletions apps/executeJS/src/widgets/code-editor/code-editor.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
import React, { useRef } from 'react';
import React, { useEffect, useRef } from 'react';

import { Editor, EditorProps } from '@monaco-editor/react';
import type { Options as PrettierOptions } from 'prettier';
import prettier from 'prettier/standalone';
import babel from 'prettier/plugins/babel';
import estree from 'prettier/plugins/estree';
import typescript from 'prettier/plugins/typescript';

import type { CodeEditorProps } from '../../shared/types';

const prettierOptions: PrettierOptions = {
semi: true,
trailingComma: 'es5',
singleQuote: true,
printWidth: 80,
tabWidth: 2,
useTabs: false,
};

export const CodeEditor: React.FC<CodeEditorProps> = ({
value,
onChange,
Expand All @@ -10,18 +26,91 @@ export const CodeEditor: React.FC<CodeEditorProps> = ({
theme = 'vs-dark',
}) => {
const editorRef = useRef<any>(null);
const disposablesRef = useRef<Array<{ dispose(): void }>>([]);

// Monaco Editor 설정
const handleEditorDidMount: EditorProps['onMount'] = (editor, monaco) => {
try {
editorRef.current = editor;

// Cmd+Enter 키바인딩 추가
// 이전 등록된 포맷터가 남아있는 경우, 먼저 해제
if (disposablesRef.current.length > 0) {
disposablesRef.current.forEach((disposable) => {
disposable.dispose();
});
disposablesRef.current = [];
}

// JavaScript 포맷터 등록
const jsDisposable =
monaco.languages.registerDocumentFormattingEditProvider('javascript', {
async provideDocumentFormattingEdits(model) {
const text = model.getValue();

try {
const formatted = await prettier.format(text, {
...prettierOptions,
parser: 'babel',
plugins: [babel, estree],
});

return [
{
range: model.getFullModelRange(),
text: formatted,
},
];
} catch (error) {
console.error('Prettier formatting error:', error);
return [];
}
},
});

// TypeScript 포맷터 등록
const tsDisposable =
monaco.languages.registerDocumentFormattingEditProvider('typescript', {
async provideDocumentFormattingEdits(model) {
const text = model.getValue();

try {
const formatted = await prettier.format(text, {
...prettierOptions,
parser: 'typescript',
plugins: [typescript, estree],
});

return [
{
range: model.getFullModelRange(),
text: formatted,
},
];
} catch (error) {
console.error('Prettier formatting error:', error);
return [];
}
},
});

// Disposable들을 ref에 저장
disposablesRef.current = [jsDisposable, tsDisposable];

// 단축키 바인딩
if (monaco && monaco.KeyMod && monaco.KeyCode) {
// Cmd+Enter 코드 실행
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => {
const currentValue = editor.getValue();
onExecute?.(currentValue);
});

// Cmd+Shift+F prettier 포맷 실행
editor.addCommand(
monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyF,
() => {
editor.getAction('editor.action.formatDocument')?.run();
}
);
}

// 에디터 포커스
Expand Down Expand Up @@ -73,6 +162,17 @@ export const CodeEditor: React.FC<CodeEditorProps> = ({
acceptSuggestionOnEnter: 'off' as const,
};

// Cleanup: unmount 시 포맷터 등록 해제
useEffect(() => {
return () => {
// 모든 disposable 해제
disposablesRef.current.forEach((disposable) => {
disposable.dispose();
});
disposablesRef.current = [];
};
}, []);

return (
<div className="h-full w-full min-h-0">
<Editor
Expand Down
5 changes: 1 addition & 4 deletions docs/docs/dev/_meta.json
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
[
"development",
"commands"
]
["development", "commands"]
6 changes: 1 addition & 5 deletions docs/docs/guide/_meta.json
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
[
"getting-started",
"npm-modules",
"live-demo"
]
["getting-started", "npm-modules", "live-demo"]