Skip to content

Commit 14e0702

Browse files
authored
Merge pull request #678 from Efratror/LSP-Feature/Declaration_Support
LSP feature/declaration support
2 parents ae80913 + d8a163b commit 14e0702

File tree

5 files changed

+251
-3
lines changed

5 files changed

+251
-3
lines changed

java/src/processing/mode/java/SketchInterval.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ public class SketchInterval {
1414
this.stopPdeOffset = stopPdeOffset;
1515
}
1616

17-
final int tabIndex;
18-
final int startTabOffset;
19-
final int stopTabOffset;
17+
public final int tabIndex;
18+
public final int startTabOffset;
19+
public final int stopTabOffset;
2020

2121
final int startPdeOffset;
2222
final int stopPdeOffset;

java/src/processing/mode/java/lsp/PdeAdapter.java

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,29 @@
66
import java.util.Arrays;
77
import java.util.Collections;
88
import java.util.concurrent.CompletableFuture;
9+
import java.util.concurrent.ExecutionException;
910
import java.util.HashSet;
1011
import java.util.List;
1112
import java.util.Map;
1213
import java.util.Optional;
1314
import java.util.Set;
1415
import java.util.stream.Collectors;
1516
import java.util.stream.IntStream;
17+
1618
import org.eclipse.lsp4j.CompletionItem;
1719
import org.eclipse.lsp4j.CompletionItemKind;
1820
import org.eclipse.lsp4j.Diagnostic;
1921
import org.eclipse.lsp4j.DiagnosticSeverity;
2022
import org.eclipse.lsp4j.InsertTextFormat;
23+
import org.eclipse.lsp4j.Location;
2124
import org.eclipse.lsp4j.jsonrpc.CompletableFutures;
2225
import org.eclipse.lsp4j.Position;
2326
import org.eclipse.lsp4j.PublishDiagnosticsParams;
2427
import org.eclipse.lsp4j.Range;
2528
import org.eclipse.lsp4j.services.LanguageClient;
2629
import org.eclipse.lsp4j.TextEdit;
2730
import org.jsoup.Jsoup;
31+
2832
import processing.app.Base;
2933
import processing.app.contrib.ModeContribution;
3034
import processing.app.Platform;
@@ -41,6 +45,7 @@
4145
import processing.mode.java.PreprocService;
4246
import processing.mode.java.PreprocSketch;
4347

48+
import static java.util.Arrays.copyOfRange;
4449

4550
class PdeAdapter {
4651
File rootPath;
@@ -54,6 +59,7 @@ class PdeAdapter {
5459
CompletableFuture<PreprocSketch> cps;
5560
CompletionGenerator suggestionGenerator;
5661
Set<URI> prevDiagnosticReportUris = new HashSet<>();
62+
PreprocSketch ps;
5763

5864

5965
PdeAdapter(File rootPath, LanguageClient client) {
@@ -104,6 +110,41 @@ static Offset toLineCol(String s, int offset) {
104110
return new Offset(line, col);
105111
}
106112

113+
114+
/**
115+
* Converts a tabOffset to a position within a tab
116+
* @param program current code(text) from a tab
117+
* @param tabOffset character offset inside a tab
118+
* @return Position(line and col) within the tab
119+
*/
120+
static Position toPosition(String program, int tabOffset){
121+
Offset offset = toLineCol(program, tabOffset);
122+
return new Position(offset.line, offset.col-1);
123+
}
124+
125+
126+
/**
127+
* Converts a range (start to end offset) to a location.
128+
* @param program current code(text) from a tab
129+
* @param startTabOffset starting character offset inside a tab
130+
* @param stopTabOffset ending character offset inside a tab
131+
* @param uri uri from a tab
132+
* @return Range inside a file
133+
*/
134+
static Location toLocation(
135+
String program,
136+
int startTabOffset,
137+
int stopTabOffset,
138+
URI uri
139+
){
140+
Position startPos = toPosition(program, startTabOffset);
141+
Position stopPos = toPosition(program, stopTabOffset);
142+
143+
Range range = new Range(startPos, stopPos);
144+
return new Location(uri.toString(), range);
145+
}
146+
147+
107148
static void init() {
108149
Base.setCommandLine();
109150
Platform.init();
@@ -116,6 +157,10 @@ void notifySketchChanged() {
116157
preprocService.notifySketchChanged();
117158
errorChecker.notifySketchChanged();
118159
preprocService.whenDone(cps::complete);
160+
try { ps = cps.get();
161+
} catch (InterruptedException | ExecutionException e) {
162+
throw new RuntimeException(e);
163+
}
119164
}
120165

121166
Optional<SketchCode> findCodeByUri(URI uri) {
@@ -126,6 +171,59 @@ Optional<SketchCode> findCodeByUri(URI uri) {
126171
);
127172
}
128173

174+
175+
/**
176+
* Looks for the tab number for a given text
177+
* @param code text(code) from a tab
178+
* @return tabIndex where the code belongs to, or empty
179+
*/
180+
public Optional<Integer> findTabIndex(SketchCode code){
181+
int tabsCount = sketch.getCodeCount();
182+
java.util.OptionalInt optionalTabIndex;
183+
optionalTabIndex = IntStream.range(0, tabsCount)
184+
.filter(i -> sketch.getCode(i).equals(code))
185+
.findFirst();
186+
187+
if(optionalTabIndex.isEmpty()){
188+
return Optional.empty();
189+
}
190+
191+
return Optional.of(optionalTabIndex.getAsInt());
192+
}
193+
194+
195+
/**
196+
* Looks for the javaOffset, this offset is the character position inside the
197+
* full java file. The position can be used by the AST to find a node.
198+
* @param uri uri of the file(tab) where to look
199+
* @param line line number
200+
* @param col column number
201+
* @return character offset within the full AST
202+
*/
203+
public Optional<Integer> findJavaOffset(URI uri, int line, int col){
204+
205+
Optional<SketchCode> optionalCode = findCodeByUri(uri);
206+
if(optionalCode.isEmpty()){
207+
System.out.println("couldn't find sketch code");
208+
return Optional.empty();
209+
}
210+
SketchCode code = optionalCode.get();
211+
212+
Optional<Integer> optionalTabIndex = findTabIndex(code);
213+
if (optionalTabIndex.isEmpty()){
214+
System.out.println("couldn't find tab index");
215+
return Optional.empty();
216+
}
217+
int tabIndex = optionalTabIndex.get();
218+
219+
String[] codeLines = copyOfRange(code.getProgram().split("\n"), 0,line);
220+
String codeString = String.join("\n", codeLines);
221+
int tabOffset = codeString.length() + col;
222+
223+
return Optional.of(ps.tabOffsetToJavaOffset(tabIndex, tabOffset));
224+
}
225+
226+
129227
void updateProblems(List<Problem> problems) {
130228
Map<URI, List<Diagnostic>> dias = problems.stream()
131229
.map(prob -> {

java/src/processing/mode/java/lsp/PdeLanguageServer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
7777
completionOptions.setTriggerCharacters(List.of("."));
7878
capabilities.setCompletionProvider(completionOptions);
7979
capabilities.setDocumentFormattingProvider(true);
80+
capabilities.setDeclarationProvider(true);
8081
var result = new InitializeResult(capabilities);
8182
return CompletableFuture.completedFuture(result);
8283
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package processing.mode.java.lsp;
2+
3+
import java.net.URI;
4+
import java.util.ArrayList;
5+
import java.util.Collections;
6+
import java.util.List;
7+
8+
import org.eclipse.jdt.core.dom.ASTNode;
9+
import org.eclipse.jdt.core.dom.IBinding;
10+
import org.eclipse.jdt.core.dom.MethodDeclaration;
11+
import org.eclipse.jdt.core.dom.SimpleName;
12+
import org.eclipse.jdt.core.dom.TypeDeclaration;
13+
import org.eclipse.jdt.core.dom.VariableDeclaration;
14+
15+
import org.eclipse.lsp4j.Location;
16+
17+
import processing.app.SketchCode;
18+
import processing.mode.java.PreprocSketch;
19+
import processing.mode.java.SketchInterval;
20+
21+
import static processing.mode.java.ASTUtils.getSimpleNameAt;
22+
import static processing.mode.java.ASTUtils.resolveBinding;
23+
24+
25+
public class PdeSymbolFinder {
26+
27+
/**
28+
* searches a declaration node for a provided character offset
29+
*
30+
* @param ps processed sketch, for AST-nodes and sketch
31+
* @param javaOffset character offset for the node we want to look up
32+
* @return Location list if a declaration is found, else an empty list.
33+
*/
34+
static public List<? extends Location> searchDeclaration(PreprocSketch ps, int javaOffset) {
35+
ASTNode root = ps.compilationUnit;
36+
37+
SimpleName simpleName = getSimpleNameAt(root, javaOffset, javaOffset);
38+
if (simpleName == null) {
39+
System.out.println("no simple name found at location");
40+
return Collections.emptyList();
41+
}
42+
43+
IBinding binding = resolveBinding(simpleName);
44+
if (binding == null) {
45+
System.out.println("binding not resolved");
46+
return Collections.emptyList();
47+
}
48+
49+
String key = binding.getKey();
50+
ASTNode declarationNode = ps.compilationUnit.findDeclaringNode(key);
51+
if (declarationNode == null) {
52+
System.out.println("declaration not found");
53+
return Collections.emptyList();
54+
}
55+
56+
SimpleName declarationName = switch (binding.getKind()) {
57+
case IBinding.TYPE -> ((TypeDeclaration) declarationNode).getName();
58+
case IBinding.METHOD -> ((MethodDeclaration) declarationNode).getName();
59+
case IBinding.VARIABLE ->
60+
((VariableDeclaration) declarationNode).getName();
61+
default -> null;
62+
};
63+
64+
if (declarationName == null) {
65+
System.out.println("declaration name not found " + declarationNode);
66+
return Collections.emptyList();
67+
}
68+
69+
if (!declarationName.equals(simpleName)) {
70+
System.out.println("found declaration, name: " + declarationName);
71+
} else {
72+
System.out.println("already at declaration");
73+
}
74+
75+
SketchInterval si = ps.mapJavaToSketch(declarationName);
76+
if (si == SketchInterval.BEFORE_START) {
77+
System.out.println("declaration is outside of the sketch");
78+
return Collections.emptyList();
79+
}
80+
81+
//Create a location for the found declaration
82+
SketchCode code = ps.sketch.getCode(si.tabIndex);
83+
String program = code.getProgram();
84+
URI uri = PdeAdapter.pathToUri(code.getFile());
85+
86+
Location location =
87+
PdeAdapter.toLocation(program, si.startTabOffset, si.stopTabOffset, uri);
88+
89+
List<Location> declarationList = new ArrayList<>();
90+
declarationList.add(location);
91+
92+
return declarationList;
93+
}
94+
}

java/src/processing/mode/java/lsp/PdeTextDocumentService.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,25 @@
88
import org.eclipse.lsp4j.CompletionItem;
99
import org.eclipse.lsp4j.CompletionList;
1010
import org.eclipse.lsp4j.jsonrpc.CompletableFutures;
11+
import org.eclipse.lsp4j.DeclarationParams;
1112
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
1213
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
1314
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
1415
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
1516
import org.eclipse.lsp4j.DocumentFormattingParams;
1617
import org.eclipse.lsp4j.TextEdit;
18+
import org.eclipse.lsp4j.Location;
19+
import org.eclipse.lsp4j.LocationLink;
1720

1821
import java.util.Collections;
1922
import java.net.URI;
23+
import java.util.Optional;
24+
25+
import processing.mode.java.PreprocSketch;
26+
27+
import static org.eclipse.lsp4j.jsonrpc.CompletableFutures.computeAsync;
28+
import static org.eclipse.lsp4j.jsonrpc.messages.Either.forLeft;
29+
2030

2131
class PdeTextDocumentService implements TextDocumentService {
2232
PdeLanguageServer pls;
@@ -82,4 +92,49 @@ public CompletableFuture<List<? extends TextEdit>> formatting(DocumentFormatting
8292
})
8393
.orElse(CompletableFuture.completedFuture(Collections.emptyList()));
8494
}
95+
96+
97+
@Override
98+
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> declaration(DeclarationParams params) {
99+
System.out.println("searching for declaration");
100+
101+
java.net.URI uri = java.net.URI.create(params.getTextDocument().getUri());
102+
int lineNumber = params.getPosition().getLine();
103+
int colNumber = params.getPosition().getCharacter();
104+
105+
Optional<PdeAdapter> adapterOptional =
106+
pls.getAdapter(uri);
107+
108+
if(adapterOptional.isEmpty()){
109+
System.out.println("pde adapter not found");
110+
return CompletableFutures.computeAsync(_x -> Either
111+
.forLeft(Collections.emptyList()));
112+
}
113+
114+
PdeAdapter adapter = adapterOptional.get();
115+
PreprocSketch preprocSketch = adapter.ps;
116+
Optional<Integer> optionalJavaOffset = adapter.findJavaOffset(uri,
117+
lineNumber, colNumber);
118+
119+
if(optionalJavaOffset.isEmpty()){
120+
System.out.println("javaOffset not found");
121+
return CompletableFutures.computeAsync(_x -> Either
122+
.forLeft(Collections.emptyList()));
123+
}
124+
int javaOffset = optionalJavaOffset.get();
125+
126+
List<? extends Location> locations;
127+
locations = PdeSymbolFinder.searchDeclaration(preprocSketch, javaOffset);
128+
129+
Optional<CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>>>
130+
OptCompFutEit = Optional.ofNullable(CompletableFutures
131+
.computeAsync(_x -> locations))
132+
.map(_x -> _x.thenApply(Either::forLeft)
133+
);
134+
135+
return OptCompFutEit.orElse(
136+
computeAsync(_x -> forLeft(Collections.emptyList()))
137+
);
138+
}
139+
85140
}

0 commit comments

Comments
 (0)