Skip to content

RPC command console#540

Open
epicleafies wants to merge 28 commits intobitcoin-core:qt6from
epicleafies:rpc-command-console
Open

RPC command console#540
epicleafies wants to merge 28 commits intobitcoin-core:qt6from
epicleafies:rpc-command-console

Conversation

@epicleafies
Copy link
Copy Markdown
Contributor

@epicleafies epicleafies commented Mar 25, 2026

Created page for rpc command console. #508
Created option under settings for console.
Preserved parsing logic currently in use.
Added unit and functional tests.

Screenshot from 2026-03-24 16-38-26 Screenshot from 2026-03-24 16-39-07

Copy link
Copy Markdown
Collaborator

@johnny9 johnny9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noticed issues with scrolling when command responses are large. Switch from a List View to columns fixes it due to the simpler layout mechanism of columns

Comment thread qml/pages/node/CommandConsole.qml Outdated

// Output area — ListView virtualizes delegates so only visible rows are
// instantiated, preventing layout churn during long console sessions.
ListView {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The list view has problems with very long command responses. In my testing, the best fix is to convert this to a Flickable+Column+Repeater. With that we just need to make sure the command history buffer has a sensible limit.

Copy link
Copy Markdown
Contributor

@MarnixCroes MarnixCroes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10a6d50

A few observations from first quick test:

  • The entry box should automatically be selected when opening the console window.
  • Selecting the entry box does not always work that well. I have to click it twice.
  • Up and down keystroke should navigate suggested commands (when there are, for example type only get), instead of the history
  • When selecting a suggested option using mouse arrow and clicking it, the entry box gets deselected. Which it shouldn't, so the user can select the command and then press enter. Without having to select the entry box again first and then press enter.

Comment thread qml/pages/node/CommandConsole.qml Outdated
ContinueButton {
id: submitButton
objectName: "consoleSubmitButton"
text: qsTr("Run")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't an icon better, than text?

Maybe an arrow? (with better color and style)

Image

@epicleafies epicleafies force-pushed the rpc-command-console branch from 387cc36 to 676db66 Compare April 17, 2026 04:43
Port the RPC command line parser from src/qt/rpcconsole.cpp into a
standalone class suitable for use in the QML GUI. Handles standard
and functional syntax, nested commands, result subscripts, quoted
strings, and redacts sensitive arguments (e.g. walletpassphrase).

Adds a unit test executable (rpc_console_tests) covering parse-only
mode, execution, nested commands, subscripts, and redaction.
Introduce RpcConsoleModel, a QObject that sits between QML and
RpcCommandExecutor. It manages command submission, a capped command
history with Up/Down navigation, pending-text preservation, an
auto-complete list built from node.listRpcCommands(), and an
`executing` property for UI busy-state feedback.

Commands are dispatched to a background QThread via RpcConsoleWorker
so the UI thread is never blocked during RPC execution. Results are
delivered back on the main thread via a queued signal.

Adds a unit test executable (rpc_console_model_tests) covering
history navigation, deduplication, history size cap, and the
clear/reset invokables.
The RpcConsoleModel instantiated in bitcoin.cpp uses bitcoin_cli
(via RpcCommandExecutor → interfaces::Node::executeRpc). Add it as
a PRIVATE dependency of the bitcoinqml target so the linker can
resolve the required symbols.
Add a full-featured RPC console page backed by RpcConsoleModel.
The output area renders colour-coded lines (request, reply, error)
from a ListModel fed by commandResultReceived signals. The input
field supports Up/Down history navigation, Enter to submit, and
resets the history cursor whenever the user resumes typing. A
Clear button empties the output list on demand.

Register the QML file in bitcoin_qml.qrc so it is available as a
resource to the engine.
Instantiate RpcConsoleModel in QmlGuiMain, connect it to
NodeModel::nodeInitialized so its available-command list is
populated once the node is ready, and expose it as the
'rpcConsoleModel' context property. Register RpcConsoleModel as
an uncreatable QML type so its enums are accessible from QML.

Add a 'Console' setting item to NodeSettings that pushes the
CommandConsole page onto the navigation stack, consistent with
the existing 'Network Traffic' and 'Peers' entries.
Add qml_test_console.py to exercise the CommandConsole page end-to-end:
navigating to the page, submitting commands, verifying output lines
appear with correct colour-coding, history Up/Down navigation, and
the Clear button.

Pass -disablewallet to the GUI node in the test harness so the
process starts without wallet initialisation, matching a minimal
node-only setup appropriate for console testing.

Register the new test in the CI workflow so it runs on every push.
The port of RPCParseCommandLine replaced the assert(filter_begin_pos)
invariant in close_out_params() with a defensive if-guard. This is
incorrect: filter_begin_pos is guaranteed non-zero whenever
nDepthInsideSensitive decrements to zero, because it is set in
add_to_current_stack() at the same time the depth counter is set to 1.

Silently skipping the emplace_back when the invariant is violated would
leave sensitive arguments (walletpassphrase, encryptwallet, etc.)
unredacted in the history/display output. Restore the assert to match
src/qt/rpcconsole.cpp and catch any future logic regressions.
Switch the output delegate from Text to TextEdit with readOnly and
selectByMouse enabled, so users can select and copy command results.
Text does not support mouse selection; TextEdit with readOnly provides
the same rendering while allowing the user to highlight output text.
The help-console text, parse error message, and generic error message
were constructed as raw QString literals, making them untranslatable.

Replace the file-scope static HELP_CONSOLE_TEXT with an inline tr()
call inside the worker's execute() slot, where QObject::tr() is
available. Wrap the two error format strings in tr() in both the
worker and RpcConsoleModel::submitCommand().

No behavioral change.
QmlTestHarness had -disablewallet baked into its start() method,
applying it to every test that uses the harness. This would break any
future functional test that exercises wallet functionality. Add an
extra_args parameter to QmlTestHarness and pass -disablewallet only
from the console test, which does not need wallet support.

Also rename parseUnbalancedParenFails to parseImplicitlyClosedParenSucceeds
in test_rpccommandexecutor.cpp. The test verifies that parsing succeeds
(the parser's trailing-newline injection implicitly closes the paren),
so the previous name was the opposite of what the assertion checks.
Explain why importprivkey, dumpprivkey, dumpwallet, and importwallet are
intentionally absent from HISTORY_FILTER: their sensitive data is a file
path or key blob rather than a passphrase, so redacting them would obscure
useful history context without a meaningful security benefit.

Also add a comment clarifying the assert(filter_begin_pos) invariant:
filter_begin_pos is always set to a position > 0 (at minimum the opening
paren or space following the sensitive command name), so the assert is
always satisfied before the range is recorded.
Two small fixes to RpcConsoleWorker::execute():

* Emit commandResultReceived before calling setExecuting(false).
  The previous order created a single-frame window where the submit
  button was re-enabled before the reply line appeared in the output,
  allowing a second submission before the user could see the result.

* Match "help-console" case-insensitively and after trimming whitespace.
  The old literal comparison against "help-console\n" silently ignored
  "HELP-CONSOLE" or a command with trailing spaces.
Commands like listunspent on a large wallet can produce megabytes of
JSON. Feeding that into a Text/TextEdit element stalls the GUI layout
engine. Cap output at 50,000 characters and append a notice directing
the user to bitcoin-cli for the full result when truncation occurs.

Adds a unit test (LargeResultNode + QSignalSpy) that exercises the
full worker-thread path and verifies the truncation notice appears.
The previous layout used a Repeater inside a Column, which instantiates
every output delegate eagerly. Long console sessions accumulate many rows
and cause visible layout churn.

Switch to ListView so delegates are virtualized — only those visible on
screen are instantiated. Move the welcome/security-warning texts into the
ListView header (they scroll with content and disappear naturally as
output grows). Auto-scroll on new rows is handled by positionViewAtEnd()
deferred via Qt.callLater() so it runs after the layout pass.

The old Connections on outputScroll.contentItem.contentHeightChanged is
removed; onCountChanged is the appropriate hook for ListView.
Expose `executing` as a public page property bound to
rpcConsoleModel.executing. Functional tests previously waited on
consoleSubmitButton.enabled, which breaks if the button is disabled for
any other reason (e.g. empty input). Waiting on the executing flag gives
tests a direct, semantic signal that the worker thread has finished.

Also move the internal `_navigatingHistory` flag into a QtObject so it
does not appear in the page's public API. The flag is an implementation
detail of history navigation and has no business being accessible from
outside the component.
Three small UX improvements:

* Add a TapHandler on the NavigationBar2 header so tapping the header
  title dismisses the keyboard / unfocuses the input field.

* Add a page-level TapHandler (TakeOverForbidden) that unfocuses the
  input field when the user taps anywhere outside it. Interactive
  children retain their exclusive grabs and work normally.

* Switch the submit button from NavButton to ContinueButton with
  explicit left/right padding, and disable it when the input field is
  empty to prevent accidental empty submissions.
The page-level TapHandler added in the previous polish commit does not
fire for clicks inside the ListView: Qt 6.2's Flickable
childMouseEventFilter disrupts passive grab notifications on ancestor
items. Handlers placed inside the ListView's contentItem (z:-1
MouseArea, TapHandler) also fail — Flickable's internal press-grab
machinery prevents them from receiving events.

Fix this by placing a MouseArea outside the ListView entirely, as a
sibling item at z:1 (above the ListView's default z:0). It intercepts
every press in the output area, clears focus from the input field, then
sets mouse.accepted = false so the event passes through to the ListView
for normal scroll and tap handling.
Introduce a reusable scrollable monospace text display backed by a
list model, intended to be shared between the RPC console and the
debug log page. Built on Flickable + Column + Repeater so child
heights sum exactly and the scrollbar stays stable on long rows;
virtualization is deliberately omitted and callers must bound the
model size.

Supports optional left/right columns around the main content column
(e.g. line numbers + relative time for the debug log, or timestamps
for the console), configurable content text format so consumers opt
into RichText explicitly, theme-aware selection colours, and
coalesced auto-scroll so bulk appends schedule a single scroll.
…ting to C++

Replace the per-page ListView and JavaScript output buffer in
CommandConsole.qml with a binding to a new RpcOutputListModel owned
by RpcConsoleModel, rendered through the shared MonospaceOutputView
component. The 5000-row cap that previously lived as a JS constant
in the QML page now lives on the model and trims the oldest rows
under beginRemoveRows / endRemoveRows, and a Q_INVOKABLE clearOutput
replaces the ad-hoc clearRequested signal.

Pretty-printing of RPC replies is moved from a regex-based JS
helper on the HTML-escaped string to a UniValue tree walk in C++.
The previous regex colourised anything matching "key":-shaped text
and so false-positively coloured substrings inside string values
that happened to end with a colon; the tree walk colours only real
object keys. Request, reply, error and key colours remain theme-
aware and are pushed to the model from QML so dark/light toggles
take effect on newly-appended rows.

test_rpcconsolemodel exercises the output-model cap, reset, and
the key-vs-value discrimination that the regex could not handle.
@epicleafies epicleafies force-pushed the rpc-command-console branch from 676db66 to b1670f5 Compare April 17, 2026 05:20
Populate the autocomplete list with "help <command>" entries for every
registered RPC command, plus the "help-console" meta-command.  This lets
users discover inline help directly from the completion popup without
having to know the exact syntax.
Add a console icon (PNG + SVG source) for use in the command console
input bar, and register it in the Qt resource file.
Replace the bordered TextField and "Run" ContinueButton with a cleaner
layout: transparent input bar with a thin separator line, a console icon
prefix, zero-padding text field, and a compact icon-only submit button
with hover/press states.  The result is a more compact, chat-style input
area that matches the overall dark-theme aesthetic.
Allow users to arrow through autocomplete suggestions with Up/Down keys
when the popup is open, and accept the highlighted entry with Tab.  The
selected item is visually distinguished with the orange accent colour.

Changes:
- Track autocompleteIndex; reset it when the suggestion list updates
- Up/Down keys navigate the popup when visible, fall through to history
  browsing otherwise
- Tab accepts filteredCommands[autocompleteIndex] instead of always [0]
- Align popup x to inputField.x so it tracks the text field position
- Suppress popup from reopening during programmatic history navigation
Replace the Repeater.onItemAdded + _scrollQueued coalescing approach
with a single Connections on the Flickable's contentHeightChanged
signal. This ensures the Column has already laid out the new delegate
before scrollToBottom() fires, so contentHeight is accurate and the
scroll reaches the true bottom.
Migrate the console submit button from a plain Item with manual
MouseArea click/hover handling to an AbstractButton with proper
background, contentItem, and declarative states. This follows QML
best practices and simplifies the hover/pressed visual feedback
logic with no behavior change.
Remove the inputField.activeFocus gate from the autocomplete popup
open condition. The popup should appear whenever the typed text
matches available commands, regardless of whether the input field
has active keyboard focus (e.g. after programmatic text changes).

Add functional tests covering autocomplete popup visibility,
no-match hiding, click-to-apply suggestions, and help-prefix
matching.
Add availableCommandsIncludesHelpVariants test that verifies
RpcConsoleModel::availableCommands() includes "help <cmd>" variants
and "help-console", is sorted case-insensitively, and contains no
duplicates. Uses a CommandListNode mock that returns a known set of
commands.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants