Skip to content

feat: add live debug inline values for ST/IL editor#639

Merged
thiagoralves merged 2 commits into
developmentfrom
feat/st-debug-inline-values
Feb 28, 2026
Merged

feat: add live debug inline values for ST/IL editor#639
thiagoralves merged 2 commits into
developmentfrom
feat/st-debug-inline-values

Conversation

@thiagoralves
Copy link
Copy Markdown
Contributor

@thiagoralves thiagoralves commented Feb 28, 2026

Summary

  • Displays real-time debug variable values as green inline badges next to variable occurrences in Structured Text and Instruction List Monaco editors, matching the existing debug visualization in FBD/LD graphical editors
  • Strips comment regions (//, (* *), /* */) before scanning so variables inside comments are never decorated
  • Matches complex variable expressions (FB members like TON0.Q, array elements like my_array[3]) by reading keys directly from the debugVariableValues store map, with longest-first matching and overlap detection to prevent partial matches
  • Sets the ST/IL editor to read-only during active debug sessions (consistent with FBD/LD behavior)

Test plan

  • Open a project with an ST program containing variables of various types (BOOL, INT, REAL, TIME)
  • Include FB instances (e.g., TON, CTU) and array variables in the program
  • Mark variables for debug, compile, and start the debugger
  • Verify green inline value badges appear next to variable occurrences and update in real-time
  • Verify TON0.Q, TON0.ET, my_array[3] etc. show their own badges at the correct positions
  • Verify variables inside comments (//, (* *), /* */) do NOT get badges
  • Verify editor becomes read-only during debug and re-enables on disconnect
  • Verify switching tabs preserves/restores decorations correctly
  • Run npm run lint and npm run test — no regressions

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Inline debug values now display next to matching expressions in ST/IL editors during debugging sessions.
    • Editor automatically switches to read-only mode when the debugger is active to prevent accidental edits.
  • Improvements

    • More stable tracking and positioning of inline values, correctly handling commented code and long names to reduce visual overlap.
  • Style

    • Enhanced visual styling for debug value badges to improve readability.

Display real-time debug variable values as green inline badges next to
variable occurrences in the Structured Text and Instruction List editors,
matching the existing debug visualization in FBD/LD graphical editors.

- Strip ST comment regions (//, (* *), /* */) before scanning to avoid
  decorating variables inside comments
- Match complex variable expressions (FB members like TON0.Q, array
  elements like my_array[3]) by reading keys from debugVariableValues
- Sort expressions longest-first with overlap detection to prevent
  partial matches (e.g. TON0 inside TON0.Q)
- Set editor to read-only during active debug sessions
- Support function-block instance context for composite key resolution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 28, 2026

Walkthrough

Adds comment-aware parsing and inline debug-value decorations to the Monaco editor, wires editor readOnly to debugger visibility, resolves fbInstanceContext and debugVariableValues for matching, and adds CSS for debug-value badges.

Changes

Cohort / File(s) Summary
Monaco Editor Debug Display
src/renderer/components/_features/[workspace]/editor/monaco/index.tsx
Adds comment-stripping utility with BlockCommentState, resolves fbInstanceContext, computes stable debugVarKeySet, locates watched expression positions per line (respecting comments), prevents overlap via claimed ranges, and applies inline decorations showing current variable values; synchronizes editor readOnly with debugger visibility.
Debug Badge Styling
src/renderer/styles/globals.css
Adds .oplc-monaco-wrapper .debug-inline-value styling (white text, green background, padding, border-radius, small bold font) and a dark-theme override for the badge background.

Sequence Diagram

sequenceDiagram
    participant Editor as Monaco Editor
    participant Debugger as Debug Context
    participant Parser as Line Parser
    participant Matcher as Pattern Matcher
    participant Renderer as Decoration Renderer

    Editor->>Debugger: request fbInstanceContext & debugVariableValues
    Debugger-->>Editor: return instance context & variable map
    Editor->>Parser: for each line -> stripLineComments(preserve cols)
    Parser-->>Editor: stripped line text
    Editor->>Matcher: build regex patterns from debugVarKeySet
    Matcher->>Matcher: match patterns (prioritize long names, avoid overlaps)
    Matcher-->>Editor: matches with source ranges
    Editor->>Renderer: create decorations (claimed ranges, value lookups)
    Renderer-->>Editor: apply inline debug-value decorations
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I hop through lines where comments hide,
I strip the noise and match with pride.
Green badges bloom beside each name,
Values shown — the debugger's game.
A bunny cheers: debug made tame!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main feature addition: live debug inline values for ST/IL editors, matching the primary change in the changeset.
Description check ✅ Passed The PR description includes a clear summary of changes and a detailed test plan, but is missing required sections from the template like References, DOD checklist items, and test coverage percentage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/st-debug-inline-values

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderer/components/_features/`[workspace]/editor/monaco/index.tsx:
- Around line 71-105: The stripLineComments function incorrectly treats comment
markers inside string literals as comments; update stripLineComments to track a
stringLiteral state (e.g., false | "'" | '"' ) alongside BlockCommentState and
skip recognizing '//' , '(*', '*)', '/*', '*/' when inside a string, handling
escape/backslash so an escaped quote doesn't end the string; modify the while
loop logic in stripLineComments to toggle stringLiteral on unescaped quotes and
only enter or exit block/line comment logic when stringLiteral is false so
inline strings like 'http://...' are preserved.

In `@src/renderer/styles/globals.css`:
- Around line 198-200: The dark-mode inline debug badge (.dark
.oplc-monaco-wrapper .debug-inline-value) uses a too-light green; update its
background-color to a slightly darker, higher-contrast shade (for example
`#2e7d32` or a similar darker green) so 11px white text meets
accessibility/contrast and remains readable in dark mode.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 212617b and a47858d.

📒 Files selected for processing (2)
  • src/renderer/components/_features/[workspace]/editor/monaco/index.tsx
  • src/renderer/styles/globals.css

Comment on lines +71 to +105
function stripLineComments(line: string, state: BlockCommentState): { stripped: string; state: BlockCommentState } {
const chars = [...line]
let i = 0
let s = state

while (i < chars.length) {
if (s) {
const endMarker = s === 'paren' ? ')' : '/'
if (chars[i] === '*' && chars[i + 1] === endMarker) {
chars[i] = ' '
chars[i + 1] = ' '
i += 2
s = false
} else {
chars[i] = ' '
i++
}
} else {
if (chars[i] === '/' && chars[i + 1] === '/') {
for (let j = i; j < chars.length; j++) chars[j] = ' '
break
}
if (chars[i] === '(' && chars[i + 1] === '*') {
chars[i] = ' '
chars[i + 1] = ' '
i += 2
s = 'paren'
} else if (chars[i] === '/' && chars[i + 1] === '*') {
chars[i] = ' '
chars[i + 1] = ' '
i += 2
s = 'slash'
} else {
i++
}
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.

⚠️ Potential issue | 🟠 Major

Guard comment parsing against string literals.

Line 89, Line 93, and Line 98 currently treat comment markers as comments even when they appear inside ST/IL strings (e.g., 'http://...'), which can suppress valid inline decorations later on the same line.

💡 Suggested fix
 function stripLineComments(line: string, state: BlockCommentState): { stripped: string; state: BlockCommentState } {
   const chars = [...line]
   let i = 0
   let s = state
+  let inString = false
 
   while (i < chars.length) {
     if (s) {
       const endMarker = s === 'paren' ? ')' : '/'
       if (chars[i] === '*' && chars[i + 1] === endMarker) {
@@
     } else {
+      if (chars[i] === "'") {
+        // ST escaped quote inside string: ''
+        if (inString && chars[i + 1] === "'") {
+          i += 2
+          continue
+        }
+        inString = !inString
+        i++
+        continue
+      }
+
+      if (inString) {
+        i++
+        continue
+      }
+
       if (chars[i] === '/' && chars[i + 1] === '/') {
         for (let j = i; j < chars.length; j++) chars[j] = ' '
         break
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/_features/`[workspace]/editor/monaco/index.tsx around
lines 71 - 105, The stripLineComments function incorrectly treats comment
markers inside string literals as comments; update stripLineComments to track a
stringLiteral state (e.g., false | "'" | '"' ) alongside BlockCommentState and
skip recognizing '//' , '(*', '*)', '/*', '*/' when inside a string, handling
escape/backslash so an escaped quote doesn't end the string; modify the while
loop logic in stripLineComments to toggle stringLiteral on unescaped quotes and
only enter or exit block/line comment logic when stringLiteral is false so
inline strings like 'http://...' are preserved.

Comment on lines +198 to +200
.dark .oplc-monaco-wrapper .debug-inline-value {
background-color: #388e3c;
}
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.

⚠️ Potential issue | 🟡 Minor

Increase dark-mode badge contrast for small text.

Line 199 uses a lighter green that can drop readability for 11px white text. A slightly darker shade would improve accessibility.

💡 Suggested tweak
 .dark .oplc-monaco-wrapper .debug-inline-value {
-  background-color: `#388e3c`;
+  background-color: `#2e7d32`;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.dark .oplc-monaco-wrapper .debug-inline-value {
background-color: #388e3c;
}
.dark .oplc-monaco-wrapper .debug-inline-value {
background-color: `#2e7d32`;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/styles/globals.css` around lines 198 - 200, The dark-mode inline
debug badge (.dark .oplc-monaco-wrapper .debug-inline-value) uses a too-light
green; update its background-color to a slightly darker, higher-contrast shade
(for example `#2e7d32` or a similar darker green) so 11px white text meets
accessibility/contrast and remains readable in dark mode.

Split the single useEffect into a position-scanning useMemo (runs once
when the watched variable set changes) and a value-stamping useEffect
(runs on each 50ms poll with O(n) map lookups only). The editor is
read-only during debug so positions are stable and don't need rescanning.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/renderer/components/_features/[workspace]/editor/monaco/index.tsx (1)

71-106: ⚠️ Potential issue | 🟠 Major

String literals are still parsed as comments in stripLineComments.

This still misclassifies markers like //, (*, or /* when they appear inside ST/IL string literals, which can hide valid debug decorations on that line.

Proposed fix
 type BlockCommentState = false | 'paren' | 'slash'
 function stripLineComments(line: string, state: BlockCommentState): { stripped: string; state: BlockCommentState } {
   const chars = [...line]
   let i = 0
   let s = state
+  let inString: false | "'" | '"' = false

   while (i < chars.length) {
     if (s) {
       const endMarker = s === 'paren' ? ')' : '/'
       if (chars[i] === '*' && chars[i + 1] === endMarker) {
@@
       } else {
         chars[i] = ' '
         i++
       }
     } else {
+      const ch = chars[i]
+      if ((ch === "'" || ch === '"') && !inString) {
+        inString = ch
+        i++
+        continue
+      }
+      if (inString) {
+        // ST escaped single quote: ''
+        if (inString === "'" && chars[i] === "'" && chars[i + 1] === "'") {
+          i += 2
+          continue
+        }
+        if (chars[i] === inString) inString = false
+        i++
+        continue
+      }
+
       if (chars[i] === '/' && chars[i + 1] === '/') {
         for (let j = i; j < chars.length; j++) chars[j] = ' '
         break
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/_features/`[workspace]/editor/monaco/index.tsx around
lines 71 - 106, stripLineComments currently treats comment markers inside string
literals as real comments; update stripLineComments to track string literal
state (e.g., a inString flag or current quote char) alongside the
BlockCommentState (s) and ignore sequences like //, (*, /* when inside a string,
handling escaped quotes correctly so you don't exit the string on an escaped
quote; modify the loop that scans chars (used in stripLineComments) to toggle
the string state when encountering opening/closing quotes and to skip
comment-detection logic while inString, preserving existing block-comment
handling when not in a string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderer/components/_features/`[workspace]/editor/monaco/index.tsx:
- Around line 347-354: The loop that finds matches for each expression stops
after the first match due to the break, causing only one badge per expression
per line; remove the break inside the while ((match =
pattern.exec(result.stripped)) !== null) loop so the code continues scanning for
additional non-overlapping matches (respecting the claimed array check) and
pushes multiple positions for the same expr on the same line (keep using
claimed, positions.push, startCol/endCol, and lineNumber as-is).

---

Duplicate comments:
In `@src/renderer/components/_features/`[workspace]/editor/monaco/index.tsx:
- Around line 71-106: stripLineComments currently treats comment markers inside
string literals as real comments; update stripLineComments to track string
literal state (e.g., a inString flag or current quote char) alongside the
BlockCommentState (s) and ignore sequences like //, (*, /* when inside a string,
handling escaped quotes correctly so you don't exit the string on an escaped
quote; modify the loop that scans chars (used in stripLineComments) to toggle
the string state when encountering opening/closing quotes and to skip
comment-detection logic while inString, preserving existing block-comment
handling when not in a string.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a47858d and e496377.

📒 Files selected for processing (1)
  • src/renderer/components/_features/[workspace]/editor/monaco/index.tsx

Comment on lines +347 to +354
while ((match = pattern.exec(result.stripped)) !== null) {
const startCol = match.index + 1
const endCol = startCol + match[0].length
if (claimed.some(([s, e]) => startCol < e && endCol > s)) continue
claimed.push([startCol, endCol])
positions.push({ expr, line: lineNumber, startCol, endCol })
break // Only first occurrence per expression per line
}
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.

⚠️ Potential issue | 🟡 Minor

Only the first same-expression occurrence per line gets a badge.

break at Line 353 stops scanning after one match per expression, so repeated uses on the same line are skipped.

Proposed fix
         while ((match = pattern.exec(result.stripped)) !== null) {
           const startCol = match.index + 1
           const endCol = startCol + match[0].length
           if (claimed.some(([s, e]) => startCol < e && endCol > s)) continue
           claimed.push([startCol, endCol])
           positions.push({ expr, line: lineNumber, startCol, endCol })
-          break // Only first occurrence per expression per line
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/_features/`[workspace]/editor/monaco/index.tsx around
lines 347 - 354, The loop that finds matches for each expression stops after the
first match due to the break, causing only one badge per expression per line;
remove the break inside the while ((match = pattern.exec(result.stripped)) !==
null) loop so the code continues scanning for additional non-overlapping matches
(respecting the claimed array check) and pushes multiple positions for the same
expr on the same line (keep using claimed, positions.push, startCol/endCol, and
lineNumber as-is).

@thiagoralves thiagoralves merged commit de60e9a into development Feb 28, 2026
11 checks passed
@thiagoralves thiagoralves deleted the feat/st-debug-inline-values branch February 28, 2026 21:54
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.

1 participant