Skip to content

Commit

Permalink
Code editor tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
Alan Fleming committed Dec 20, 2024
1 parent 7eeecff commit 55959d8
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 19 deletions.
86 changes: 79 additions & 7 deletions examples/code_editor.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
"\n",
"Code completion is provided for the Python mime types `text/x-python` and `text/x-ipython`. The default invocation for code completion is `Tab`, the same as is used in Jupyterlab.\n",
"\n",
"### Tooltips\n",
"### Tooltips (Inspect)\n",
"\n",
"Documentation `Tooltips` can be invoked with `Shift Tab`.\n",
"\n",
"### `namespace_id`\n",
"\n",
"An alternate namespaces can be specified that corresponds to a namespace registry maintained by the `App`. The default namespace \"\" also includes the IPython Shell `user_ns."
"An alternate namespaces can be specified that corresponds to a namespace registry maintained by the `App`. The default namespace \"\" also includes the IPython Shell `user_ns.\n",
"\n",
"## Example"
]
},
{
Expand All @@ -31,6 +33,8 @@
"metadata": {},
"outputs": [],
"source": [
"import asyncio\n",
"\n",
"import ipylab\n",
"from ipylab.code_editor import CodeEditorOptions"
]
Expand All @@ -45,10 +49,13 @@
"# Default syntax is Python\n",
"ce = ipylab.CodeEditor(\n",
" mime_type=\"text/x-python\",\n",
" description=\"Code editor\",\n",
" description=\"<b>Code editor</b>\",\n",
" tooltip=\"This is a code editor. Code completion is provided for Python\",\n",
" value=\"def test():\\n ipylab.app.notification.notify('CodeEditor evaluation')\\n\\n# Place the cursor in the CodeEditor and press `Shift Enter`\\ntest()\",\n",
" layout={\"height\": \"120px\", \"overflow\": \"hidden\"},\n",
" description_allow_html=True,\n",
")\n",
"asyncio.get_event_loop().call_later(0.5, ce.focus)\n",
"ce"
]
},
Expand Down Expand Up @@ -97,7 +104,12 @@
"metadata": {},
"outputs": [],
"source": [
"ce.editor_options = {\"lineNumbers\": False, \"codeFolding\": True}"
"ce.editor_options = {\n",
" \"autoClosingBrackets\": True,\n",
" \"matchBrackets\": True,\n",
" \"highlightTrailingWhitespace\": True,\n",
" \"highlightWhitespace\": True,\n",
"}"
]
},
{
Expand All @@ -106,14 +118,74 @@
"id": "8",
"metadata": {},
"outputs": [],
"source": [
"values = [\"short\", \"long \" * 20, \"multi line\\n\" * 10]\n",
"\n",
"\n",
"async def test():\n",
" import asyncio\n",
" import random\n",
"\n",
" for _ in range(20):\n",
" ce.value = random.choice(values) # noqa: S311\n",
" await asyncio.sleep(random.randint(10, 300) / 1e3) # noqa: S311"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9",
"metadata": {},
"outputs": [],
"source": [
"ce"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "10",
"metadata": {},
"outputs": [],
"source": [
"t = ce.to_task(test())"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "11",
"metadata": {},
"outputs": [],
"source": [
"t.cancel()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "12",
"metadata": {},
"outputs": [],
"source": [
"# Place the label above\n",
"ce.layout.flex_flow = \"column\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "13",
"metadata": {},
"outputs": [],
"source": [
"# Add the same editor to the shell.\n",
"ipylab.app.shell.add(ce)"
]
},
{
"cell_type": "markdown",
"id": "9",
"id": "14",
"metadata": {},
"source": [
"### Other mime_types\n",
Expand All @@ -124,7 +196,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "10",
"id": "15",
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -134,7 +206,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "11",
"id": "16",
"metadata": {},
"outputs": [],
"source": [
Expand Down
22 changes: 15 additions & 7 deletions ipylab/code_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@

import asyncio
import inspect
import typing
from asyncio import Task
from typing import TYPE_CHECKING, Any, NotRequired, TypedDict

from IPython.core import completer as IPC # noqa: N812
from IPython.utils.tokenutil import token_at_cursor
from ipywidgets import register, widget_serialization
from ipywidgets import Layout, register, widget_serialization
from ipywidgets.widgets.trait_types import InstanceDict
from ipywidgets.widgets.widget_description import DescriptionStyle
from ipywidgets.widgets.widget_string import _String
from traitlets import Callable, Dict, Instance, Int, Unicode, default, observe
from traitlets import Callable, Container, Dict, Instance, Int, Unicode, default, observe

import ipylab
from ipylab._compat.typing import override
Expand Down Expand Up @@ -185,7 +186,7 @@ class CodeEditor(Ipylab, _String):
The completer is invoked with `Tab` by default. Use completer_invoke_keys to change.
`evaluate` and `do_complete` can be overloaded as required.
`evaluate` and `load_value` can be overloaded as required.
Adjust `completer.disable_matchers` as required.
"""

Expand All @@ -197,6 +198,8 @@ class CodeEditor(Ipylab, _String):
editor_options: Instance[CodeEditorOptions] = Dict().tag(sync=True) # type: ignore
update_throttle_ms = Int(100, help="The limit at which changes are synchronised").tag(sync=True)
_sync = Int(0).tag(sync=True)

layout = InstanceDict(Layout, kw={"overflow": "hidden"}).tag(sync=True, **widget_serialization)
placeholder = None # Presently not available

value = Unicode()
Expand All @@ -211,7 +214,8 @@ class CodeEditor(Ipylab, _String):
)

namespace_id = Unicode("")
evaluate = Callable()
evaluate: Container[typing.Callable[[str], typing.Coroutine]] = Callable() # type: ignore
load_value: Container[typing.Callable[[str], None]] = Callable() # type: ignore

@default("key_bindings")
def _default_key_bindings(self):
Expand All @@ -227,6 +231,10 @@ def _default_key_bindings(self):
def _default_evaluate(self):
return self.completer.evaluate

@default("load_value")
def _default_load_value(self):
return lambda value: self.set_trait("value", value)

@observe("value")
def _observe_value(self, _):
if not self._setting_value and not self._update_task:
Expand All @@ -235,9 +243,9 @@ def _observe_value(self, _):
async def send_value():
try:
while True:
self._sync = self._sync + 1
value = self.value
await self.operation("setValue", {"sync": self._sync, "value": value})
await self.operation("setValue", {"value": value})
self._sync = self._sync + 1
await asyncio.sleep(self.update_throttle_ms / 1e3)
if self.value == value:
return
Expand All @@ -264,7 +272,7 @@ async def _do_operation_for_frontend(self, operation: str, payload: dict, buffer
if payload["sync"] == self._sync:
self._setting_value = True
try:
self.value = payload["value"]
self.load_value(payload["value"])
finally:
self._setting_value = False
return self.value == payload["value"]
Expand Down
4 changes: 1 addition & 3 deletions src/widgets/code_editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,6 @@ export class CodeEditorModel extends IpylabModel {
return this.editorModel.sharedModel.clearUndoHistory();
case 'setValue':
this._syncRequired = true;
// Clear first for better reliability with plugins.
this.editorModel.sharedModel.setSource('');
this.editorModel.sharedModel.setSource(payload.value);
this._syncRequired = false;
return true;
Expand Down Expand Up @@ -151,13 +149,13 @@ export class CodeEditorView extends StringView {
model: this.model.editorModel
});
this.editorWidget.id = this.editorWidget.id || UUID.uuid4();
this.editorWidget.addClass(this.className);
this.model.on('change:mimeType', this.updateCompleter, this);
this.model.on('change:completer_invoke_keys', this.updateCompleter, this);
this.model.on('change:editor_options', this.updateEditorOptions, this);
this.updateCompleter();
this.updateEditorOptions();
this.editorWidget.addClass('ipylab-CodeEditor');
this.editorWidget.addClass(this.className);
this.el.appendChild(this.editorWidget.node);

// Initialize the command registry with the bindings.
Expand Down
8 changes: 6 additions & 2 deletions style/widget.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,14 @@
.ipylab-MainArea {
height: 100%;
width: 100%;
display: block;
display: flex;
}

.ipylab-CodeEditor {
width: 100%;
height: 100%;
overflow-y: auto;
overflow: auto;

/* overflow-x: auto;
overflow-y: auto; */
}

0 comments on commit 55959d8

Please sign in to comment.