notebook.json
{
"bundleFiles": [
["codemirror-bundle.md", "codemirror-bundle.js"]
]
}
This sets up CodeMirror. It supports getting and setting the text, and sends the code-input
event when the text is edited. The filetype can be changed dynamically.
code-edit.js
const {tags} = window.CodeMirrorModules['@lezer/highlight'];
const {HighlightStyle} = window.CodeMirrorModules['@codemirror/language'];
const {EditorView} = window.CodeMirrorModules['@codemirror/view'];
const myTheme = EditorView.theme({
"&": {
color: "#FFFF33", // Bright yellow text
backgroundColor: "#FF6600", // Bright orange background
},
".cm-content": {
caretColor: "#FFFF33", // Bright yellow caret
},
"&.cm-focused .cm-cursor": {
borderLeftColor: "#FFFF33", // Bright yellow cursor
},
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, ::selection": {
backgroundColor: "#FF9933", // Light orange selection background
},
".cm-activeLine": {
backgroundColor: "#FF8533", // Slightly darker orange for active line
},
".cm-gutters": {
backgroundColor: "#FF6600", // Bright orange background for gutters
color: "#FFFF66", // Bright yellow for line numbers
border: "none",
},
".cm-tooltip": {
border: "1px solid #FFFF66",
backgroundColor: "#FF6600",
color: "#FFFF33",
},
".cm-tooltip.cm-tooltip-autocomplete": {
"& > ul > li[aria-selected]": {
backgroundColor: "#FF8533",
color: "#FFFF33",
},
},
}, {dark: true});
const myHighlightStyle = HighlightStyle.define([
{ tag: tags.keyword, color: "#FF4500" }, // Red-orange keywords
{ tag: tags.string, color: "#FFFF66" }, // Light yellow strings
{ tag: tags.comment, color: "#66FF66", fontStyle: "italic" }, // Green comments
{ tag: tags.function(tags.variableName), color: "#66CCFF" }, // Light blue functions
{ tag: tags.number, color: "#FF66CC" }, // Pink numbers
{ tag: tags.variableName, color: "#FFFF33" }, // Brighter yellow variable names
{ tag: tags.operator, color: "#FF66FF" }, // Magenta operators
{ tag: tags.meta, color: "#66FFFF" }, // Cyan meta
{ tag: tags.attributeName, color: "#FFD700" }, // Gold attribute names
{ tag: tags.className, color: "#FF6600" }, // Orange class names
{ tag: tags.typeName, color: "#66FFCC" }, // Mint green type names
{ tag: tags.tagName, color: "#FF4500" }, // Red-orange tag names
{ tag: tags.bracket, color: "#FFFF66" }, // Light yellow brackets
{ tag: tags.angleBracket, color: "#FFCC66" }, // Peach angle brackets
{ tag: tags.link, color: "#66CCFF" }, // Light blue links
{ tag: tags.heading, color: "#FFCC00", fontWeight: "bold" }, // Dark yellow headings
{ tag: tags.emphasis, color: "#FFFF33", fontStyle: "italic" }, // Brighter yellow italic
{ tag: tags.strong, color: "#FFFF33", fontWeight: "bold" }, // Brighter yellow bold
{ tag: tags.strikethrough, color: "#FFFF33", textDecoration: "line-through" }, // Brighter yellow strikethrough
{ tag: tags.attributeValue, color: "#FFD700" }, // Gold attribute values
{ tag: tags.definition(tags.typeName), color: "#FFD700" }, // Gold type definitions
{ tag: tags.definition(tags.className), color: "#FF6600" }, // Orange class definitions
]);
console.log(myHighlightStyle)
export class CodeEdit extends HTMLElement {
constructor() {
super()
this.attachShadow({mode: 'open'})
}
connectedCallback() {
this.shadowRoot.adoptedStyleSheets = [this.constructor.styleSheet]
this.initEditor()
}
static css = `
:host {
display: flex;
flex-direction: column;
align-items: stretch;
flex-grow: 1;
background-color: #fff;
height: 100%;
}
`
static get styleSheet() {
if (this._styleSheet === undefined) {
this._styleSheet = new CSSStyleSheet()
this._styleSheet.replaceSync(this.css)
}
return this._styleSheet
}
set value(value) {
if (this.view) {
this.view.dispatch({changes: {
from: 0,
to: this.view.state.doc.length,
insert: value
}})
} else {
this._value = value
}
}
get value() {
if (this.view) {
return this.view.state.doc.toString()
} else {
return this._value ?? ''
}
}
set fileType(value) {
this._fileType = value
if (this.view) {
const langPlugins = this.langPlugins
this.view.dispatch({
effects:
this.languageCompartment.reconfigure(langPlugins)
})
}
}
get fileType() {
return this._fileType
}
get langPlugins() {
const cmView = window.CodeMirrorModules['@codemirror/view']
const cmState = window.CodeMirrorModules['@codemirror/state']
const cmLanguage = window.CodeMirrorModules['@codemirror/language']
const cmJavaScript = window.CodeMirrorModules['@codemirror/lang-javascript']
const cmCss = window.CodeMirrorModules['@codemirror/lang-css']
const cmHtml = window.CodeMirrorModules['@codemirror/lang-html']
const cmJson = window.CodeMirrorModules['@codemirror/lang-json']
const cmMarkdown = window.CodeMirrorModules['@codemirror/lang-markdown']
const langPlugins = []
if (['js', 'javascript'].includes(this.fileType)) {
langPlugins.push(cmJavaScript.javascriptLanguage)
} else if (this.fileType === 'css') {
langPlugins.push(cmCss.cssLanguage)
} else if (this.fileType === 'html') {
langPlugins.push(cmHtml.htmlLanguage)
} else if (this.fileType === 'json') {
langPlugins.push(cmJson.jsonLanguage)
} else if (this.fileType === 'md') {
const codeLanguages = [
cmLanguage.LanguageDescription.of({
name: 'javascript',
alias: ['js'],
async load() {
return new cmLanguage.LanguageSupport(cmJavaScript.javascriptLanguage)
},
}),
cmLanguage.LanguageDescription.of({
name: 'css',
async load() {
return new cmLanguage.LanguageSupport(cmCss.cssLanguage)
},
}),
cmLanguage.LanguageDescription.of({
name: 'json',
async load() {
return new cmLanguage.LanguageSupport(cmJson.jsonLanguage)
},
}),
cmLanguage.LanguageDescription.of({
name: 'html',
async load() {
const javascript = new cmLanguage.LanguageSupport(cmJavaScript.javascriptLanguage)
const css = new cmLanguage.LanguageSupport(cmCss.cssLanguage)
return new cmLanguage.LanguageSupport(cmHtml.htmlLanguage, [css, javascript])
},
}),
]
const { language, support } = cmMarkdown.markdown({codeLanguages, addKeymap: false})
const markdownSupport = new cmLanguage.LanguageSupport(
language, [...support, cmState.Prec.high(cmView.keymap.of(cmMarkdown.markdownKeymap))]
)
langPlugins.push(
markdownSupport
)
}
return langPlugins
}
initEditor() {
const cmView = window.CodeMirrorModules['@codemirror/view']
const cmState = window.CodeMirrorModules['@codemirror/state']
const cmLanguage = window.CodeMirrorModules['@codemirror/language']
const cmCommands = window.CodeMirrorModules['@codemirror/commands']
const cmAutocomplete = window.CodeMirrorModules['@codemirror/autocomplete']
const cmSearch = window.CodeMirrorModules['@codemirror/search']
const cmLint = window.CodeMirrorModules['@codemirror/lint']
this.languageCompartment = new cmState.Compartment()
const langPlugins = this.langPlugins
const basicSetup = [
cmView.lineNumbers(),
cmView.highlightActiveLineGutter(),
cmView.highlightSpecialChars(),
cmCommands.history(),
cmLanguage.foldGutter(),
cmView.drawSelection(),
cmView.dropCursor(),
cmState.EditorState.allowMultipleSelections.of(true),
cmLanguage.indentOnInput(),
cmLanguage.bracketMatching(),
cmAutocomplete.closeBrackets(),
cmAutocomplete.autocompletion(),
cmView.rectangularSelection(),
cmView.crosshairCursor(),
cmView.highlightActiveLine(),
cmSearch.highlightSelectionMatches(),
cmLanguage.syntaxHighlighting(myHighlightStyle),
cmView.keymap.of([
...cmAutocomplete.closeBracketsKeymap,
...cmCommands.defaultKeymap,
...cmSearch.searchKeymap,
...cmCommands.historyKeymap,
...cmLanguage.foldKeymap,
...cmAutocomplete.completionKeymap,
...cmLint.lintKeymap,
]),
]
const viewTheme = cmView.EditorView.theme({
'&': {flexGrow: '1', height: '100%'},
'.cm-scroller': {overflow: 'auto'}
})
this.view = new cmView.EditorView({
doc: this._value ?? '',
extensions: [
...basicSetup,
this.languageCompartment.of(langPlugins),
myTheme,
cmView.EditorView.updateListener.of(e => {
if (e.docChanged) {
this.dispatchEvent(new CustomEvent(
'code-input', {bubbles: true, composed: true}
))
}
}),
],
root: this.shadowRoot,
})
this.shadowRoot.append(this.view.dom)
}
focus() {
this.view.focus()
}
}
app-view.js
export class AppView extends HTMLElement {
constructor() {
super()
this.attachShadow({mode: 'open'})
}
connectedCallback() {
const globalStyle = document.createElement('style')
globalStyle.textContent = `
body {
margin: 0;
padding: 0;
}
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
`
document.head.append(globalStyle)
const style = document.createElement('style')
style.textContent = `
`
this.shadowRoot.append(style)
const codeEdit = document.createElement('code-edit')
codeEdit.fileType = 'js'
codeEdit.value = `const x = 9`
this.shadowRoot.append(codeEdit)
}
}
app.js
import {CodeEdit} from '/code-edit.js'
import {AppView} from '/app-view.js'
customElements.define('code-edit', CodeEdit)
customElements.define('app-view', AppView)
async function setup() {
const appView = document.createElement('app-view')
document.body.append(appView)
}
await setup()