feat: add completion and hover language-feature providers#5
Merged
Conversation
6fea1ee to
171adb0
Compare
Add `completion` and `hover` props to the Editor widget. Each names a trame @trigger that receives (code, line, column) and returns results; the component registers Monaco completion/hover providers that call the trigger over the existing websocket and map the normalized results onto Monaco. The consumer writes only Python: no client-side JavaScript and no access to the Monaco instance are required. - completion items: {label, kind, detail, documentation, insertText} - hover: a markdown string, a list of strings, or {contents: [...]} - positions: line is 1-based, column is 0-based - requests honor Monaco's CancellationToken; providers are disposed on unmount and re-registered when the language changes - adds a jedi-backed Python example under example/language-features
171adb0 to
a6ece94
Compare
jourdain
reviewed
Jun 15, 2026
jourdain
reviewed
Jun 15, 2026
jourdain
reviewed
Jun 15, 2026
jourdain
reviewed
Jun 15, 2026
| @@ -0,0 +1,98 @@ | |||
| """Editor language features: completion + hover backed by a Python callback. | |||
Collaborator
There was a problem hiding this comment.
This file could looks like
"""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 TrameApp
from trame.ui.vuetify3 import SinglePageLayout
from trame.widgets import code
from trame.widgets import vuetify3 as v3
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.
'''
class PyCodeEditor(TrameApp):
def __init__(self, server=None):
super().__init__(server)
self._build_ui()
def _build_ui(self):
self.state.trame__title = "PyEditor"
with SinglePageLayout(server) as self.ui:
self.ui.title.set_text("Editor language features (jedi)")
with self.ui.content:
with v3.VContainer(fluid=True, classes="fill-height pa-0"):
code.Editor(
value=INITIAL_CODE,
language="python",
theme="vs",
style="width: 100%; height: 100%;",
completion=self.on_completion,
hover=self.on_hover,
)
def on_completion(self, 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]
]
def on_hover(self, 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
if __name__ == "__main__":
app = PyCodeEditor()
app.server.start()
jourdain
reviewed
Jun 15, 2026
| :param theme: | ||
| :param language: | ||
| :param textmate: | ||
| :param completion: name of a server ``@trigger`` that returns completion |
Collaborator
There was a problem hiding this comment.
should be function/method pointer directly. Let's compute their trigger internally.
jourdain
reviewed
Jun 15, 2026
Collaborator
There was a problem hiding this comment.
def __init__(self, completion=None, hover=None, **kwargs):
super().__init__(
"vs-editor",
**kwargs,
)
self._attr_names += [
"options",
"value",
("model_value", "modelValue"),
"theme",
"language",
"textmate",
("completion_trigger_characters", "completionTriggerCharacters"),
]
self._event_names += [
"input",
]
if completion is not None:
self._attributes["completion_trigger"] = f'completion="{self.ctrl.trigger_name(completion)}"'
if hover is not None:
self._attributes["hover_trigger"] = f'hover="{self.ctrl.trigger_name(hover)}"'
Collaborator
|
Looks great, but lets make it easier on the user. |
Address review: pass a function/method to completion/hover instead of a trigger-name string. The widget registers it via ctrl.trigger_name and hands the client the generated name, keeping the trigger internal while still returning results to Monaco's providers. Convert both examples to the callable form and update the test to the callable contract.
Collaborator
Author
|
Done in the follow-up commit, switched completion/hover to take the callable directly and register the trigger internally via ctrl.trigger_name, as you laid out. Both examples now pass the function. |
jourdain
reviewed
Jun 15, 2026
|
|
||
| import re | ||
|
|
||
| from trame.app import get_server |
Collaborator
There was a problem hiding this comment.
Can you make use of TrameApp instead of get_server?
Collaborator
|
Happy to merge as-is, but ideally I would like the examples to lineup with the latest best practice. |
Match the language-features example: wrap the live-state demo in a TrameApp subclass with the completion callback as a method, instead of a bare get_server plus module-level layout.
Collaborator
Author
|
this looks alot better, thanks for the guidance. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add
completionandhoverprops to the Editor widget. Each names a trame @trigger that receives (code, line, column) and returns results; the component registers Monaco completion/hover providers that call the trigger over the existing websocket and map the normalized results onto Monaco. The consumer writes only Python: no client-side JavaScript and no access to the Monaco instance are required.this is not an LSP as what is proposed in the language-servers branch. as such we can register other complete handlers like a live-state contextual complete as demonstrated in the example. This capability is complementary to the LSP capability.