Published
Edited
May 6, 2020
Insert cell
Insert cell
Insert cell
html`<div id="editor">${cmView.dom}</div>`
Insert cell
Insert cell
cmTheme = CodeMirrorImports.view.EditorView.theme({
scroller: {
'font-family': 'sans-serif',
// 'font-family': 'Fira Mono, monospace'
},
wrap: {
'border': '1px solid silver'
},
})
Insert cell
commonmarkHighlighter = CodeMirrorImports.highlight.highlighter({
'strong': {
'font-weight': 700,
},
'emphasis': {
'font-style': 'italic',
},
'monospace': {
'font-family': '"Fira Mono", monospace',
},
typeName: {
'font-size': '2em'
},
})
Insert cell
commonmarkHighlightStyles = CodeMirrorImports.highlight.styleTags({
'strong_emphasis': '+strong string',
'emphasis': '+emphasis regexp',
'atx_heading_marker': 'strong typeName',
'block_quote': 'emphasis meta',
'code_span': 'monospace keyword',
'heading_content': '+typeName',
'setext_heading_underline': 'meta',
})
Insert cell
Insert cell
Insert cell
{
const myDoc = [
'# h1',
'> hello **world**!',
'> goodbye **world**!',
]
const tsTree = tsParser.parse((index, position) => {
const {row, column} = position
const line = myDoc[row]
if (line != null) {
return (line + '\n').slice(column)
}
})
const st = new SitterTree(tsTree)
const lezerTree = st.build()
return {
st,
lezerTree,
repr: lezerTree.toString().split('(').map((x, i, a) => x + (i != a.length - 1 ? '(' : '')),
}
}
Insert cell
Insert cell
myTSSyntax = new TreeSitterSyntax(tsParser)
Insert cell
class TreeSitterSyntax {
constructor(parser) {
this.parser = parser
this.docNodeType = this._getDocNodeType()
const setSyntax = CodeMirrorImports.state.StateEffect.define()
this.field = CodeMirrorImports.state.StateField.define({
create(state) { return TreeSitterSyntaxState.init(parser, state.doc) },
update(value, tr) { return value.apply(tr, parser, setSyntax) }
})
this.extension = [
// the one to provide highlighting
CodeMirrorImports.state.EditorState.syntax.of(this),
this.field,
// CodeMirrorImports.view.ViewPlugin.define(view => new HighlightWorker(view, this, setSyntax)),
]
}
_getDocNodeType() {
const tree = this.parser.parse('')
const st = new SitterTree(tree)
return st.build().type
}
// syntax interface methods
getTree(state) { return state.field(this.field).tree }
parsePos(state) { return state.field(this.field).upto }
ensureTree(state, upto, timeout) {
// FIXME: should not always re-parse
const field = state.field(this.field)
return field.startParse(this.parser, state.doc)
}
docNodeTypeAt() { return this.docNodeType }
}
Insert cell
class TreeSitterSyntaxState {
constructor(tree, upto) {
// it seems that tree-sitter cannot do step-by-step/async parsing :(
// upto is always 0 (not started) or doc.length (finished)
this.tree = tree
this.upto = upto

this.parsingTask = null
this.updatedTree = tree
}

static _parseOrFail(parser, doc, parseArgs, time = 0, _upto = 5e6) {
let result = null
const oldParseTimeout = parser.getTimeoutMicros()
parser.setTimeoutMicros(time)
try {
result = parser.parse((index, position) => {
const {row, column} = position
const line = doc.text[row]
if (line != null) {
// BUG of tree-sitter: should add a newline for tree-sitter to proceed?
return (line + (row != doc.lines - 1 ? '\n' : '')).slice(column)
}
}, ...parseArgs);
} catch (e) {
if (e.message == 'Parsing failed') {
console.warn('TreeSitter parsing failed, usually due to timeout...')
} else {
console.error('Exception from tree-sitter! There may be bugs in tree-sitter or language definition.', e)
}
} finally {
parser.setTimeoutMicros(oldParseTimeout)
}
return result
}
static init(parser, doc) {
const tsTree = TreeSitterSyntaxState._parseOrFail(parser, doc, [])
const tree = tsTree ? new SitterTree(tsTree).build() : _LezerTree.Tree.empty
return new TreeSitterSyntaxState(tree, tsTree ? doc.length : 0)
}
apply(tr, parser, effect) {
for (let e of tr.effects) if (e.is(effect)) return e.value
if (!tr.docChanged) return this
// FIXME: always start from staratch :)
return TreeSitterSyntaxState.init(parser, tr.doc)
}
startParse(parser, doc) {
this.parsingTask = TreeSitterSyntaxState._parseOrFail(parser, doc, [this.updatedTree])
}
}
Insert cell
// modified from https://discuss.codemirror.net/t/mapping-tree-sitter-trees-to-lezer-trees/2362/4

