Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ace code editor #359

Merged
merged 8 commits into from
Apr 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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