Skip to content

Conversation

@QuentinLeeWeber
Copy link
Contributor

This PR introduces a major rewrite of the Span inspector:

  • Replaced HTML-based highlighting with QSyntaxHighlighter.
  • Added caching for improved performance and prevent the TextArea Position from resetting when cursor is moved.
  • Made the input TextArea scrollable.
  • Added actual syntax highlighting functionality for Rust code.

Leon Matthes and others added 30 commits March 27, 2025 09:05
This highlights all output tokens that share the same span
as the text cursor is hovering over.
Expansion erros are now shown directly in the output textfield.
- Fix incorrect calculation of target span
- Fix false highlighting caused by Pretty Please code manipulation
- Prevents issues with special characters like '<' and '>' breaking the output HTML
These tokens are now ignored during parsing because prettyplease
inserts them automatically in certain situations.
…fresh bug

- Make output text area scrollable
- Adjust text color to match the theme
- Fix edge case where text change did not trigger refresh if cursor position stayed the same
- Before tests were failing due to an outdated Cargo.lock
- Tests were failing due to an outdated Cargo.lock
@QuentinLeeWeber QuentinLeeWeber self-assigned this Dec 10, 2025
@QuentinLeeWeber QuentinLeeWeber added the ⬆️ feature New feature or request label Dec 10, 2025
@codecov
Copy link

codecov bot commented Dec 10, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (c98fb62) to head (d2931f1).

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #1368   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           75        75           
  Lines        13108     13108           
=========================================
  Hits         13108     13108           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@LeonMatthesKDAB
Copy link
Collaborator

Hm, I managed to get the span inspector to panic:

