Skip to content

Commit ad18a8d

Browse files
committed
Break the MonacoEditor component for modules.
1 parent 4028387 commit ad18a8d

File tree

6 files changed

+799
-0
lines changed

6 files changed

+799
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Re-export the main Monaco Editor component
2+
export { default } from "./MonacoEditor";
3+
4+
// Export all the modular utilities for advanced usage
5+
export * from "./modules/monaco-config";
6+
export * from "./modules/monaco-vfs-setup";
7+
export * from "./modules/monaco-diagnostics";
8+
export * from "./modules/monaco-collaboration";
9+
export * from "./modules/monaco-utils";
10+
11+
// Export types
12+
export type { DiagnosticsCount } from "./modules/monaco-diagnostics";
13+
export type { CollaborationRefs } from "./modules/monaco-collaboration";
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import * as monaco from "monaco-editor";
2+
import { MonacoBinding } from "y-monaco";
3+
import type { FileNode } from "../../ProjectManagementPanel/file.types";
4+
import type { CollaborationUser } from "../../YJSCollaborationService";
5+
import { VFSBridge } from "../../../../lib/vfs/vfs-bridge";
6+
import { getLanguageFromFileName } from "./monaco-utils";
7+
8+
export interface CollaborationRefs {
9+
currentBindingRef: React.MutableRefObject<MonacoBinding | null>;
10+
currentFileRef: React.MutableRefObject<string | null>;
11+
contentUnsubscribeRef: React.MutableRefObject<(() => void) | null>;
12+
}
13+
14+
export const bindEditorToFile = (
15+
file: FileNode,
16+
editorRef: React.MutableRefObject<monaco.editor.IStandaloneCodeEditor | null>,
17+
collaborationRefs: CollaborationRefs,
18+
collaborationService: any,
19+
vfsBridge: VFSBridge | undefined,
20+
onFileContentChange?: (fileId: string, content: string) => void,
21+
debouncedUpdateDiagnostics?: (filePath: string, content?: string) => void
22+
) => {
23+
if (!editorRef.current || !vfsBridge) return;
24+
25+
const editor = editorRef.current;
26+
27+
// Destroy previous binding and content subscription
28+
if (collaborationRefs.currentBindingRef.current) {
29+
collaborationRefs.currentBindingRef.current.destroy();
30+
collaborationRefs.currentBindingRef.current = null;
31+
}
32+
if (collaborationRefs.contentUnsubscribeRef.current) {
33+
collaborationRefs.contentUnsubscribeRef.current();
34+
collaborationRefs.contentUnsubscribeRef.current = null;
35+
}
36+
37+
try {
38+
// Get the Y.Text for this file
39+
const fileYText = collaborationService.getFileText(file.id);
40+
41+
// Initialize file content if needed
42+
if (file.content) {
43+
collaborationService.initializeFileContent(file.id, file.content);
44+
}
45+
46+
// Get current content from Y.Text
47+
const currentYContent = fileYText.toString();
48+
49+
// Get the current model from the editor (created by @monaco-editor/react)
50+
const currentModel = editor.getModel();
51+
52+
if (currentModel) {
53+
// Update the existing model instead of creating a new one
54+
if (currentModel.getValue() !== currentYContent) {
55+
currentModel.setValue(currentYContent);
56+
}
57+
58+
// Set the correct language for this file
59+
const computedLanguage = getLanguageFromFileName(file.name);
60+
if (currentModel.getLanguageId() !== computedLanguage) {
61+
monaco.editor.setModelLanguage(currentModel, computedLanguage);
62+
}
63+
64+
// Create MonacoBinding with the existing model
65+
const awareness = collaborationService.getAwareness();
66+
if (awareness) {
67+
collaborationRefs.currentBindingRef.current = new MonacoBinding(
68+
fileYText,
69+
currentModel,
70+
new Set([editor]),
71+
awareness
72+
);
73+
}
74+
}
75+
76+
// Save current file ref
77+
collaborationRefs.currentFileRef.current = file.id;
78+
79+
// Subscribe to content changes (keep VFS up-to-date)
80+
collaborationRefs.contentUnsubscribeRef.current =
81+
collaborationService.onFileContentChange(file.id, (content: string) => {
82+
onFileContentChange?.(file.id, content);
83+
if (vfsBridge) {
84+
vfsBridge.updateFileContent(file.id, content);
85+
// Update diagnostics for the new content
86+
const filePath = vfsBridge.getPathById(file.id);
87+
if (filePath && debouncedUpdateDiagnostics) {
88+
debouncedUpdateDiagnostics(filePath, content);
89+
}
90+
}
91+
});
92+
93+
// Update diagnostics for the newly opened file
94+
const filePath = vfsBridge.getPathById(file.id);
95+
if (filePath && debouncedUpdateDiagnostics) {
96+
debouncedUpdateDiagnostics(filePath);
97+
}
98+
} catch (error) {
99+
console.error("Error binding editor to file:", error);
100+
}
101+
};
102+
103+
export const updateCollaborationUsers = (
104+
fileId: string,
105+
collaborationService: any,
106+
setFileUsers: React.Dispatch<React.SetStateAction<CollaborationUser[]>>
107+
) => {
108+
try {
109+
// Use the correct method name from the collaboration service
110+
const users = collaborationService.getUsersInFile(fileId);
111+
setFileUsers(users);
112+
console.log("Updated collaboration users for file:", fileId, users);
113+
} catch (error) {
114+
console.error("Error updating collaboration users:", error);
115+
setFileUsers([]);
116+
}
117+
};
118+
119+
export const setupCollaborationListeners = (
120+
fileId: string,
121+
collaborationService: any,
122+
setFileUsers: React.Dispatch<React.SetStateAction<CollaborationUser[]>>
123+
) => {
124+
const updateUsers = () => {
125+
updateCollaborationUsers(fileId, collaborationService, setFileUsers);
126+
};
127+
128+
// Set up user change listeners using the correct method
129+
const unsubscribeUsers = collaborationService.onUsersChange(
130+
(users: CollaborationUser[]) => {
131+
// Filter to only show users in the current file
132+
const fileUsers = users.filter(
133+
(user: any) => user.cursor?.fileId === fileId
134+
);
135+
setFileUsers(fileUsers);
136+
}
137+
);
138+
139+
// Initial user update
140+
updateUsers();
141+
142+
// Return cleanup function
143+
return () => {
144+
unsubscribeUsers();
145+
};
146+
};
147+
148+
export const cleanupCollaboration = (collaborationRefs: CollaborationRefs) => {
149+
try {
150+
// Clean up binding
151+
if (collaborationRefs.currentBindingRef.current) {
152+
collaborationRefs.currentBindingRef.current.destroy();
153+
collaborationRefs.currentBindingRef.current = null;
154+
}
155+
156+
// Clean up content listener
157+
if (collaborationRefs.contentUnsubscribeRef.current) {
158+
collaborationRefs.contentUnsubscribeRef.current();
159+
collaborationRefs.contentUnsubscribeRef.current = null;
160+
}
161+
162+
collaborationRefs.currentFileRef.current = null;
163+
console.log("Cleaned up collaboration resources");
164+
} catch (error) {
165+
console.error("Error cleaning up collaboration:", error);
166+
}
167+
};
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import loader from "@monaco-editor/loader";
2+
import * as monaco from "monaco-editor";
3+
4+
// Configure Monaco environment for Vite - only if not already configured
5+
export const setupMonacoEnvironment = () => {
6+
if (typeof window !== "undefined" && !(window as any).MonacoEnvironment) {
7+
(window as any).MonacoEnvironment = {
8+
getWorker(_: string, label: string) {
9+
switch (label) {
10+
case "json":
11+
return new Worker(
12+
new URL(
13+
"monaco-editor/esm/vs/language/json/json.worker.js",
14+
import.meta.url
15+
),
16+
{ type: "module" }
17+
);
18+
case "css":
19+
case "scss":
20+
case "less":
21+
return new Worker(
22+
new URL(
23+
"monaco-editor/esm/vs/language/css/css.worker.js",
24+
import.meta.url
25+
),
26+
{ type: "module" }
27+
);
28+
case "html":
29+
case "handlebars":
30+
case "razor":
31+
return new Worker(
32+
new URL(
33+
"monaco-editor/esm/vs/language/html/html.worker.js",
34+
import.meta.url
35+
),
36+
{ type: "module" }
37+
);
38+
case "typescript":
39+
case "javascript":
40+
return new Worker(
41+
new URL(
42+
"monaco-editor/esm/vs/language/typescript/ts.worker.js",
43+
import.meta.url
44+
),
45+
{ type: "module" }
46+
);
47+
default:
48+
return new Worker(
49+
new URL(
50+
"monaco-editor/esm/vs/editor/editor.worker.js",
51+
import.meta.url
52+
),
53+
{ type: "module" }
54+
);
55+
}
56+
},
57+
};
58+
}
59+
};
60+
61+
// Configure Monaco loader
62+
export const initializeMonaco = () => {
63+
loader.config({ monaco });
64+
65+
// Ensure Monaco is ready before using it
66+
return loader
67+
.init()
68+
.then((monacoInstance) => {
69+
console.log(
70+
"Monaco Editor loaded successfully:",
71+
monacoInstance.editor.getModels().length,
72+
"models"
73+
);
74+
return monacoInstance;
75+
})
76+
.catch((error) => {
77+
console.error("Failed to load Monaco Editor:", error);
78+
throw error;
79+
});
80+
};
81+
82+
// Enhanced beforeMount configuration
83+
export const configureMonacoBeforeMount = (monacoInstance: typeof monaco) => {
84+
console.log("Monaco about to mount - configuring global settings");
85+
86+
// Add extra library definitions for better IntelliSense
87+
monacoInstance.languages.typescript.typescriptDefaults.addExtraLib(
88+
`
89+
declare module "*.css" {
90+
const content: any;
91+
export default content;
92+
}
93+
94+
declare module "*.json" {
95+
const content: any;
96+
export default content;
97+
}
98+
99+
// React types for better JSX support
100+
declare namespace React {
101+
interface Component<P = {}, S = {}> {}
102+
interface ComponentClass<P = {}> {}
103+
interface FunctionComponent<P = {}> {}
104+
type FC<P = {}> = FunctionComponent<P>;
105+
type ReactElement = any;
106+
type ReactNode = any;
107+
}
108+
109+
declare const React: any;
110+
declare const process: { env: Record<string, string> };
111+
`,
112+
"ts:extra-libs.d.ts"
113+
);
114+
115+
// Configure JavaScript defaults too
116+
monacoInstance.languages.typescript.javascriptDefaults.addExtraLib(
117+
`
118+
// Common Node.js globals for JavaScript
119+
declare const process: { env: Record<string, string> };
120+
declare const require: (id: string) => any;
121+
declare const module: { exports: any };
122+
declare const exports: any;
123+
declare const console: Console;
124+
`,
125+
"js:extra-libs.d.ts"
126+
);
127+
128+
// Enhanced language features
129+
monacoInstance.languages.typescript.typescriptDefaults.setInlayHintsOptions({
130+
includeInlayParameterNameHints: "all",
131+
includeInlayParameterNameHintsWhenArgumentMatchesName: true,
132+
includeInlayFunctionParameterTypeHints: true,
133+
includeInlayVariableTypeHints: true,
134+
includeInlayPropertyDeclarationTypeHints: true,
135+
includeInlayFunctionLikeReturnTypeHints: true,
136+
includeInlayEnumMemberValueHints: true,
137+
});
138+
};
139+
140+
// Configure Monaco language services after mount
141+
export const configureMonacoLanguageServices = () => {
142+
console.log("Configuring Monaco language services...");
143+
144+
// Configure TypeScript/JavaScript language features
145+
monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true);
146+
monaco.languages.typescript.javascriptDefaults.setEagerModelSync(true);
147+
148+
// Enhanced diagnostics options
149+
const diagnosticsOptions = {
150+
noSemanticValidation: false,
151+
noSyntaxValidation: false,
152+
noSuggestionDiagnostics: false,
153+
// Disable module resolution errors - we'll handle these with VFS
154+
diagnosticCodesToIgnore: [2307, 2345, 2304], // "Cannot find module" errors
155+
};
156+
157+
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions(
158+
diagnosticsOptions
159+
);
160+
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions(
161+
diagnosticsOptions
162+
);
163+
164+
// Set compiler options for better IntelliSense
165+
const compilerOptions = {
166+
target: monaco.languages.typescript.ScriptTarget.ES2020,
167+
allowNonTsExtensions: true,
168+
moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
169+
module: monaco.languages.typescript.ModuleKind.ESNext,
170+
noEmit: true,
171+
esModuleInterop: true,
172+
allowJs: true,
173+
checkJs: false,
174+
lib: ["ES2020", "DOM"],
175+
allowSyntheticDefaultImports: true,
176+
resolveJsonModule: true,
177+
skipLibCheck: true,
178+
moduleDetection: "force",
179+
};
180+
181+
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
182+
...compilerOptions,
183+
jsx: monaco.languages.typescript.JsxEmit.React,
184+
reactNamespace: "React",
185+
typeRoots: ["node_modules/@types"],
186+
});
187+
188+
monaco.languages.typescript.javascriptDefaults.setCompilerOptions(
189+
compilerOptions
190+
);
191+
192+
console.log("Monaco language services configured successfully");
193+
};
194+
195+
// Initialize Monaco setup
196+
setupMonacoEnvironment();
197+
initializeMonaco();

0 commit comments

Comments
 (0)