Skip to content

Commit

Permalink
Ace code editor (#359)
Browse files Browse the repository at this point in the history
  • Loading branch information
xavArtley authored and philippjfr committed Apr 3, 2019
1 parent 00e3227 commit 5770539
Show file tree
Hide file tree
Showing 7 changed files with 445 additions and 1 deletion.
175 changes: 175 additions & 0 deletions examples/reference/panes/Ace.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import panel as pn\n",
"pn.extension('ace')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The ``Ace`` pane allows to embed a code editor based on [Ace](https://ace.c9.io/).\n",
"Only a small subset of functionnalities are enabled :\n",
" - **syntax highligting** for several languages\n",
" - **themes**\n",
" - basic **completion** fonctionnalities `ctrl+space` (only static analysis of the code)\n",
" - **annotations**\n",
" - **readonly**\n",
"\n",
"#### Parameters:\n",
"\n",
"For layout and styling related parameters see the [customization user guide](../../user_guide/Customization.ipynb).\n",
"\n",
"* **``code``** (str): A string with code to set in the editor\n",
"* **``language``** (str): A string wich define the code syntax highlighter (default: 'python')\n",
"* **``theme``** (str): theme of the editor (defaut: 'chrome')\n",
"* **``annotations``** (list): list of annotations. An annotation is a dict with the following keys:\n",
" - `'row'`: row in the editor of the annotation\n",
" - `'column'`: column of the annotation\n",
" - `'text'`: 'text displayed when hover the annotation'\n",
" - `'type'`: define the type of annotation and the icon displayed {`warning` | `error`}\n",
"* **``readonly``** (boolean): A boolean to set the editor in read only mode\n",
"___"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To construct an `Ace` panel we must define it explicitly using `pn.pane.Ace`, we can add some text as initial code.\n",
"Code inserted in the editor is automatically reflected in the `code` parameter and can be linked to an other `panel`"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"py_code = \"\"\"import sys\n",
"\"\"\"\n",
"editor = pn.pane.Ace(py_code, sizing_mode='stretch_both', height=300)\n",
"html_pane = pn.pane.HTML(sizing_mode='stretch_both', height=300)\n",
"editor.link(html_pane,code=\"object\")\n",
"pn.Row(editor, html_pane)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"we can add some code in it"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"editor.code += \"\"\"import Math\n",
"\n",
"x = Math.cos(x)**2 + Math.cos(y)**2\n",
"\"\"\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can change language and theme of the editor"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"html_code = r\"\"\"<!DOCTYPE html>\n",
"<html>\n",
" <head>\n",
" <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n",
" <title>`substitute(Filename('', 'Page Title'), '^.', '\\u&', '')`</title>\n",
" </head>\n",
" <body>\n",
" <h1>Title1</h1>\n",
" <h2>Title2</h2>\n",
" <p>Paragraph</p>\n",
" </body>\n",
"</html>\n",
"\"\"\"\n",
"editor.language = \"html\"\n",
"editor.theme = \"monokai\"\n",
"editor.code = html_code\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can add some annotations to the editor"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"editor.annotations= [dict(row=1, column=0, text='an error', type='error'),\n",
" dict(row=2, column=0, text='a warning', type='warning')]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If we want just to display editor content but not edit it we can set the `readonly` property to `True`"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"editor.readonly = True"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
3 changes: 2 additions & 1 deletion panel/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ class panel_extension(_pyviz_extension):
'mathjax': 'panel.models.mathjax',
'plotly': 'panel.models.plotly',
'vega': 'panel.models.vega',
'vtk': 'panel.models.vtk'}
'vtk': 'panel.models.vtk',
'ace': 'panel.models.ace'}

