185 lines
5.2 KiB
TypeScript
185 lines
5.2 KiB
TypeScript
import type * as p from "@bokehjs/core/properties"
|
|
import {div} from "@bokehjs/core/dom"
|
|
import {HTMLBox, HTMLBoxView} from "./layout"
|
|
|
|
import type {Ace} from "ace-code"
|
|
import type * as AceCode from "ace-code"
|
|
declare const ace: typeof AceCode
|
|
|
|
declare type ModeList = {
|
|
getModeForPath(path: string): {mode: string}
|
|
}
|
|
|
|
function ID() {
|
|
// Math.random should be unique because of its seeding algorithm.
|
|
// Convert it to base 36 (numbers + letters), and grab the first 9 characters
|
|
// after the decimal.
|
|
const id = Math.random().toString(36).substr(2, 9)
|
|
return `_${id}`
|
|
}
|
|
|
|
export class AcePlotView extends HTMLBoxView {
|
|
declare model: AcePlot
|
|
|
|
protected _editor: Ace.Editor
|
|
protected _langTools: unknown
|
|
protected _modelist: ModeList
|
|
protected _container: HTMLDivElement
|
|
|
|
override connect_signals(): void {
|
|
super.connect_signals()
|
|
|
|
const {code, theme, language, filename, print_margin, annotations, readonly} = this.model.properties
|
|
this.on_change(code, () => this._update_code_from_model())
|
|
this.on_change(theme, () => this._update_theme())
|
|
this.on_change(language, () => this._update_language())
|
|
this.on_change(filename, () => this._update_filename())
|
|
this.on_change(print_margin, () => this._update_print_margin())
|
|
this.on_change(annotations, () => this._add_annotations())
|
|
this.on_change(readonly, () => {
|
|
this._editor.setReadOnly(this.model.readonly)
|
|
})
|
|
}
|
|
|
|
override render(): void {
|
|
super.render()
|
|
|
|
this._container = div({
|
|
id: ID(),
|
|
style: {
|
|
width: "100%",
|
|
height: "100%",
|
|
zIndex: "0",
|
|
},
|
|
})
|
|
this.shadow_el.append(this._container)
|
|
this._container.textContent = this.model.code
|
|
this._editor = ace.edit(this._container)
|
|
this._editor.renderer.attachToShadowRoot()
|
|
this._langTools = ace.require("ace/ext/language_tools")
|
|
this._modelist = ace.require("ace/ext/modelist")
|
|
this._editor.setOptions({
|
|
enableBasicAutocompletion: true,
|
|
enableSnippets: true,
|
|
fontFamily: "monospace", //hack for cursor position
|
|
})
|
|
this._update_theme()
|
|
this._update_filename()
|
|
this._update_language()
|
|
this._editor.setReadOnly(this.model.readonly)
|
|
this._editor.setShowPrintMargin(this.model.print_margin)
|
|
// if on keyup, update code from editor
|
|
if (this.model.on_keyup) {
|
|
this._editor.on("change", () => this._update_code_from_editor())
|
|
} else {
|
|
this._editor.on("blur", () => this._update_code_from_editor())
|
|
this._editor.commands.addCommand({
|
|
name: "updateCodeFromEditor",
|
|
bindKey: {win: "Ctrl-Enter", mac: "Command-Enter"},
|
|
exec: () => {
|
|
this._update_code_from_editor()
|
|
},
|
|
})
|
|
}
|
|
this._editor.on("change", () => this._update_code_input_from_editor())
|
|
}
|
|
|
|
_update_code_from_model(): void {
|
|
if (this._editor && this._editor.getValue() != this.model.code) {
|
|
this._editor.setValue(this.model.code)
|
|
}
|
|
}
|
|
|
|
_update_print_margin(): void {
|
|
this._editor.setShowPrintMargin(this.model.print_margin)
|
|
}
|
|
|
|
_update_code_from_editor(): void {
|
|
if (this._editor.getValue() != this.model.code) {
|
|
this.model.code = this._editor.getValue()
|
|
}
|
|
}
|
|
|
|
_update_code_input_from_editor(): void {
|
|
if (this._editor.getValue() != this.model.code_input) {
|
|
this.model.code_input = this._editor.getValue()
|
|
}
|
|
}
|
|
|
|
_update_theme(): void {
|
|
this._editor.setTheme(`ace/theme/${this.model.theme}`)
|
|
}
|
|
|
|
_update_filename(): void {
|
|
if (this.model.filename) {
|
|
const mode = this._modelist.getModeForPath(this.model.filename).mode
|
|
this.model.language = mode.slice(9)
|
|
}
|
|
}
|
|
|
|
_update_language(): void {
|
|
if (this.model.language != null) {
|
|
this._editor.session.setMode(`ace/mode/${this.model.language}`)
|
|
}
|
|
}
|
|
|
|
_add_annotations(): void {
|
|
this._editor.session.setAnnotations(this.model.annotations)
|
|
}
|
|
|
|
override after_layout(): void {
|
|
super.after_layout()
|
|
if (this._editor !== undefined) {
|
|
this._editor.resize()
|
|
}
|
|
}
|
|
}
|
|
|
|
export namespace AcePlot {
|
|
export type Attrs = p.AttrsOf<Props>
|
|
export type Props = HTMLBox.Props & {
|
|
code: p.Property<string>
|
|
code_input: p.Property<string>
|
|
on_keyup: p.Property<boolean>
|
|
language: p.Property<string>
|
|
filename: p.Property<string | null>
|
|
theme: p.Property<string>
|
|
annotations: p.Property<any[]>
|
|
print_margin: p.Property<boolean>
|
|
readonly: p.Property<boolean>
|
|
}
|
|
}
|
|
|
|
export interface AcePlot extends AcePlot.Attrs {}
|
|
|
|
export class AcePlot extends HTMLBox {
|
|
declare properties: AcePlot.Props
|
|
|
|
constructor(attrs?: Partial<AcePlot.Attrs>) {
|
|
super(attrs)
|
|
}
|
|
|
|
static override __module__ = "panel.models.ace"
|
|
|
|
static {
|
|
this.prototype.default_view = AcePlotView
|
|
|
|
this.define<AcePlot.Props>(({Any, List, Bool, Str, Nullable}) => ({
|
|
code: [ Str, "" ],
|
|
code_input: [ Str, "" ],
|
|
on_keyup: [ Bool, true ],
|
|
filename: [ Nullable(Str), null],
|
|
language: [ Str, "" ],
|
|
theme: [ Str, "chrome" ],
|
|
annotations: [ List(Any), [] ],
|
|
readonly: [ Bool, false ],
|
|
print_margin: [ Bool, false ],
|
|
}))
|
|
|
|
this.override<AcePlot.Props>({
|
|
height: 300,
|
|
width: 300,
|
|
})
|
|
}
|
|
}
|