From 30db29dd9d11933d1d1f9f3c1a0dd65e085f97ba Mon Sep 17 00:00:00 2001 From: John Dallaway Date: Thu, 18 Jun 2026 17:44:37 +0100 Subject: [PATCH] Accommodate pre-processor lines in sticky lines provider --- .../ui/editor/CSourceStickyLinesProvider.java | 67 ++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/CSourceStickyLinesProvider.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/CSourceStickyLinesProvider.java index e11fc7bf778..f0958a2b8a8 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/CSourceStickyLinesProvider.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/CSourceStickyLinesProvider.java @@ -14,6 +14,8 @@ *******************************************************************************/ package org.eclipse.cdt.internal.ui.editor; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.LinkedList; import java.util.List; @@ -29,6 +31,13 @@ import org.eclipse.cdt.core.dom.ast.IASTIfStatement; import org.eclipse.cdt.core.dom.ast.IASTNode; import org.eclipse.cdt.core.dom.ast.IASTNodeSelector; +import org.eclipse.cdt.core.dom.ast.IASTPreprocessorElifStatement; +import org.eclipse.cdt.core.dom.ast.IASTPreprocessorElseStatement; +import org.eclipse.cdt.core.dom.ast.IASTPreprocessorEndifStatement; +import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIfStatement; +import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIfdefStatement; +import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIfndefStatement; +import org.eclipse.cdt.core.dom.ast.IASTPreprocessorStatement; import org.eclipse.cdt.core.dom.ast.IASTReturnStatement; import org.eclipse.cdt.core.dom.ast.IASTStatement; import org.eclipse.cdt.core.dom.ast.IASTSwitchStatement; @@ -68,7 +77,7 @@ public class CSourceStickyLinesProvider implements IStickyLinesProvider { public List getStickyLines(ISourceViewer sourceViewer, int lineNumber, StickyLinesProperties properties) { final long startTime = System.currentTimeMillis(); - final LinkedList stickyLines = new LinkedList<>(); + List stickyLines = new LinkedList<>(); // lineNumber in the zero-based line number as known to the IDocument // textWidgetLineNumber is the the line number as known to the ISourceViewer final int textWidgetLineNumber = mapLineNumberToWidget(sourceViewer, lineNumber); @@ -119,12 +128,18 @@ public List getStickyLines(ISourceViewer sourceViewer, int lineNumb try { final int offset = sourceViewer.getDocument().getLineOffset(lineNumber); node = nodeSelector.findEnclosingNode(offset, line.length()); + if (node instanceof IASTPreprocessorStatement) { + // find enclosing non-preprocessor node using adjusted offset + final int nodeOffset = node.getFileLocation().getNodeOffset(); + node = nodeSelector.findEnclosingNode(nodeOffset - 1, 2); + } } catch (BadLocationException e) { ILog.get().error("Error getting line offset for sticky lines", e); //$NON-NLS-1$ return stickyLines; } // process sticky ancestor nodes + final LinkedList ancestorLines = new LinkedList<>(); while (null != node) { if (DEBUG) { System.out.printf("> Examining AST node: %s (lines %d-%d)\n", node.getClass().getSimpleName(), //$NON-NLS-1$ @@ -132,10 +147,14 @@ public List getStickyLines(ISourceViewer sourceViewer, int lineNumb node.getFileLocation().getEndingLineNumber()); } if (nodeInstanceOfStickyClass(node)) { - processStickyNode(node, fileLineNumber, sourceViewer, stickyLines); + processStickyNode(node, fileLineNumber, sourceViewer, ancestorLines); } node = node.getParent(); } + + // process sticky pre-processor nodes and merge with ancestor nodes + final List preprocessorLines = findPreprocessorStickyLines(fileLineNumber, sourceViewer); + stickyLines = mergeStickyLines(ancestorLines, preprocessorLines); } if (DEBUG) { System.out.println("> Sticky line count: " + stickyLines.size()); //$NON-NLS-1$ @@ -144,6 +163,45 @@ public List getStickyLines(ISourceViewer sourceViewer, int lineNumb return stickyLines; } + private List findPreprocessorStickyLines(int fileLineNumber, ISourceViewer sourceViewer) { + final Deque stack = new ArrayDeque<>(); + for (IASTPreprocessorStatement statement : fAst.getAllPreprocessorStatements()) { + if (statement.getFileLocation().getStartingLineNumber() >= fileLineNumber) { + break; + } + if (nodeInstanceOfPreprocessorIfClass(statement) || statement instanceof IASTPreprocessorElifStatement + || statement instanceof IASTPreprocessorElseStatement) { + stack.push(statement); + } else if (statement instanceof IASTPreprocessorEndifStatement) { + IASTPreprocessorStatement previous = statement; + while (!stack.isEmpty() && !nodeInstanceOfPreprocessorIfClass(previous)) { + previous = stack.pop(); + } + } + } + final LinkedList stickyLines = new LinkedList<>(); + stack.forEach(statement -> addStickyLine(statement.getFileLocation().getStartingLineNumber(), sourceViewer, + stickyLines)); + return stickyLines; + } + + private List mergeStickyLines(List lines1, List lines2) { + final Deque deque1 = new ArrayDeque<>(lines1); + final Deque deque2 = new ArrayDeque<>(lines2); + final List mergedLines = new LinkedList<>(); + while (null != deque1.peek() && null != deque2.peek()) { + // assume both lists are sorted in ascending line number order + if (deque1.peek().getLineNumber() < deque2.peek().getLineNumber()) { + mergedLines.add(deque1.pop()); + } else { + mergedLines.add(deque2.pop()); + } + } + mergedLines.addAll(deque1); + mergedLines.addAll(deque2); + return mergedLines; + } + private void processStickyNode(IASTNode node, int fileLineNumber, ISourceViewer sourceViewer, LinkedList stickyLines) { final int startingLineNumber = node.getFileLocation().getStartingLineNumber(); @@ -224,6 +282,11 @@ private boolean nodeInstanceOfStickyClass(IASTNode node) { return STICKY_NODE_CLASSES.stream().anyMatch(nodeClass -> nodeClass.isInstance(node)); } + private boolean nodeInstanceOfPreprocessorIfClass(IASTNode node) { + return node instanceof IASTPreprocessorIfStatement || node instanceof IASTPreprocessorIfdefStatement + || node instanceof IASTPreprocessorIfndefStatement; + } + private ICElement getInputElement(IEditorPart part) { final IEditorInput editorInput = part.getEditorInput(); if (null != editorInput) {