Open miketromba opened 3 years ago
can I see a larger code snippet?
Hey, I should have included more context. I've abstracted a lot into a separate class "MonacoTextmateManager" for my project; I'll share it here.
The basic structure is: MonacoTextmateEditor.vue
is a vue component that imports the MonacoTextmateManager
class and initializes it to display the editor.
At the bottom of the MonacoTextmateManager.spawnEditor method is this line which solves the problem I'm referencing:
setTimeout(this.wire.bind(this), 1)
MonacoTextmateEditor.vue
- Vue component that displays the editor
<template>
<div id="MonacoEditor"></div>
</template>
<script>
import MonacoTextmateManager from '@/src/monaco/MonacoTextmateManager'
import oneDarkTextmateTheme from '@/src/monaco/one-dark-textmate-theme'
const ENABLED = false
export default {
async mounted(){
const monacoManager = new MonacoTextmateManager({
languages: this.$config.languages,
initialLanguage: 'javascript',
pathToOnigWasm: '/monaco/onigasm.wasm'
})
// Spawn the editor
if(ENABLED){
await monacoManager.spawnEditor({
element: document.getElementById('MonacoEditor'),
theme: oneDarkTextmateTheme
})
}
}
}
</script>
MonacoTextmateManager.js
- class that manages the Monaco instance
import * as monaco from 'monaco-editor'
import { loadWASM } from 'onigasm' // peer dependency of 'monaco-textmate'
import { Registry } from 'monaco-textmate' // peer dependency
import { wireTmGrammars } from 'monaco-editor-textmate'
import ResourceCache from './ResourceCache'
export default class MonacoTextmateManager {
constructor({
languages = [],
initialLanguage = 'javascript',
pathToOnigWasm = '/monaco/onigasm.wasm',
diffEditor = false
}){
this.languages = languages
this.pathToOnigWasm = pathToOnigWasm
this.diffEditor = diffEditor
this.editor = null
this.registry = null
this.element = null
this.language = this.languages.find(lang => lang.id == initialLanguage)
this.cache = new ResourceCache({
getters: {
grammarFile: async grammarFileLocation => {
return (await fetch(grammarFileLocation)).text()
},
codeSample: async codeSampleLocation => {
return (await fetch(codeSampleLocation)).text()
}
}
})
}
// Set theme data for the editor
setTheme(themeData){
// Monaco's built-in themes aren't powereful enough to handle TM tokens
// https://github.com/Nishkalkashyap/monaco-vscode-textmate-theme-converter#monaco-vscode-textmate-theme-converter
// Important, so that plaintext appears the correct color
if(this.diffEditor){
const baseTextColor = themeData.rules.find(rule => rule.token == 'source').foreground
themeData = {
...themeData,
rules: [
...themeData.rules,
{ token: '', foreground: baseTextColor }
]
}
}
// Set the theme
monaco.editor.defineTheme('vscode-theme-editor', themeData)
}
// Set the language of the editor
async setLanguage(languageId){
// Set this.language to new language object
this.language = this.languages.find(lang => lang.id == languageId)
// Inject the code sample for this language into the editor
this.editor.setValue(
await this.getCurrentCodeSample()
)
// Fully-respawn the editor
return this.spawnEditor({ element: this.element })
}
// Spawn the editor instance
async spawnEditor({ element, theme }){
// Kill old editor if exists
if(this.editor){ this.editor.dispose() }
// Remember element
this.element = element
// Needed for monaco
this.setWebWorkerPaths()
// Define the theme
if(theme) this.setTheme(theme)
// Load WASM file for running onig regex in browser
await this.loadWASMOnce()
// Build the registry
this.registry = new Registry({
getGrammarDefinition: (async (scopeName) => {
const grammarDefinition = await this.getCurrentGrammar()
return {
format: 'json',
content: grammarDefinition
}
}).bind(this)
})
// Build the editor instance
this.editor = monaco.editor[this.diffEditor? 'createDiffEditor' : 'create'](element, {
value: await this.getCurrentCodeSample(),
language: this.language.id,
theme: 'vscode-theme-editor',
automaticLayout: true,
fontLigatures: true,
links: true,
minimap: {
enabled: true
},
rulers: [60]
})
// Set models for diff editor preview
if(this.diffEditor){
this.editor.setModel({
original: monaco.editor.createModel("This is what your diff editor will look like.\nYou should adjust these colors to fit nicely with your theme\nAnother line of text\nAnd another...", "text/plain"),
modified: monaco.editor.createModel("just some text\n\nHello World\n\nSome more text", "text/plain")
})
}
// Apply wireTMGrammars to the various objects
// For some reason, we have to push this operation into the event stack for it to always work properly
setTimeout(this.wire.bind(this), 1)
}
async wire(){
return wireTmGrammars(monaco, this.registry, this.grammars, this.editor)
}
// Return a map of monaco "language id's" to TextMate scopeNames
get grammars(){
const grammars = new Map()
this.languages.forEach(lang => grammars.set(lang.id, lang.scope))
return grammars
}
// Returns the grammar file for the current language
async getCurrentGrammar(){
return this.cache.get('grammarFile', this.language.grammarFile)
}
// Returns the code sample for the current language
async getCurrentCodeSample(){
return this.cache.get('codeSample', this.language.codeSampleFile)
}
// Fetch and load the onig regex WASM file
async loadWASMOnce(){
if(window._ONIG_WASM_LOADED){ return }
await loadWASM(this.pathToOnigWasm)
window._ONIG_WASM_LOADED = true
}
setWebWorkerPaths(){
// Since packaging is done by you, you need
// to instruct the editor how you named the
// bundles that contain the web workers.
window.MonacoEnvironment = {
getWorkerUrl: function (moduleId, label) {
if (label === 'json') {
return '/monaco/workers/json.worker.js';
}
if (label === 'css' || label === 'scss' || label === 'less') {
return '/monaco/workers/css.worker.js';
}
if (label === 'html' || label === 'handlebars' || label === 'razor') {
return '/monaco/workers/html.worker.js';
}
if (label === 'typescript' || label === 'javascript') {
return '/monaco/workers/ts.worker.js';
}
return '/monaco/workers/editor.worker.js';
}
}
}
}
ResourceCache.js
- used above to acquire/cache resource like grammar files and code samples
export default class ResourceCache {
constructor({ getters }){
this.getters = getters
this.resources = {}
for(const getterId in this.getters){
this.resources[getterId] = {}
}
}
async get(getterId, resourceId){
// Hydrate cache
if(typeof this.resources[getterId][resourceId] == 'undefined'){
this.resources[getterId][resourceId] = this.getters[getterId](resourceId) // Set to a promise (this avoids concurrency bug)
}
// Return cached resource
return this.resources[getterId][resourceId]
}
}
Here is a GIF of this where I am reloading the page... sometimes the syntax highlighting works, sometimes not.
In order to get the syntax highlighting to work 100% of the time, I had to make a minor adjustment to the example:
I guess this pushes the
wireTmGrammars
behind whatever internal monaco operations it depends on. Note: I'm using monaco-editor version 0.21.2 with this fork of monaco-editor-textmate: https://github.com/NeekSandhu/monaco-editor-textmate/pull/16