thread 'main' (86280) panicked at examples/span-inspector/src/syntax_highlighter.rs:33:28:
index out of bounds: the len is 8 but the index is 8
stack backtrace:
   0: __rustc::rust_begin_unwind
             at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/panicking.rs:698:5
   1: core::panicking::panic_fmt
             at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/core/src/panicking.rs:75:14
   2: core::panicking::panic_bounds_check
             at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/core/src/panicking.rs:271:5
   3: <usize as core::slice::index::SliceIndex<[T]>>::index_mut
             at /home/kdab/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/slice/index.rs:273:14
   4: core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut
             at /home/kdab/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/slice/index.rs:30:15
   5: <alloc::vec::Vec<T,A> as core::ops::index::IndexMut<I>>::index_mut
             at /home/kdab/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/vec/mod.rs:3629:9
   6: span_inspector::syntax_highlighter::PendingHighlights::set_foreground
             at ./src/syntax_highlighter.rs:33:28
   7: span_inspector::syntax_highlighter::<impl span_inspector::inspector::qobject::SyntaxHighlighter>::highlight_multi_line
             at ./src/syntax_highlighter.rs:250:57
   8: span_inspector::syntax_highlighter::<impl span_inspector::inspector::qobject::SyntaxHighlighter>::highlight_block
             at ./src/syntax_highlighter.rs:108:27
   9: span_inspector::inspector::qobject::_::__SyntaxHighlighter__highlight_block::__SyntaxHighlighter__highlight_block
             at ./src/inspector.rs:129:14
  10: span_inspector::inspector::qobject::_::__SyntaxHighlighter__highlight_block::{{closure}}
             at ./src/inspector.rs:15:1
  11: cxx::unwind::prevent_unwind
             at /home/kdab/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/cxx-1.0.146/src/unwind.rs:23:15
  12: cxxbridge1$SyntaxHighlighter$highlight_block
             at ./src/inspector.rs:131:9
  13: _ZN17SyntaxHighlighter14highlightBlockERK7QString
             at /home/kdab/Documents/projects/3371-Rust-RnD/cxx-qt/target/debug/build/span-inspector-ee4b217e7f92b668/out/cxxqtgen/src/inspector.cxx.cpp:898:47
  14: <unknown>
  15: <unknown>
  16: <unknown>
  17: _ZN13QTextDocument14contentsChangeEiii
  18: _ZN20QTextDocumentPrivate10finishEditEv
  19: _ZN11QTextCursor10insertTextERK7QStringRK15QTextCharFormat
  20: _ZN11QTextCursor10insertTextERK7QString
  21: <unknown>
  22: <unknown>
  23: _ZN14QQuickTextEdit13keyPressEventEP9QKeyEvent
  24: _ZN17QQuickItemPrivate15deliverKeyEventEP9QKeyEvent
  25: _ZN10QQuickItem5eventEP6QEvent
  26: _ZN14QQuickTextEdit5eventEP6QEvent
  27: _ZN16QCoreApplication15notifyInternal2EP7QObjectP6QEvent
  28: _ZN26QQuickDeliveryAgentPrivate15deliverKeyEventEP9QKeyEvent
  29: _ZN7QWindow5eventEP6QEvent
  30: _ZN16QCoreApplication15notifyInternal2EP7QObjectP6QEvent
  31: _ZN22QGuiApplicationPrivate15processKeyEventEPN29QWindowSystemInterfacePrivate8KeyEventE
  32: _ZN22QWindowSystemInterface22sendWindowSystemEventsE6QFlagsIN10QEventLoop17ProcessEventsFlagEE
  33: _ZN22QWindowSystemInterface23flushWindowSystemEventsE6QFlagsIN10QEventLoop17ProcessEventsFlagEE
  34: _ZN7QObject5eventEP6QEvent
  35: _ZN16QCoreApplication15notifyInternal2EP7QObjectP6QEvent
  36: _ZN23QCoreApplicationPrivate16sendPostedEventsEP7QObjectiP11QThreadData
  37: <unknown>
  38: <unknown>
  39: <unknown>
  40: g_main_context_iteration
  41: _ZN20QEventDispatcherGlib13processEventsE6QFlagsIN10QEventLoop17ProcessEventsFlagEE
  42: _ZN10QEventLoop4execE6QFlagsINS_17ProcessEventsFlagEE
  43: _ZN16QCoreApplication4execEv
  44: _ZN4rust9cxxqtlib116qapplicationExecI15QGuiApplicationEEiRT_
             at /home/kdab/Documents/projects/3371-Rust-RnD/cxx-qt/target/debug/build/cxx-qt-lib-40a724fba6e6b9af/out/cxxqtbuild/include/cxx-qt-lib/core/qcoreapplication.h:64:46
  45: rust$cxxqtlib1$cxxbridge1$qguiapplication_exec
             at /home/kdab/Documents/projects/3371-Rust-RnD/cxx-qt/target/debug/build/cxx-qt-lib-40a724fba6e6b9af/out/cxxqtgen/src/gui/qguiapplication.cxx.cpp:132:31
  46: cxx_qt_lib::gui::qguiapplication::ffi::qguiapplication_exec
             at /home/kdab/Documents/projects/3371-Rust-RnD/cxx-qt/crates/cxx-qt-lib/src/gui/qguiapplication.rs:71:23
  47: cxx_qt_lib::gui::qguiapplication::<impl cxx_qt_lib::gui::qguiapplication::ffi::QGuiApplication>::exec
             at /home/kdab/Documents/projects/3371-Rust-RnD/cxx-qt/crates/cxx-qt-lib/src/gui/qguiapplication.rs:153:9
  48: span_inspector::main
             at ./src/main.rs:40:13
  49: core::ops::function::FnOnce::call_once
             at /home/kdab/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5

Copy link
Collaborator

@LeonMatthesKDAB LeonMatthesKDAB left a comment

Choose a reason for hiding this comment

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

Overall, this looks surprisingly clean 🥳
I had expected a custom QSyntaxHighlighter to be much more annoying to implement.
The background coloring for spans is also really easy to parse visually.

Good job, thank you 👍
Just some smaller nitpicks remaining.