class SitterTree {
constructor(tree) {
this.tree = tree;
this.node_types = {};
}

node_group() {
const group = new _LezerTree.NodeGroup(Object.values(this.node_types).sort((a, b) => a.id - b.id));
return group.extend(commonmarkHighlightStyles);
}

build_node(node) {
const buffer = _lodashFlatten(node.children.map((child) => this.build_node(child)));
if (node.type == 'text' || node.type == 'paragraph') {
// XXX: skip when highlighting, but significant when rendering
return buffer
}
if (!this.node_types.hasOwnProperty(node.type)) {
this.node_types[node.type] = new _LezerTree.NodeType(node.type, {}, Object.keys(this.node_types).length);
}
const node_type = this.node_types[node.type];
return buffer.concat([node_type.id, node.startIndex, node.endIndex, buffer.length + 4]);
}

build() {
const rootNode = this.tree.rootNode
this.node_types[rootNode.type] = new _LezerTree.NodeType(rootNode.type, {}, 0)
const buffer = this.build_node(this.tree.rootNode);
const group = this.node_group();
return _LezerTree.Tree.build({buffer, group}).firstChild;
}
}
Insert cell
/***** WIP *****/
class HighlightWorker {
constructor(view, syntax, setSyntax) {
this.view = view
this.syntax = syntax
this.setSyntax = setSyntax
this.working = -1
this.work = this.work.bind(this)
this._scheduleWork()
}
work() {
this.working = -1
let {state} = this.view, field = state.field(this.syntax.field)
// parse routine...
console.warn('work')
this.view.dispatch(state.t().effect(this.setSyntax.of(new TreeSitterSyntaxState())))
}
_scheduleWork() {
setTimeout(this.work, 100)
}
}
Insert cell
Insert cell
cmView = {
const { EditorState } = CodeMirrorImports.state
const { EditorView } = CodeMirrorImports.view
const { keymap } = CodeMirrorImports.keymap
const { history, redo, redoSelection, undo, undoSelection } = CodeMirrorImports.history
const { foldCode, unfoldCode, foldGutter } = CodeMirrorImports.fold
const { lineNumbers } = CodeMirrorImports.gutter
const { baseKeymap, indentSelection } = CodeMirrorImports.commands
const { bracketMatching } = CodeMirrorImports.matchbrackets
const { closeBrackets } = CodeMirrorImports.closebrackets
const { specialChars } = CodeMirrorImports['special-chars']
const { multipleSelections } = CodeMirrorImports['multiple-selections']
const { search, defaultSearchKeymap } = CodeMirrorImports['search']
const { autocomplete, startCompletion } = CodeMirrorImports['autocomplete']
const { defaultHighlighter } = CodeMirrorImports.highlight
const isMac = false
const state = EditorState.create({
doc: initialContent,
extensions: [
lineNumbers(),
specialChars(),
history(),
foldGutter(),
multipleSelections(),
commonmarkHighlighter,
myTSSyntax.extension,
search({keymap: defaultSearchKeymap}),
defaultHighlighter,
bracketMatching(),
closeBrackets,
autocomplete(),
cmTheme,
keymap({
"Mod-z": undo,
"Mod-Shift-z": redo,
"Mod-u": view => undoSelection(view) || true,
[isMac ? "Mod-Shift-u" : "Alt-u"]: redoSelection,
"Ctrl-y": isMac ? undefined : redo,
"Shift-Tab": indentSelection,
"Mod-Alt-[": foldCode,
"Mod-Alt-]": unfoldCode,
"Mod-Space": startCompletion,
}),
keymap(baseKeymap),
]})

const view = new EditorView({state})
return view
}
Insert cell
Insert cell
Insert cell
Insert cell
TreeSitter = {
// NOTE: this binding is permanent after a require to tree-sitter!
if (window.Module == null) {
window.Module = {
locateFile: path => {
if (path === 'tree-sitter.wasm') {
return SRC_TS_WASM
}
return path
}
}
}
const TreeSitter = await require(SRC_TS_LOADER)
await TreeSitter.init()
return TreeSitter
}
Insert cell
tsParser = {
const parser = new TreeSitter()
parser.setLanguage(langMarkdown)
return parser
}
Insert cell
langMarkdown = TreeSitter.Language.load(SRC_TS_MARKDOWN_WASM)
Insert cell
_streamSyntax = importFromCM('stream-syntax')
Insert cell
Insert cell
Insert cell
_LezerTree = import('lezer-tree')
Insert cell
_syntax = importFromCM('syntax')
Insert cell
html`<style>
#editor {
padding: 1em;
}
</style>`
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more