def __call__(self, *args, **params):
# Abort if IPython not found
Expand Down
41 changes: 41 additions & 0 deletions panel/models/ace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""
Defines custom AcePlot bokeh model to render Ace editor.
"""
import os

from bokeh.core.properties import String, Override, Dict, Any, List, Bool
from bokeh.models import HTMLBox

from ..compiler import CUSTOM_MODELS

class AcePlot(HTMLBox):
"""
A Bokeh model that wraps around a Ace editor and renders it inside
a Bokeh plot.
"""

__javascript__ = ['https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.3/ace.js',
'https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.3/ext-language_tools.js']

__js_require__ = {'paths': {'ace': 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.3/ace'},
'shim': {'ace': 'ace'}}

__implementation__ = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'ace.ts')

code = String()

theme = String(default='chrome')

language = String(default='python')

annotations = List(Dict(String, Any), default=[])

readonly = Bool(default=False)

height = Override(default=300)

width = Override(default=300)



CUSTOM_MODELS['panel.models.ace.AcePlot'] = AcePlot
129 changes: 129 additions & 0 deletions panel/models/ace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import * as p from "core/properties"
import {HTMLBox, HTMLBoxView} from "models/layouts/html_box"
import { div } from 'core/dom';

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.
return '_' + Math.random().toString(36).substr(2, 9);
}


export class AcePlotView extends HTMLBoxView {
model: AcePlot
protected _ace: any
protected _editor: any
protected _langTools: any
protected _container: HTMLDivElement

initialize(): void {
super.initialize()
this._ace = (window as any).ace
this._container = div({
id: ID(),
style: {
width: "100%",
height: "100%"
}
})
}

connect_signals(): void {
super.connect_signals()
this.connect(this.model.properties.code.change, () => this._update_code_from_model())
this.connect(this.model.properties.theme.change, () => this._update_theme())
this.connect(this.model.properties.language.change, () => this._update_language())
this.connect(this.model.properties.annotations.change, () => this._add_annotations())
this.connect(this.model.properties.readonly.change, () => {
this._editor.setReadOnly(this.model.readonly)
})
}

render(): void {
super.render()
if (!(this._container === this.el.childNodes[0]))
this.el.appendChild(this._container)
this._container.textContent = this.model.code
this._editor = this._ace.edit(this._container.id)
this._editor.setTheme("ace/theme/" + `${this.model.theme}`)
this._editor.session.setMode("ace/mode/" + `${this.model.language}`)
this._editor.setReadOnly(this.model.readonly)
this._langTools = this._ace.require('ace/ext/language_tools')
this._editor.setOptions({
enableBasicAutocompletion: true,
enableSnippets: true,
fontFamily: "monospace", //hack for cursor position
});
this._editor.on('change', () => this._update_code_from_editor())
}

_update_code_from_model(): void {
if (this._editor && this._editor.getValue() != this.model.code)
this._editor.setValue(this.model.code)
}

_update_code_from_editor(): void {
if(this._editor.getValue() != this.model.code){
this.model.code = this._editor.getValue()
}
}

_update_theme(): void{
this._editor.setTheme("ace/theme/" + `${this.model.theme}`)
}

_update_language(): void{
this._editor.session.setMode("ace/mode/" + `${this.model.language}`)
}

_add_annotations(): void{
this._editor.session.setAnnotations(this.model.annotations)
}

after_layout(): void{
super.after_layout()
this._editor.resize()
}

}

export namespace AcePlot {
export type Attrs = p.AttrsOf<Props>
export type Props = HTMLBox.Props & {
code: p.Property<string>
language: p.Property<string>
theme: p.Property<string>
annotations: p.Property<any[]>
readonly: p.Property<boolean>
}
}

export interface AcePlot extends AcePlot.Attrs {}

export class AcePlot extends HTMLBox {
properties: AcePlot.Props

constructor(attrs?: Partial<AcePlot.Attrs>) {
super(attrs)
}

static initClass(): void {
this.prototype.type = "AcePlot"
this.prototype.default_view = AcePlotView

this.define<AcePlot.Props>({
code: [ p.String ],
language: [ p.String, 'python' ],
theme: [ p.String, 'chrome' ],
annotations: [ p.Array, [] ],
readonly: [ p.Boolean, false ]
})

this.override({
height: 300,
width: 300
})
}
}
AcePlot.initClass()
1 change: 1 addition & 0 deletions panel/pane/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from .plot import Bokeh, Matplotlib, RGGPlot, YT # noqa
from .vega import Vega # noqa
from .vtk import VTK # noqa
from .ace import Ace # noqa


def panel(obj, **kwargs):
Expand Down
Loading

0 comments on commit 5770539

Please sign in to comment.