@@ -66,25 +66,48 @@ bool CodeEditor::init() {
6666 m_Editor.SetCompletionCallbacks (
6767 [this ](size_t aSelectedIndex) { m_OnCompletionAccepted (aSelectedIndex); },
6868 [this ]() { m_OnCompletionCancelled (); });
69- // a narrow gutter decorator: a red dot marks a breakpoint, double-click toggles it
69+ // a narrow gutter decorator: a red dot marks a breakpoint, single click toggles it; an amber
70+ // right-pointing arrow marks the current paused line (VS-style). When both apply (paused on a
71+ // breakpoint line) the arrow is drawn ON TOP of the dot so both states are visible at once.
72+ //
73+ // Breakpoint TOGGLING is always allowed — the user can set/remove BPs even when Debug is off
74+ // (they're stored, just dormant until shouldArmDebug() installs the line hook). The visual
75+ // alpha fades when Debug is off as a hint that those BPs won't fire yet — see
76+ // m_BreakpointInteractionEnabled (renamed semantically to "BPs will actually fire").
7077 m_Editor.SetLineDecorator (16 .0f , [this ](TextEditor::Decorator& aDecorator) {
7178 const int32_t line0 = aDecorator.line ; // zero-based
7279 ImGui::InvisibleButton (" ##bp" , ImVec2 (aDecorator.width , aDecorator.height ));
7380 const bool hovered = ImGui::IsItemHovered ();
74- if (m_BreakpointInteractionEnabled && hovered && ImGui::IsMouseDoubleClicked (ImGuiMouseButton_Left)) {
81+ if (hovered && ImGui::IsMouseClicked (ImGuiMouseButton_Left)) {
7582 const bool has = (m_BreakpointLines.find (line0) != m_BreakpointLines.end ());
7683 if (m_OnBreakpointToggled) {
77- m_OnBreakpointToggled (line0, !has); // toggle
84+ m_OnBreakpointToggled (line0, !has); // toggle — allowed regardless of debug state
7885 }
7986 }
8087 const bool isBreakpoint = (m_BreakpointLines.find (line0) != m_BreakpointLines.end ());
81- const bool drawHoverHalo = hovered && m_BreakpointInteractionEnabled; // no false affordance when interaction is off
82- if (isBreakpoint || drawHoverHalo) {
83- const ImVec2 rectMin = ImGui::GetItemRectMin ();
88+ const ImVec2 rectMin = ImGui::GetItemRectMin ();
89+ const float centerX = rectMin.x + aDecorator.width * 0 .5f ;
90+ const float centerY = rectMin.y + aDecorator.height * 0 .5f ;
91+ ImDrawList* drawList = ImGui::GetWindowDrawList ();
92+ if (isBreakpoint || hovered) {
8493 const float radius = (aDecorator.height - 6 .0f ) * 0 .5f ;
85- const uint8_t bpAlpha = m_BreakpointInteractionEnabled ? 255 : 110 ; // fade existing dot when toggling is off
94+ // when debug is off the user can still toggle BPs but they won't fire yet — fade the
95+ // dot alpha as a visual hint.
96+ const uint8_t bpAlpha = m_BreakpointInteractionEnabled ? 255 : 110 ;
8697 const ImU32 color = isBreakpoint ? IM_COL32 (220 , 40 , 40 , bpAlpha) : IM_COL32 (220 , 40 , 40 , 90 );
87- ImGui::GetWindowDrawList ()->AddCircleFilled (ImVec2 (rectMin.x + aDecorator.width * 0 .5f , rectMin.y + aDecorator.height * 0 .5f ), radius, color);
98+ drawList->AddCircleFilled (ImVec2 (centerX, centerY), radius, color);
99+ }
100+ // current paused line: amber right-pointing arrow (VS-style). Drawn after the bp dot so it
101+ // sits on top when both coexist — the high-contrast amber/red pair stays readable.
102+ if (m_CurrentExecLine == line0) {
103+ const float arrowHalfWidth = aDecorator.width * 0 .30f ;
104+ const float arrowHalfHeight = aDecorator.height * 0 .28f ;
105+ const ImVec2 p1 (centerX - arrowHalfWidth, centerY - arrowHalfHeight); // top-left
106+ const ImVec2 p2 (centerX - arrowHalfWidth, centerY + arrowHalfHeight); // bottom-left
107+ const ImVec2 p3 (centerX + arrowHalfWidth, centerY); // right-middle (tip)
108+ drawList->AddTriangleFilled (p1, p2, p3, IM_COL32 (255 , 200 , 0 , 255 ));
109+ // thin black outline so the arrow stays visible against any line background tint.
110+ drawList->AddTriangle (p1, p2, p3, IM_COL32 (0 , 0 , 0 , 200 ), 1 .0f );
88111 }
89112 });
90113 return true ;
@@ -280,10 +303,12 @@ void CodeEditor::ClearErrorMarkers() {
280303
281304void CodeEditor::AddErrorMarker (const size_t & vErrorLine, const std::string& vErrorMsg) {
282305 m_ErrorMarkers[(int32_t )vErrorLine] = vErrorMsg;
283- // AddMarker's textTooltip carries the message again, so the per-line error tooltip
284- // is back (regression #2/#4 fixed); the cursor still jumps to the error line.
306+ // AddMarker's textTooltip carries the message again, so the per-line error tooltip is back
307+ // (regression #2/#4 fixed). The caret is NOT moved here — that used to jump the caret to
308+ // the error line on every error-revision bump (incl. the end-of-run bump from
309+ // ScriptingEngine::m_run), which fought with CodePane's VS-style caret sync on Step. The
310+ // host now drives caret movement exclusively via MoveCursorTo on state-revision bumps.
285311 m_RebuildMarkers ();
286- m_Editor.SetCursor ((int32_t )vErrorLine, 0 );
287312}
288313
289314// Commands
@@ -390,9 +415,15 @@ void CodeEditor::SetCurrentExecLine(int32_t aZeroBasedLine) {
390415 if (m_CurrentExecLine != aZeroBasedLine) {
391416 m_CurrentExecLine = aZeroBasedLine;
392417 m_RebuildMarkers ();
393- if (m_CurrentExecLine >= 0 ) {
394- m_Editor.SetCursor (m_CurrentExecLine, 0 ); // scroll to the paused line
395- }
418+ }
419+ }
420+
421+ void CodeEditor::MoveCursorTo (int32_t aZeroBasedLine, int32_t aZeroBasedColumn) {
422+ // explicit caret jump — used by CodePane to sync the caret to the active line on each new
423+ // pause event (state revision bump). Separated from SetCurrentExecLine so the marker update
424+ // (drawn every frame) doesn't fight a user who manually moved the caret between pauses.
425+ if (aZeroBasedLine >= 0 ) {
426+ m_Editor.SetCursor (aZeroBasedLine, aZeroBasedColumn);
396427 }
397428}
398429
@@ -410,13 +441,31 @@ float CodeEditor::GetCurrentFontScale() const {
410441
411442void CodeEditor::m_RebuildMarkers () {
412443 m_Editor.ClearMarkers ();
413- // breakpoints are drawn by the line decorator (a red dot); markers carry errors + current line
444+ // breakpoints are drawn by the line decorator (a red dot); markers carry errors + current line.
445+ //
446+ // Line-number gutter color: leave default (0 = no override). Only the code-line background is
447+ // tinted, so the gutter numbers stay readable on dark themes (the amber tint behind white text
448+ // washed out the line numbers on the previous design).
449+ //
450+ // TextEditor::AddMarker keys by line, so two calls on the same line — the second wins. On a
451+ // pause-on-error the error line IS the current exec line, so we'd lose either the amber
452+ // pause hint or the red error styling + tooltip. Merge them: red text background (error) +
453+ // error message as the text tooltip + "current line" gutter tooltip on hover so the dual
454+ // role of the line is still surfaced.
414455 for (const auto & errorMarker : m_ErrorMarkers) {
415- m_Editor.AddMarker (errorMarker.first - 1 , 0 , IM_COL32 (200 , 0 , 40 , 80 ), " " , errorMarker.second );
416- }
417- // current paused line: amber line number + translucent amber text
456+ const int32_t errorLine0Based = errorMarker.first - 1 ;
457+ const bool isAlsoCurrentLine = (m_CurrentExecLine == errorLine0Based);
458+ const char * lineNumberTooltip = isAlsoCurrentLine ? " current line" : " " ;
459+ m_Editor.AddMarker (errorLine0Based, 0 , IM_COL32 (200 , 0 , 40 , 80 ), lineNumberTooltip, errorMarker.second );
460+ }
461+ // current paused line: translucent amber over the code line — ONLY when not already emitted
462+ // as a merged error marker above. Alpha kept low so the syntax-highlighted text reads cleanly
463+ // on top.
418464 if (m_CurrentExecLine >= 0 ) {
419- m_Editor.AddMarker (m_CurrentExecLine, IM_COL32 (255 , 200 , 0 , 255 ), IM_COL32 (255 , 200 , 0 , 60 ), " current line" , " " );
465+ const bool alreadyEmittedAsError = (m_ErrorMarkers.find (m_CurrentExecLine + 1 ) != m_ErrorMarkers.end ());
466+ if (!alreadyEmittedAsError) {
467+ m_Editor.AddMarker (m_CurrentExecLine, 0 , IM_COL32 (255 , 200 , 0 , 60 ), " current line" , " " );
468+ }
420469 }
421470}
422471
0 commit comments