From cab1d7d353111a70ab0f0a1d14f2c211a0e17aa4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 19 Oct 2024 22:56:45 +0300 Subject: [PATCH] client: Set up syntax highlight in read-only code (closes #504) --- .../widgets/type_widgets/read_only_code.js | 75 ++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/src/public/app/widgets/type_widgets/read_only_code.js b/src/public/app/widgets/type_widgets/read_only_code.js index 2bcaec195..040996ddb 100644 --- a/src/public/app/widgets/type_widgets/read_only_code.js +++ b/src/public/app/widgets/type_widgets/read_only_code.js @@ -1,4 +1,6 @@ import TypeWidget from "./type_widget.js"; +import libraryLoader from "../../services/library_loader.js"; +import options from "../../services/options.js"; const TPL = `
@@ -21,9 +23,38 @@ export default class ReadOnlyCodeTypeWidget extends TypeWidget { doRender() { this.$widget = $(TPL); - this.$content = this.$widget.find('.note-detail-readonly-code-content'); + this.$editor = this.$widget.find('.note-detail-readonly-code-content'); super.doRender(); + + this.initialized = this.initEditor(); + } + + async initEditor() { + await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR); + + // these conflict with backward/forward navigation shortcuts + delete CodeMirror.keyMap.default["Alt-Left"]; + delete CodeMirror.keyMap.default["Alt-Right"]; + + CodeMirror.modeURL = `${window.glob.assetPath}/node_modules/codemirror/mode/%N/%N.js`; + CodeMirror.modeInfo.find(mode=>mode.name === "JavaScript").mimes.push(...["application/javascript;env=frontend", "application/javascript;env=backend"]); + CodeMirror.modeInfo.find(mode=>mode.name === "SQLite").mimes=["text/x-sqlite", "text/x-sqlite;schema=trilium"]; + + this.codeEditor = CodeMirror(this.$editor[0], { + value: "", + viewportMargin: Infinity, + indentUnit: 4, + matchBrackets: true, + matchTags: {bothTags: true}, + highlightSelectionMatches: {showToken: false, annotateScrollbar: false}, + gutters: ["CodeMirror-lint-markers"], + lineNumbers: true, + // we line wrap partly also because without it horizontal scrollbar displays only when you scroll + // all the way to the bottom of the note. With line wrap, there's no horizontal scrollbar so no problem + lineWrapping: options.is('codeLineWrapEnabled'), + readOnly: true + }); } async doRefresh(note) { @@ -33,7 +64,47 @@ export default class ReadOnlyCodeTypeWidget extends TypeWidget { content = this.format(content); } - this.$content.text(content); + // CodeMirror breaks pretty badly on null, so even though it shouldn't happen (guarded by a consistency check) + // we provide fallback + this.codeEditor.setValue(content || ""); + this.codeEditor.clearHistory(); + + let info = CodeMirror.findModeByMIME(note.mime); + if (!info) { + // Switch back to plain text if CodeMirror does not have a mode for whatever MIME type we're editing. + // To avoid inheriting a mode from a previously open code note. + info = CodeMirror.findModeByMIME("text/plain"); + } + + this.codeEditor.setOption("mode", info.mime); + CodeMirror.autoLoadMode(this.codeEditor, info.mode); + this.show(); + } + + show() { + this.$widget.show(); + + if (this.codeEditor) { // show can be called before render + this.codeEditor.refresh(); + } + } + + focus() { + this.$editor.focus(); + this.codeEditor.focus(); + } + + scrollToEnd() { + this.codeEditor.setCursor(this.codeEditor.lineCount(), 0); + this.codeEditor.focus(); + } + + cleanup() { + if (this.codeEditor) { + this.spacedUpdate.allowUpdateWithoutChange(() => { + this.codeEditor.setValue(''); + }); + } } async executeWithContentElementEvent({resolve, ntxId}) {