diff --git a/package-lock.json b/package-lock.json index 5e328a2..08fb699 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "moment": "^2.29.4", "monaco-editor-textmate": "^4.0.0", "monaco-textmate": "^3.0.1", + "monaco-vim": "^0.4.1", "next": "13.0.7", "onigasm": "^2.2.5", "react": "18.2.0", @@ -5613,6 +5614,14 @@ "onigasm": "^2.0.0" } }, + "node_modules/monaco-vim": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/monaco-vim/-/monaco-vim-0.4.1.tgz", + "integrity": "sha512-+cy0TE9xarjLIgUexqxIEbat3K1l7WbiFSLZKAO2kYl1qFRvkeWn4ro/C4c6dK0i9+WQKUC4Dhu/nyCbZfA37w==", + "peerDependencies": { + "monaco-editor": "*" + } + }, "node_modules/ms": { "version": "2.1.2", "license": "MIT" diff --git a/package.json b/package.json index ba5df6f..2024e62 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "moment": "^2.29.4", "monaco-editor-textmate": "^4.0.0", "monaco-textmate": "^3.0.1", + "monaco-vim": "^0.4.1", "next": "13.0.7", "onigasm": "^2.2.5", "react": "18.2.0", diff --git a/src/components/workspace/Editor/Editor.module.scss b/src/components/workspace/Editor/Editor.module.scss index a36a6ed..c8f1aad 100644 --- a/src/components/workspace/Editor/Editor.module.scss +++ b/src/components/workspace/Editor/Editor.module.scss @@ -11,9 +11,12 @@ z-index: 1; display: flex; gap: 1rem; - justify-content: flex-end; + justify-content: space-between; font-size: 0.8rem; padding: 0.2rem 1rem; + .vimStatuBar { + font-family: monospace; + } } .editor { diff --git a/src/components/workspace/Editor/Editor.tsx b/src/components/workspace/Editor/Editor.tsx index ab8d674..0b2a03c 100644 --- a/src/components/workspace/Editor/Editor.tsx +++ b/src/components/workspace/Editor/Editor.tsx @@ -23,13 +23,14 @@ const Editor: FC = ({ file, projectId, className = '' }) => { const { updateFileContent, getFileContent, updateOpenFile } = useWorkspaceActions(); - const { isFormatOnSave } = useSettingAction(); + const { isFormatOnSave, getSettingStateByKey } = useSettingAction(); const [isLoaded, setIsLoaded] = useState(false); const [isEditorInitialized, setIsEditorInitialized] = useState(false); const [cursorPosition, setCursorPosition] = useState<[number, number]>([ 0, 0, ]); + const editorMode = getSettingStateByKey('editorMode'); // Using this extra state to trigger save file from js event const [saveFileCounter, setSaveFileCounter] = useState(1); @@ -42,6 +43,10 @@ const Editor: FC = ({ file, projectId, className = '' }) => { const editorRef = useRef(null); const monacoRef = useRef(null); + const vimStatusBarRef = useRef(null); + const vimModeRef = useRef<{ + dispose: () => void; + } | null>(null); // eslint-disable-next-line prefer-const let lspWebSocket: ReconnectingWebSocket | null; @@ -157,6 +162,21 @@ const Editor: FC = ({ file, projectId, className = '' }) => { updateOpenFile(file.id, { isDirty: true }, projectId); }; + useEffect(() => { + (async () => { + if (!editorRef.current) return; + if (editorMode === 'vim') { + const { initVimMode } = await import('monaco-vim'); + vimModeRef.current = initVimMode( + editorRef.current, + vimStatusBarRef.current as unknown as HTMLElement, + ); + } else { + vimModeRef.current?.dispose(); + } + })().catch(() => {}); + }, [editorRef, editorMode]); + useEffect(() => { if (!isEditorInitialized) { return; @@ -180,6 +200,7 @@ const Editor: FC = ({ file, projectId, className = '' }) => { lspWebSocket?.close(); }; return () => { + vimModeRef.current?.dispose(); lspWebSocket?.close(); }; }, []); @@ -191,6 +212,9 @@ const Editor: FC = ({ file, projectId, className = '' }) => { return (
+
+ +
Ln {cursorPosition[0]}, Col {cursorPosition[1]} diff --git a/src/components/workspace/WorkspaceSidebar/WorkspaceSidebar.tsx b/src/components/workspace/WorkspaceSidebar/WorkspaceSidebar.tsx index f5c5485..ced9463 100644 --- a/src/components/workspace/WorkspaceSidebar/WorkspaceSidebar.tsx +++ b/src/components/workspace/WorkspaceSidebar/WorkspaceSidebar.tsx @@ -4,7 +4,7 @@ import { AppData } from '@/constant/AppData'; import { useSettingAction } from '@/hooks/setting.hooks'; import { useWorkspaceActions } from '@/hooks/workspace.hooks'; import { Project } from '@/interfaces/workspace.interface'; -import { Form, Input, Popover, Switch } from 'antd'; +import { Form, Input, Popover, Select, Switch } from 'antd'; import Link from 'next/link'; import { FC } from 'react'; import s from './WorkspaceSidebar.module.scss'; @@ -38,9 +38,12 @@ const WorkspaceSidebar: FC = ({ getTonAmountForInteraction, isAutoBuildAndDeployEnabled, toggleAutoBuildAndDeploy, + getSettingStateByKey, + updateEditorMode, } = useSettingAction(); const hasEditAccess = isProjectEditable(); + const editorMode = getSettingStateByKey('editorMode') ?? 'default'; const menuItems: MenuItem[] = [ { @@ -112,6 +115,20 @@ const WorkspaceSidebar: FC = ({

+
+ + + +
) { @@ -71,4 +72,10 @@ export function useSettingAction() { autoBuildAndDeploy: active, }); } + + function updateEditorMode(mode: 'default' | 'vim') { + updateStateByKey({ + editorMode: mode, + }); + } } diff --git a/src/interfaces/setting.interface.ts b/src/interfaces/setting.interface.ts index 684229b..adec0d0 100644 --- a/src/interfaces/setting.interface.ts +++ b/src/interfaces/setting.interface.ts @@ -3,4 +3,5 @@ export interface SettingInterface { formatOnSave?: boolean; autoBuildAndDeploy?: boolean; tonAmountForInteraction?: string; + editorMode: 'default' | 'vim'; } diff --git a/src/state/setting.state.ts b/src/state/setting.state.ts index 90f79ee..285dd3a 100644 --- a/src/state/setting.state.ts +++ b/src/state/setting.state.ts @@ -11,6 +11,7 @@ export const settingState = atom({ formatOnSave: false, autoBuildAndDeploy: true, tonAmountForInteraction: '0.05', + editorMode: 'default', }, effects_UNSTABLE: [persistAtom], }); diff --git a/types/monaco-vim.d.ts b/types/monaco-vim.d.ts new file mode 100644 index 0000000..c07144d --- /dev/null +++ b/types/monaco-vim.d.ts @@ -0,0 +1,7 @@ +declare module 'monaco-vim' { + export function initVimMode( + editor: monaco.editor.IStandaloneCodeEditor, + statusBar: HTMLElement, + options?: { keyHandler?: Record; override?: boolean }, + ): { dispose: () => void }; +}