Skip to content

Commit bbe4e16

Browse files
committed
StickyLines work in progresss
1 parent 823adf6 commit bbe4e16

File tree

2 files changed

+352
-0
lines changed

2 files changed

+352
-0
lines changed

org.eclipse.jdt.ui/plugin.xml

+14
Original file line numberDiff line numberDiff line change
@@ -7851,4 +7851,18 @@
78517851
file-extensions="class without source">
78527852
</file-association>
78537853
</extension>
7854+
<extension
7855+
point="org.eclipse.ui.editors.stickyLinesProviders">
7856+
<stickyLinesProvider
7857+
class="org.eclipse.jdt.internal.ui.javaeditor.JavaStickyLinesProvider"
7858+
id="org.eclipse.jdt.internal.ui.StickyLinesProviderJava">
7859+
<enabledWhen>
7860+
<and>
7861+
<with variable="editor">
7862+
<instanceof value="org.eclipse.jdt.internal.ui.javaeditor.JavaEditor"/>
7863+
</with>
7864+
</and>
7865+
</enabledWhen>
7866+
</stickyLinesProvider>
7867+
</extension>
78547868
</plugin>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Red Hat Inc. and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* Red Hat Inc. - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.jdt.internal.ui.javaeditor;
15+
16+
import java.util.LinkedList;
17+
import java.util.List;
18+
import java.util.regex.Matcher;
19+
import java.util.regex.Pattern;
20+
21+
import org.eclipse.swt.custom.StyledText;
22+
23+
import org.eclipse.jface.text.ITextViewerExtension5;
24+
import org.eclipse.jface.text.source.ISourceViewer;
25+
26+
import org.eclipse.ui.IEditorInput;
27+
import org.eclipse.ui.IEditorPart;
28+
29+
import org.eclipse.ui.texteditor.stickyscroll.IStickyLine;
30+
import org.eclipse.ui.texteditor.stickyscroll.IStickyLinesProvider;
31+
import org.eclipse.ui.texteditor.stickyscroll.StickyLine;
32+
33+
import org.eclipse.jdt.core.ICompilationUnit;
34+
import org.eclipse.jdt.core.IJavaElement;
35+
import org.eclipse.jdt.core.ITypeRoot;
36+
import org.eclipse.jdt.core.JavaModelException;
37+
import org.eclipse.jdt.core.WorkingCopyOwner;
38+
import org.eclipse.jdt.core.dom.ASTNode;
39+
import org.eclipse.jdt.core.dom.ASTParser;
40+
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
41+
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
42+
import org.eclipse.jdt.core.dom.CompilationUnit;
43+
import org.eclipse.jdt.core.dom.DoStatement;
44+
import org.eclipse.jdt.core.dom.EnhancedForStatement;
45+
import org.eclipse.jdt.core.dom.ForStatement;
46+
import org.eclipse.jdt.core.dom.IfStatement;
47+
import org.eclipse.jdt.core.dom.LambdaExpression;
48+
import org.eclipse.jdt.core.dom.MethodDeclaration;
49+
import org.eclipse.jdt.core.dom.Modifier;
50+
import org.eclipse.jdt.core.dom.ModuleDeclaration;
51+
import org.eclipse.jdt.core.dom.RecordDeclaration;
52+
import org.eclipse.jdt.core.dom.SimpleName;
53+
import org.eclipse.jdt.core.dom.Statement;
54+
import org.eclipse.jdt.core.dom.SwitchExpression;
55+
import org.eclipse.jdt.core.dom.SwitchStatement;
56+
import org.eclipse.jdt.core.dom.TryStatement;
57+
import org.eclipse.jdt.core.dom.WhileStatement;
58+
59+
import org.eclipse.jdt.internal.corext.dom.IASTSharedValues;
60+
import org.eclipse.jdt.internal.corext.refactoring.structure.ASTNodeSearchUtil;
61+
62+
import org.eclipse.jdt.ui.JavaUI;
63+
64+
65+
public class JavaStickyLinesProvider implements IStickyLinesProvider {
66+
67+
private final static int IGNORE_LINE_INDENTATION= -1;
68+
private final static Pattern ELSE_PATTERN= Pattern.compile("else[\\s,{]"); //$NON-NLS-1$
69+
private final static Pattern DO_PATTERN= Pattern.compile("do[\\s,{]"); //$NON-NLS-1$
70+
private final static Pattern WHILE_PATTERN= Pattern.compile("while[\\s,{]"); //$NON-NLS-1$
71+
private final static Pattern TRY_PATTERN= Pattern.compile("try[\\s,{]"); //$NON-NLS-1$
72+
private final static Pattern NEW_PATTERN= Pattern.compile("new[\\s,{]"); //$NON-NLS-1$
73+
74+
@Override
75+
public List<IStickyLine> getStickyLines(ISourceViewer sourceViewer, int lineNumber, StickyLinesProperties properties) {
76+
final LinkedList<IStickyLine> stickyLines= new LinkedList<>();
77+
JavaEditor javaEditor= (JavaEditor) properties.editor();
78+
StyledText textWidget= sourceViewer.getTextWidget();
79+
ICompilationUnit unit= null;
80+
int textWidgetLineNumber= mapLineNumberToWidget(sourceViewer, lineNumber);
81+
// int originalWidgetLineNumber= textWidgetLineNumber;
82+
int startIndentation= 0;
83+
String line= textWidget.getLine(textWidgetLineNumber);
84+
System.out.println("line is " + line); //$NON-NLS-1$
85+
try {
86+
startIndentation= getIndentation(line);
87+
while (startIndentation == IGNORE_LINE_INDENTATION) {
88+
textWidgetLineNumber--;
89+
if (textWidgetLineNumber <= 0) {
90+
break;
91+
}
92+
line= textWidget.getLine(textWidgetLineNumber);
93+
System.out.println("replacement line is " + line); //$NON-NLS-1$
94+
startIndentation= getIndentation(line);
95+
}
96+
} catch (IllegalArgumentException e) {
97+
stickyLines.clear();
98+
}
99+
100+
if (textWidgetLineNumber > 0) {
101+
ITypeRoot typeRoot= getJavaInput(javaEditor);
102+
// System.out.println("line is " + line); //$NON-NLS-1$
103+
// System.out.println("line2 is " + textWidget.getLine(textWidgetLineNumber)); //$NON-NLS-1$
104+
// System.out.println("line number is " + lineNumber); //$NON-NLS-1$
105+
// System.out.println("textWidgetLineNumber is " + textWidgetLineNumber); //$NON-NLS-1$
106+
107+
ASTNode node= null;
108+
if (typeRoot != null) {
109+
WorkingCopyOwner workingCopyOwner= new WorkingCopyOwner() {
110+
};
111+
try {
112+
unit= typeRoot.getWorkingCopy(workingCopyOwner, null);
113+
if (unit != null) {
114+
CompilationUnit cu= convertICompilationUnitToCompilationUnit(unit);
115+
// int originalPosition= cu.getPosition(originalWidgetLineNumber + 1, 0);
116+
// System.out.println(textWidgetLineNumber + " " + startIndentation); //$NON-NLS-1$
117+
int mapNumber= mapWidgetToLineNumber(sourceViewer, textWidgetLineNumber);
118+
System.out.println("map number is " + mapNumber); //$NON-NLS-1$
119+
System.out.println("start indentation is " + startIndentation); //$NON-NLS-1$
120+
System.out.println("line is " + line); //$NON-NLS-1$
121+
System.out.println("cu position is " + cu.getPosition(mapNumber+1,startIndentation)); //$NON-NLS-1$
122+
node= ASTNodeSearchUtil.getAstNode(cu, cu.getPosition(mapWidgetToLineNumber(sourceViewer, textWidgetLineNumber+1), startIndentation), line.length() - startIndentation);
123+
// System.out.println("node found is " + (node != null ? node.getClass() : node)); //$NON-NLS-1$
124+
while (node == null && textWidgetLineNumber > 0) {
125+
line= textWidget.getLine(--textWidgetLineNumber);
126+
System.out.println("node null line is " + line); //$NON-NLS-1$
127+
startIndentation= getIndentation(line);
128+
while (startIndentation == IGNORE_LINE_INDENTATION && textWidgetLineNumber > 0) {
129+
line= textWidget.getLine(--textWidgetLineNumber);
130+
System.out.println("node null replacement line is " + line); //$NON-NLS-1$
131+
startIndentation= getIndentation(line);
132+
}
133+
if (textWidgetLineNumber > 0) {
134+
System.out.println("line is " + line); //$NON-NLS-1$
135+
System.out.println("mapped line is " + mapWidgetToLineNumber(sourceViewer, textWidgetLineNumber+1)); //$NON-NLS-1$
136+
System.out.println("start indentation is " + startIndentation); //$NON-NLS-1$
137+
System.out.println("line length is " + line.length()); //$NON-NLS-1$
138+
System.out.println("position is " + cu.getPosition(textWidgetLineNumber + 1, startIndentation)); //$NON-NLS-1$
139+
int position= cu.getPosition(textWidgetLineNumber + 1, startIndentation);
140+
if (position >= 0) {
141+
node= ASTNodeSearchUtil.getAstNode(cu, cu.getPosition(textWidgetLineNumber+1, startIndentation), line.length() - startIndentation);
142+
}
143+
}
144+
}
145+
if (node != null) {
146+
// node= node.getParent();
147+
System.out.println("node is " + node.getClass()); //$NON-NLS-1$
148+
System.out.println("node start position is " + node.getStartPosition()); //$NON-NLS-1$
149+
boolean addStickyLine= false;
150+
int nodeLineNumber= 0;
151+
while (node != null) {
152+
addStickyLine= false;
153+
switch (node.getNodeType()) {
154+
case ASTNode.ANNOTATION_TYPE_DECLARATION:
155+
case ASTNode.TYPE_DECLARATION:
156+
case ASTNode.ENUM_DECLARATION:
157+
addStickyLine= true;
158+
ASTNode name= ((AbstractTypeDeclaration)node).getName();
159+
System.out.println(((SimpleName)name).getFullyQualifiedName());
160+
nodeLineNumber= cu.getLineNumber(name.getStartPosition());
161+
break;
162+
case ASTNode.METHOD_DECLARATION:
163+
addStickyLine= true;
164+
ASTNode methodName= ((MethodDeclaration)node).getName();
165+
nodeLineNumber= cu.getLineNumber(methodName.getStartPosition());
166+
break;
167+
case ASTNode.RECORD_DECLARATION:
168+
addStickyLine= true;
169+
ASTNode recordName= ((RecordDeclaration)node).getName();
170+
nodeLineNumber= cu.getLineNumber(recordName.getStartPosition());
171+
break;
172+
case ASTNode.MODULE_DECLARATION:
173+
addStickyLine= true;
174+
ASTNode moduleName= ((ModuleDeclaration)node).getName();
175+
nodeLineNumber= cu.getLineNumber(moduleName.getStartPosition());
176+
break;
177+
case ASTNode.LAMBDA_EXPRESSION:
178+
addStickyLine= true;
179+
ASTNode lambdaBody= ((LambdaExpression)node).getBody();
180+
nodeLineNumber= cu.getLineNumber(lambdaBody.getStartPosition());
181+
break;
182+
case ASTNode.IF_STATEMENT:
183+
addStickyLine= true;
184+
IfStatement ifStmt= (IfStatement)node;
185+
ASTNode ifExpression= ifStmt.getExpression();
186+
nodeLineNumber= cu.getLineNumber(ifExpression.getStartPosition());
187+
System.out.println("if stmt is " + textWidget.getLine(nodeLineNumber - 1)); //$NON-NLS-1$
188+
Statement elseStmt= ifStmt.getElseStatement();
189+
if (elseStmt != null) {
190+
int elseLine= cu.getLineNumber(elseStmt.getStartPosition());
191+
if (elseLine <= textWidgetLineNumber + 1) {
192+
Pattern p= ELSE_PATTERN;
193+
nodeLineNumber= elseLine;
194+
String stmtLine= textWidget.getLine(nodeLineNumber - 1);
195+
System.out.println("else line is " + stmtLine); //$NON-NLS-1$
196+
Matcher m= p.matcher(stmtLine);
197+
while (!m.find() && nodeLineNumber > 1) {
198+
nodeLineNumber--;
199+
stmtLine= textWidget.getLine(nodeLineNumber - 1);
200+
System.out.println("next else line is " + stmtLine); //$NON-NLS-1$
201+
m= p.matcher(stmtLine);
202+
}
203+
node= node.getParent();
204+
}
205+
}
206+
while (node.getLocationInParent() == IfStatement.ELSE_STATEMENT_PROPERTY) {
207+
node= node.getParent();
208+
}
209+
break;
210+
case ASTNode.FOR_STATEMENT:
211+
addStickyLine= true;
212+
ASTNode forExpression= ((ForStatement)node).getExpression();
213+
nodeLineNumber= cu.getLineNumber(forExpression.getStartPosition());
214+
break;
215+
case ASTNode.ENHANCED_FOR_STATEMENT:
216+
addStickyLine= true;
217+
ASTNode enhancedForExpression= ((EnhancedForStatement)node).getExpression();
218+
nodeLineNumber= cu.getLineNumber(enhancedForExpression.getStartPosition());
219+
break;
220+
case ASTNode.SWITCH_EXPRESSION:
221+
addStickyLine= true;
222+
ASTNode switchExpExpression= ((SwitchExpression)node).getExpression();
223+
nodeLineNumber= cu.getLineNumber(switchExpExpression.getStartPosition());
224+
break;
225+
case ASTNode.SWITCH_STATEMENT:
226+
addStickyLine= true;
227+
ASTNode switchStmtExpression= ((SwitchStatement)node).getExpression();
228+
nodeLineNumber= cu.getLineNumber(switchStmtExpression.getStartPosition());
229+
break;
230+
case ASTNode.WHILE_STATEMENT:
231+
case ASTNode.DO_STATEMENT:
232+
case ASTNode.TRY_STATEMENT:
233+
case ASTNode.ANONYMOUS_CLASS_DECLARATION:
234+
addStickyLine= true;
235+
String checkString= ""; //$NON-NLS-1$
236+
ASTNode bodyProperty= null;
237+
Pattern pattern= null;
238+
if (node.getNodeType() == ASTNode.DO_STATEMENT) {
239+
bodyProperty= ((DoStatement)node).getBody();
240+
pattern= DO_PATTERN;
241+
} else if (node.getNodeType() == ASTNode.WHILE_STATEMENT) {
242+
bodyProperty= ((WhileStatement)node).getBody();
243+
pattern= WHILE_PATTERN;
244+
} else if (node.getNodeType() == ASTNode.TRY_STATEMENT) {
245+
bodyProperty= ((TryStatement)node).getBody();
246+
pattern= TRY_PATTERN;
247+
} else if (node.getNodeType() == ASTNode.ANONYMOUS_CLASS_DECLARATION) {
248+
bodyProperty= (ASTNode) ((AnonymousClassDeclaration)node).bodyDeclarations().get(0);
249+
pattern= NEW_PATTERN;
250+
}
251+
if (bodyProperty != null) {
252+
nodeLineNumber= cu.getLineNumber(bodyProperty.getStartPosition());
253+
String stmtLine= textWidget.getLine(nodeLineNumber - 1);
254+
Matcher m= pattern.matcher(stmtLine);
255+
while (!m.find() && nodeLineNumber > 1) {
256+
nodeLineNumber--;
257+
stmtLine= textWidget.getLine(nodeLineNumber - 1);
258+
m= pattern.matcher(stmtLine);
259+
}
260+
}
261+
break;
262+
case ASTNode.SWITCH_CASE:
263+
case ASTNode.CASE_DEFAULT_EXPRESSION:
264+
case ASTNode.CATCH_CLAUSE:
265+
addStickyLine= true;
266+
nodeLineNumber= cu.getLineNumber(node.getStartPosition());
267+
break;
268+
default:
269+
break;
270+
}
271+
if (addStickyLine) {
272+
System.out.println("adding sticky line"); //$NON-NLS-1$
273+
System.out.println("line number is " + mapLineNumberToWidget(sourceViewer, nodeLineNumber - 1)); //$NON-NLS-1$
274+
stickyLines.addFirst(new StickyLine(mapLineNumberToWidget(sourceViewer, nodeLineNumber - 1), sourceViewer));
275+
}
276+
if (node.getNodeType() == ASTNode.MODIFIER) {
277+
Modifier modifier= (Modifier)node;
278+
startIndentation+= modifier.getLength();
279+
node= ASTNodeSearchUtil.getAstNode(cu, cu.getPosition(textWidgetLineNumber+1, startIndentation), line.length() - startIndentation);
280+
} else {
281+
node= node.getParent();
282+
}
283+
System.out.println("parent node is " + (node != null ? node.getClass() : node)); //$NON-NLS-1$
284+
}
285+
}
286+
}
287+
if (unit != null) {
288+
unit.discardWorkingCopy();
289+
}
290+
} catch (JavaModelException e) {
291+
// do nothing
292+
}
293+
}
294+
}
295+
return stickyLines;
296+
}
297+
298+
public static ITypeRoot getJavaInput(IEditorPart part) {
299+
IEditorInput editorInput= part.getEditorInput();
300+
if (editorInput != null) {
301+
IJavaElement input= JavaUI.getEditorInputJavaElement(editorInput);
302+
if (input instanceof ITypeRoot) {
303+
return (ITypeRoot) input;
304+
}
305+
}
306+
return null;
307+
}
308+
309+
private int mapLineNumberToWidget(ISourceViewer sourceViewer, int line) {
310+
if (sourceViewer instanceof ITextViewerExtension5 extension) {
311+
return extension.modelLine2WidgetLine(line);
312+
}
313+
return line;
314+
}
315+
316+
private int mapWidgetToLineNumber(ISourceViewer sourceViewer, int line) {
317+
if (sourceViewer instanceof ITextViewerExtension5 extension) {
318+
return extension.widgetLine2ModelLine(line);
319+
}
320+
return line;
321+
}
322+
323+
private int getIndentation(String line) {
324+
if (line == null || line.isBlank()) {
325+
return IGNORE_LINE_INDENTATION;
326+
}
327+
return line.length() - line.stripLeading().length();
328+
}
329+
330+
private static CompilationUnit convertICompilationUnitToCompilationUnit(ICompilationUnit compilationUnit) {
331+
ASTParser parser= ASTParser.newParser(IASTSharedValues.SHARED_AST_LEVEL);
332+
parser.setKind(ASTParser.K_COMPILATION_UNIT);
333+
parser.setSource(compilationUnit);
334+
parser.setResolveBindings(false);
335+
return (CompilationUnit) parser.createAST(null);
336+
}
337+
338+
}

0 commit comments

Comments
 (0)