Comment on lines +203 to +222
impl cxx_qt::Constructor<(*mut QTextDocument,)> for qobject::SyntaxHighlighter {
type BaseArguments = (*mut QTextDocument,);
type InitializeArguments = ();
type NewArguments = ();

fn route_arguments(
args: (*mut QTextDocument,),
) -> (
Self::NewArguments,
Self::BaseArguments,
Self::InitializeArguments,
) {
((), args, ())
}

fn new(_: ()) -> SyntaxHighlighterRust {
SyntaxHighlighterRust::default()
}
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yay, all the work to implement the Constructor stuff is paying off 🥳

);
let expand_result = Self::expand(&text.to_string());

let (formated_rust, char_flags) = match expand_result.clone() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
let (formated_rust, char_flags) = match expand_result.clone() {
let (formatted_rust, char_flags) = match expand_result.clone() {

nit-pick: typo

.map_err(|err| eprintln!("Parsing error: {err}"))
.unwrap();

let formated_rust = prettyplease::unparse(&file);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
let formated_rust = prettyplease::unparse(&file);
let formatted_rust = prettyplease::unparse(&file);

typo

Comment on lines 300 to 303
self.as_mut()
.rust_mut()
.output_highlighter
.pin_mut()
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is duplicated boilerplate from the statement above.
Please extract this into an output_highlighter local variable.

Comment on lines +111 to +114
unsafe extern "C++Qt" {
include!(<QSyntaxHighlighter>);
#[qobject]
type QSyntaxHighlighter;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please split the CXX-Qt bridge into two, where all of this goes into the syntax_highlighter.rs .
That would clean this module up quite a bit.

Copy link
Contributor Author

@QuentinLeeWeber QuentinLeeWeber Dec 22, 2025

Choose a reason for hiding this comment

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

It does not seem possible for me to split this up, because both parts need to use the same types, but they are defined in different namespaces.

For example:

error[E0308]: mismatched types
   --> examples/span-inspector/src/inspector.rs:161:43
    |
161 |                 make_q_syntax_highlighter(output.text_document());
    |                 ------------------------- ^^^^^^^^^^^^^^^^^^^^^^ expected `syntax_highlighter_ffi::QTextDocument`, found `qobject::QTextDocument`
    |                 |
    |                 arguments to this function are incorrect
    |
    = note: `qobject::QTextDocument` and `syntax_highlighter_ffi::QTextDocument` have similar names, but are actually distinct types

(None, State::Literal)
}

(State::Default, "#[") => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

#![ can also mark the start of a macro (which then applies to module scope)

.qt_module("Network")
.qt_module("Quick")
.file("src/inspector.rs")
.qobject_header("include/helper.h")
Copy link
Collaborator

Choose a reason for hiding this comment

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

This will need to use ccp_file now that 0.8 is out :) (needs rebasing/merging of main)

.file("src/inspector.rs")
.qobject_header("include/helper.h")
.build();
println!("cargo:rerun-if-changed=include/helper.h");
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is not needed, cpp_file will do this automatically :)

proc-macro2.workspace = true
prettyplease = { version = "0.2", features = ["verbatim"] }
syn.workspace = true
fancy-regex = "0.16.2"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Any reason you used fancy-regex and not regex?

Copy link
Contributor Author

@QuentinLeeWeber QuentinLeeWeber Dec 22, 2025

Choose a reason for hiding this comment

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

It seems that the regex crate does not support lookahead or lookbehind.

for i in 0..block_length {
let color = match flags[(block_position + i) as usize] {
TokenFlag::Original => QColor::from_rgba(0, 100, 155, 170),
TokenFlag::Generated => QColor::from_rgba(0, 255, 0, 15),
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this green has a little bit too much blue in it, which visually makes it appeary related to the "Original" coloring.
I guess you can also try adding an equal amount of red to balance it and just make it brighter.
But overall, I really like the approach of using the background color to indicate this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⬆️ feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants