Skip to content
Open
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
98 changes: 98 additions & 0 deletions example/language-features/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""Editor language features: completion + hover backed by a Python callback.

The ``completion`` and ``hover`` props on ``code.Editor`` each name a trame
``@trigger`` that receives ``(code, line, column)`` and returns results. Here
both are backed by jedi, giving live Python completion and docstring-on-hover
entirely in-process, with no client-side JavaScript.

The contract:

* completion trigger returns a list of items, each a dict with keys
``label`` (required), ``kind``, ``detail``, ``documentation``, ``insertText``.
* hover trigger returns a markdown string, a list of markdown strings, or
``{"contents": [...]}`` (or ``None`` for no hover).
* positions are passed as ``line`` (1-based) and ``column`` (0-based), matching
jedi's API directly.

Run with::

pip install trame trame-vuetify trame-code jedi
python app.py
"""

import jedi
from trame.app import get_server
from trame.ui.vuetify3 import SinglePageLayout
from trame.widgets import code
from trame.widgets import vuetify3 as vuetify

server = get_server()
state, ctrl = server.state, server.controller

INITIAL_CODE = '''import math


def circle_area(radius):
"""Return the area of a circle with the given radius."""
return math.pi * radius**2


# Type "math." below, or hover a name, to see completion and docstrings.
math.
'''


@server.trigger("py_complete")
def py_complete(code_text, line, column):
"""Completion items for (code, line, column). line 1-based, column 0-based."""
try:
completions = jedi.Script(code=code_text).complete(line, column)
except Exception:
return []
return [
{
"label": c.name,
"kind": c.type,
"detail": (c.description or "")[:80],
}
for c in completions[:200]
]


@server.trigger("py_hover")
def py_hover(code_text, line, column):
"""Hover markdown (signature + docstring) for the symbol at the cursor."""
try:
definitions = jedi.Script(code=code_text).help(line, column)
except Exception:
return None
if not definitions:
return None
definition = definitions[0]
contents = []
signatures = [s.to_string() for s in definition.get_signatures()]
if signatures:
contents.append("```python\n" + "\n".join(signatures) + "\n```")
doc = definition.docstring(raw=True) or ""
if doc:
contents.append(doc)
return {"contents": contents} if contents else None


state.trame__title = "Editor language features"

with SinglePageLayout(server) as layout:
layout.title.set_text("Editor language features (jedi)")
with layout.content:
with vuetify.VContainer(fluid=True, classes="fill-height pa-0"):
code.Editor(
value=INITIAL_CODE,
language="python",
theme="vs",
completion="py_complete",
hover="py_hover",
style="width: 100%; height: 100%;",
)

if __name__ == "__main__":
server.start()
4 changes: 4 additions & 0 deletions example/language-features/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
trame
trame-vuetify
trame-code
jedi
123 changes: 123 additions & 0 deletions example/live-state/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""Editor language features: completion from live in-process state.

This example shows the capability that distinguishes a callback-backed provider
from a language server: the suggestions come from a live Python object in the
running process, not from source text or type stubs. A language server cannot
offer these names, because they exist only at runtime.

Here the editor completes the keys of an in-memory ``DATASET`` when the cursor
is inside a ``dataset["..."]`` subscript, annotating each with the live value's
type and length. Swap ``DATASET`` for a loaded data file, a database schema, or
any live object and the same handler surfaces those names into the editor.

The contract (shared with the language-features example):

* completion trigger returns a list of items, each a dict with key ``label``
(required) and optional ``kind``, ``detail``, ``documentation``, ``insertText``.
* positions are passed as ``line`` (1-based) and ``column`` (0-based).

Run with::

pip install trame trame-vuetify trame-code
python app.py
"""

import re

from trame.app import get_server
from trame.ui.vuetify3 import SinglePageLayout
from trame.widgets import code
from trame.widgets import vuetify3 as vuetify

server = get_server()
state, ctrl = server.state, server.controller

# Stand-in for state that exists only at runtime (a loaded dataset, a live
# object graph, a fetched schema...). None of these names appear in the source.
DATASET = {
"pressure": [0.0] * 1000,
"density": [0.0] * 1000,
"temperature": [0.0] * 1000,
"velocity": [(0.0, 0.0, 0.0)] * 1000,
"time_steps": list(range(50)),
}

# Match an unclosed string subscript at the cursor: dataset["pre
_SUBSCRIPT_RE = re.compile(r"""\[\s*["']([^"']*)$""")


def _describe(value):
"""A short, live description of a value: type plus length when available."""
type_name = type(value).__name__
try:
return f"{type_name}, len {len(value)}"
except TypeError:
return type_name


@server.trigger("live_complete")
def live_complete(code_text, line, column):
"""Complete DATASET keys when the cursor is inside a string subscript."""
lines = code_text.split("\n")
if line < 1 or line > len(lines):
return []
prefix = lines[line - 1][:column]

match = _SUBSCRIPT_RE.search(prefix)
if not match:
return [] # not inside a key subscript -> no suggestions (defer to others)

stub = match.group(1)
return [
{"label": key, "kind": "field", "detail": _describe(value)}
for key, value in DATASET.items()
if key.startswith(stub)
]


INITIAL_CODE = """# Live-state completion demo
#
# 1. Put the cursor between the empty quotes on the last line: dataset[""]
# 2. Type a letter (p, d, t, or v). Suggestions appear automatically.
# 3. The keys are read live from the in-memory DATASET object
# (pressure, density, temperature, velocity, time_steps),
# each shown with its Python type and length.
#
# A language server cannot offer these: they exist only at runtime.

field = dataset[""]
"""

state.trame__title = "Live-state completion"

with SinglePageLayout(server) as layout:
layout.title.set_text("Live-state completion")
with layout.content:
with vuetify.VContainer(fluid=True, classes="fill-height pa-0"):
code.Editor(
v_model=("live_code", INITIAL_CODE),
language="python",
theme="vs",
completion="live_complete",
# open the list as soon as the key string is entered
completion_trigger_characters=("completion_triggers", ['"', "'", "["]),
# dict-key completion happens inside a string literal, where
# Monaco suppresses suggestions by default; enable them there.
options=(
"live_editor_options",
{
"quickSuggestions": {
"other": True,
"comments": False,
"strings": True,
},
# only our provider's keys, no document-word noise
"wordBasedSuggestions": False,
"minimap": {"enabled": False},
},
),
style="width: 100%; height: 100%;",
)

if __name__ == "__main__":
server.start()
3 changes: 3 additions & 0 deletions example/live-state/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
trame
trame-vuetify
trame-code
1 change: 1 addition & 0 deletions tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pytest
trame
20 changes: 20 additions & 0 deletions tests/test_language_features.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
def test_completion_and_hover_props_serialize():
"""The completion/hover props are accepted and render onto the element."""
from trame.app import get_server
from trame.ui.html import DivLayout
from trame.widgets import code

server = get_server("test_language_features")
with DivLayout(server):
editor = code.Editor(
language="python",
completion="my_complete",
hover="my_hover",
)

assert "completion" in editor._attr_names
assert "hover" in editor._attr_names

html = editor.html
assert 'completion="my_complete"' in html
assert 'hover="my_hover"' in html
9 changes: 9 additions & 0 deletions trame_code/widgets/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ class Editor(HtmlElement):
:param theme:
:param language:
:param textmate:
:param completion: name of a server ``@trigger`` that returns completion
items (list of {label, kind, detail, documentation, insertText}) for
``(code, line, column)``. line is 1-based, column 0-based.
:param hover: name of a server ``@trigger`` that returns hover content (a
markdown string, list of strings, or {contents: [...]}) for
``(code, line, column)``.

Events:

Expand All @@ -48,6 +54,9 @@ def __init__(self, **kwargs):
"theme",
"language",
"textmate",
"completion",
"hover",
("completion_trigger_characters", "completionTriggerCharacters"),
]
self._event_names += [
"input",
Expand Down
Loading
Loading