Skip to content

Commit 6382ab5

Browse files
committed
cli.lwp3.repl: add basic code completion
1 parent 192004a commit 6382ab5

File tree

2 files changed

+55
-19
lines changed

2 files changed

+55
-19
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9191
### Added
9292
- Size check when restoring firmware via USB/DFU.
9393
- Added `pybricksdev.tools.chunk()` function.
94+
- Added basic command completion to `pybricksdev lwp3 repl`.
9495
### Fixed
9596
- Wait for some time to allow program output to be received before disconnecting
9697
in the `run` command.

pybricksdev/cli/lwp3/repl.py

Lines changed: 54 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
"""
88

99
import asyncio
10+
from enum import Enum
1011
import inspect
1112
import logging
1213
import os
14+
import re
1315
import struct
1416
from pathlib import Path
1517

@@ -18,6 +20,8 @@
1820
from bleak.backends.device import BLEDevice
1921
from bleak.backends.scanner import AdvertisementData
2022
from prompt_toolkit import PromptSession
23+
from prompt_toolkit.completion import Completer, Completion, FuzzyCompleter
24+
from prompt_toolkit.document import Document
2125
from prompt_toolkit.history import FileHistory
2226
from prompt_toolkit.patch_stdout import StdoutProxy, patch_stdout
2327

@@ -42,34 +46,65 @@
4246

4347
# The first groups is any type from bytecodes that inherits from int (includes
4448
# enums/flags) or bytes.
45-
_eval_pool.update(
46-
{
47-
k: v
48-
for k, v in bytecodes.__dict__.items()
49-
if inspect.isclass(v)
50-
and v.__module__ == bytecodes.__name__
51-
and (issubclass(v, int) or issubclass(v, bytes))
52-
}
53-
)
49+
_PARAMETER_TYPES = {
50+
k: v
51+
for k, v in bytecodes.__dict__.items()
52+
if inspect.isclass(v)
53+
and v.__module__ == bytecodes.__name__
54+
and (issubclass(v, int) or issubclass(v, bytes))
55+
}
56+
57+
_eval_pool.update(_PARAMETER_TYPES)
5458

5559
# The second group are all of the non-abstract message types from the messages module.
56-
_eval_pool.update(
57-
{
58-
k: v
59-
for k, v in messages.__dict__.items()
60-
if inspect.isclass(v)
61-
and issubclass(v, AbstractMessage)
62-
and not inspect.isabstract(v)
63-
}
64-
)
60+
_MESSAGE_KINDS = {
61+
k: v
62+
for k, v in messages.__dict__.items()
63+
if inspect.isclass(v)
64+
and issubclass(v, AbstractMessage)
65+
and not inspect.isabstract(v)
66+
}
67+
68+
_eval_pool.update(_MESSAGE_KINDS)
69+
70+
71+
class _CommandCompleter(Completer):
72+
"""
73+
Custom completer for command prompt.
74+
"""
75+
76+
# matches words with dots in them, e.g. "Enum.MEMBER"
77+
_MATCH_DOT = re.compile(r"[a-zA-Z0-9_\.]+")
78+
79+
def get_completions(self, document: Document, complete_event):
80+
if document.get_word_before_cursor() == ".":
81+
# if this is a dotted word, look up the enum member
82+
cls = _PARAMETER_TYPES.get(
83+
document.get_word_before_cursor(pattern=self._MATCH_DOT).split(".")[0]
84+
)
85+
if cls and issubclass(cls, Enum):
86+
for m in cls:
87+
yield Completion(m.name)
88+
elif document.find_enclosing_bracket_left("(", ")") is not None:
89+
# if we are inside of "(...)", list the enums and other parameter types
90+
for p in _PARAMETER_TYPES.keys():
91+
yield Completion(p)
92+
elif document.get_word_under_cursor() == "":
93+
# if we are at the beginning of the line, list the commands
94+
for m in _MESSAGE_KINDS.keys():
95+
yield Completion(m)
6596

6697

6798
async def repl() -> None:
6899
"""
69100
Provides an interactive REPL for sending and receiving LWP3 messages.
70101
"""
71102
os.makedirs(history_file.parent, exist_ok=True)
72-
session = PromptSession(history=FileHistory(history_file))
103+
104+
session = PromptSession(
105+
history=FileHistory(history_file),
106+
completer=FuzzyCompleter(_CommandCompleter()),
107+
)
73108

74109
def match_lwp3_uuid(dev: BLEDevice, adv: AdvertisementData) -> None:
75110
if LWP3_HUB_SERVICE_UUID.lower() not in adv.service_uuids:

0 commit comments

Comments
 (0)