1// ============================================================ 2// Browser VM IDE - Monaco Editor 3// ============================================================ 4 5let editor = null; 6let currentFile = 'kernel.c'; 7 8// ============================================================ 9// Default files 10// ============================================================ 11 12const defaultFiles = { 13 'kernel.c': `// MyOS kernel 14// This will eventually run on your virtual x86 machine. 15 16void kernel_main(void) 17{ 18 // TODO: initialize the kernel 19 20 while (1) { 21 // Kernel loop 22 } 23} 24`, 25}; 26 27// ============================================================ 28// Initialize Monaco 29// ============================================================ 30 31export function initializeEditor() { 32 return new Promise((resolve, reject) => { 33 if (typeof require === 'undefined') { 34 reject(new Error('Monaco loader was not found.')); 35 36 return; 37 } 38 39 require.config({ 40 paths: { 41 vs: 'https://cdn.jsdelivr.net/npm/[email protected]/min/vs', 42 }, 43 }); 44 45 require(['vs/editor/editor.main'], () => { 46 try { 47 editor = monaco.editor.create(document.getElementById('editor'), { 48 value: defaultFiles[currentFile], 49 50 language: 'c', 51 52 theme: 'vs-dark', 53 54 automaticLayout: true, 55 56 minimap: { 57 enabled: true, 58 }, 59 60 fontSize: 14, 61 62 fontFamily: "'Cascadia Code', 'Fira Code', Consolas, monospace", 63 64 tabSize: 4, 65 66 insertSpaces: true, 67 68 wordWrap: 'off', 69 70 smoothScrolling: true, 71 72 cursorBlinking: 'smooth', 73 74 renderWhitespace: 'selection', 75 76 scrollBeyondLastLine: false, 77 78 padding: { 79 top: 10, 80 bottom: 10, 81 }, 82 }); 83 84 resolve(); 85 } catch (error) { 86 reject(error); 87 } 88 }); 89 }); 90} 91 92// ============================================================ 93// Get editor 94// ============================================================ 95 96export function getEditor() { 97 return editor; 98} 99 100// ============================================================ 101// Get current code 102// ============================================================ 103 104export function getCurrentCode() { 105 if (!editor) { 106 return ''; 107 } 108 109 return editor.getValue(); 110} 111 112// ============================================================ 113// Set current file 114// ============================================================ 115 116export function setCurrentFile(filename, contents = '') { 117 if (!editor) { 118 return; 119 } 120 121 currentFile = filename; 122 123 const language = getLanguageFromFilename(filename); 124 125 /* 126 * Monaco models let different files have 127 * different syntax highlighting. 128 */ 129 130 const uri = monaco.Uri.parse(`file:///${encodeURIComponent(filename)}`); 131 132 let model = monaco.editor.getModel(uri); 133 134 if (!model) { 135 model = monaco.editor.createModel(contents, language, uri); 136 } else { 137 model.setValue(contents); 138 } 139 140 editor.setModel(model); 141 142 editor.focus(); 143 144 updateTab(filename); 145} 146 147// ============================================================ 148// Get current filename 149// ============================================================ 150 151export function getCurrentFile() { 152 return currentFile; 153} 154 155// ============================================================ 156// Change language 157// ============================================================ 158 159function getLanguageFromFilename(filename) { 160 const lower = filename.toLowerCase(); 161 162 if (lower.endsWith('.c')) { 163 return 'c'; 164 } 165 166 if (lower.endsWith('.h')) { 167 return 'c'; 168 } 169 170 if (lower.endsWith('.cpp')) { 171 return 'cpp'; 172 } 173 174 if (lower.endsWith('.asm') || lower.endsWith('.s')) { 175 return 'plaintext'; 176 } 177 178 if (lower.endsWith('.ld')) { 179 return 'plaintext'; 180 } 181 182 if (lower.endsWith('.makefile')) { 183 return 'make'; 184 } 185 186 if (filename === 'Makefile' || filename === 'makefile') { 187 return 'make'; 188 } 189 190 if (lower.endsWith('.json')) { 191 return 'json'; 192 } 193 194 if (lower.endsWith('.js')) { 195 return 'javascript'; 196 } 197 198 if (lower.endsWith('.html')) { 199 return 'html'; 200 } 201 202 if (lower.endsWith('.css')) { 203 return 'css'; 204 } 205 206 if (lower.endsWith('.sh')) { 207 return 'shell'; 208 } 209 210 return 'plaintext'; 211} 212 213// ============================================================ 214// Update editor tab 215// ============================================================ 216 217function updateTab(filename) { 218 const tab = document.getElementById('currentFileTab'); 219 220 if (!tab) { 221 return; 222 } 223 224 tab.textContent = filename; 225} 226 227// ============================================================ 228// Set editor contents 229// ============================================================ 230 231export function setEditorContents(contents) { 232 if (!editor) { 233 return; 234 } 235 236 editor.setValue(contents); 237} 238 239// ============================================================ 240// Focus editor 241// ============================================================ 242 243export function focusEditor() { 244 if (editor) { 245 editor.focus(); 246 } 247} 248 249// ============================================================ 250// Resize editor 251// ============================================================ 252 253export function resizeEditor() { 254 if (editor) { 255 editor.layout(); 256 } 257} 258 259// ============================================================ 260// Add a file to Monaco 261// ============================================================ 262 263export function createEditorModel(filename, contents = '') { 264 if (typeof monaco === 'undefined') { 265 return null; 266 } 267 268 const uri = monaco.Uri.parse(`file:///${encodeURIComponent(filename)}`); 269 270 const existing = monaco.editor.getModel(uri); 271 272 if (existing) { 273 existing.setValue(contents); 274 275 return existing; 276 } 277 278 return monaco.editor.createModel( 279 contents, 280 getLanguageFromFilename(filename), 281 uri 282 ); 283} 284 285// ============================================================ 286// Delete a Monaco model 287// ============================================================ 288 289export function deleteEditorModel(filename) { 290 if (typeof monaco === 'undefined') { 291 return; 292 } 293 294 const uri = monaco.Uri.parse(`file:///${encodeURIComponent(filename)}`); 295 296 const model = monaco.editor.getModel(uri); 297 298 if (model) { 299 model.dispose(); 300 } 301}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.