Skip to content

Commit

Permalink
Merge pull request #678 from Efratror/LSP-Feature/Declaration_Support
Browse files Browse the repository at this point in the history
LSP feature/declaration support
  • Loading branch information
benfry authored Feb 27, 2023
2 parents ae80913 + d8a163b commit 14e0702
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 3 deletions.
6 changes: 3 additions & 3 deletions java/src/processing/mode/java/SketchInterval.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ public class SketchInterval {
this.stopPdeOffset = stopPdeOffset;
}

final int tabIndex;
final int startTabOffset;
final int stopTabOffset;
public final int tabIndex;
public final int startTabOffset;
public final int stopTabOffset;

final int startPdeOffset;
final int stopPdeOffset;
Expand Down
98 changes: 98 additions & 0 deletions java/src/processing/mode/java/lsp/PdeAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,29 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DiagnosticSeverity;
import org.eclipse.lsp4j.InsertTextFormat;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.jsonrpc.CompletableFutures;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.TextEdit;
import org.jsoup.Jsoup;

import processing.app.Base;
import processing.app.contrib.ModeContribution;
import processing.app.Platform;
Expand All @@ -41,6 +45,7 @@
import processing.mode.java.PreprocService;
import processing.mode.java.PreprocSketch;

import static java.util.Arrays.copyOfRange;

class PdeAdapter {
File rootPath;
Expand All @@ -54,6 +59,7 @@ class PdeAdapter {
CompletableFuture<PreprocSketch> cps;
CompletionGenerator suggestionGenerator;
Set<URI> prevDiagnosticReportUris = new HashSet<>();
PreprocSketch ps;


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


/**
* Converts a tabOffset to a position within a tab
* @param program current code(text) from a tab
* @param tabOffset character offset inside a tab
* @return Position(line and col) within the tab
*/
static Position toPosition(String program, int tabOffset){
Offset offset = toLineCol(program, tabOffset);
return new Position(offset.line, offset.col-1);
}


/**
* Converts a range (start to end offset) to a location.
* @param program current code(text) from a tab
* @param startTabOffset starting character offset inside a tab
* @param stopTabOffset ending character offset inside a tab
* @param uri uri from a tab
* @return Range inside a file
*/
static Location toLocation(
String program,
int startTabOffset,
int stopTabOffset,
URI uri
){
Position startPos = toPosition(program, startTabOffset);
Position stopPos = toPosition(program, stopTabOffset);

Range range = new Range(startPos, stopPos);
return new Location(uri.toString(), range);
}


static void init() {
Base.setCommandLine();
Platform.init();
Expand All @@ -116,6 +157,10 @@ void notifySketchChanged() {
preprocService.notifySketchChanged();
errorChecker.notifySketchChanged();
preprocService.whenDone(cps::complete);
try { ps = cps.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}

Optional<SketchCode> findCodeByUri(URI uri) {
Expand All @@ -126,6 +171,59 @@ Optional<SketchCode> findCodeByUri(URI uri) {
);
}


/**
* Looks for the tab number for a given text
* @param code text(code) from a tab
* @return tabIndex where the code belongs to, or empty
*/
public Optional<Integer> findTabIndex(SketchCode code){
int tabsCount = sketch.getCodeCount();
java.util.OptionalInt optionalTabIndex;
optionalTabIndex = IntStream.range(0, tabsCount)
.filter(i -> sketch.getCode(i).equals(code))
.findFirst();

if(optionalTabIndex.isEmpty()){
return Optional.empty();
}

return Optional.of(optionalTabIndex.getAsInt());
}


/**
* Looks for the javaOffset, this offset is the character position inside the
* full java file. The position can be used by the AST to find a node.
* @param uri uri of the file(tab) where to look
* @param line line number
* @param col column number
* @return character offset within the full AST
*/
public Optional<Integer> findJavaOffset(URI uri, int line, int col){

Optional<SketchCode> optionalCode = findCodeByUri(uri);
if(optionalCode.isEmpty()){
System.out.println("couldn't find sketch code");
return Optional.empty();
}
SketchCode code = optionalCode.get();

Optional<Integer> optionalTabIndex = findTabIndex(code);
if (optionalTabIndex.isEmpty()){
System.out.println("couldn't find tab index");
return Optional.empty();
}
int tabIndex = optionalTabIndex.get();

String[] codeLines = copyOfRange(code.getProgram().split("\n"), 0,line);
String codeString = String.join("\n", codeLines);
int tabOffset = codeString.length() + col;

return Optional.of(ps.tabOffsetToJavaOffset(tabIndex, tabOffset));
}


void updateProblems(List<Problem> problems) {
Map<URI, List<Diagnostic>> dias = problems.stream()
.map(prob -> {
Expand Down
1 change: 1 addition & 0 deletions java/src/processing/mode/java/lsp/PdeLanguageServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
completionOptions.setTriggerCharacters(List.of("."));
capabilities.setCompletionProvider(completionOptions);
capabilities.setDocumentFormattingProvider(true);
capabilities.setDeclarationProvider(true);
var result = new InitializeResult(capabilities);
return CompletableFuture.completedFuture(result);
}
Expand Down
94 changes: 94 additions & 0 deletions java/src/processing/mode/java/lsp/PdeSymbolFinder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package processing.mode.java.lsp;

import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclaration;

import org.eclipse.lsp4j.Location;

import processing.app.SketchCode;
import processing.mode.java.PreprocSketch;
import processing.mode.java.SketchInterval;

import static processing.mode.java.ASTUtils.getSimpleNameAt;
import static processing.mode.java.ASTUtils.resolveBinding;


public class PdeSymbolFinder {

/**
* searches a declaration node for a provided character offset
*
* @param ps processed sketch, for AST-nodes and sketch
* @param javaOffset character offset for the node we want to look up
* @return Location list if a declaration is found, else an empty list.
*/
static public List<? extends Location> searchDeclaration(PreprocSketch ps, int javaOffset) {
ASTNode root = ps.compilationUnit;

SimpleName simpleName = getSimpleNameAt(root, javaOffset, javaOffset);
if (simpleName == null) {
System.out.println("no simple name found at location");
return Collections.emptyList();
}

IBinding binding = resolveBinding(simpleName);
if (binding == null) {
System.out.println("binding not resolved");
return Collections.emptyList();
}

String key = binding.getKey();
ASTNode declarationNode = ps.compilationUnit.findDeclaringNode(key);
if (declarationNode == null) {
System.out.println("declaration not found");
return Collections.emptyList();
}

SimpleName declarationName = switch (binding.getKind()) {
case IBinding.TYPE -> ((TypeDeclaration) declarationNode).getName();
case IBinding.METHOD -> ((MethodDeclaration) declarationNode).getName();
case IBinding.VARIABLE ->
((VariableDeclaration) declarationNode).getName();
default -> null;
};

if (declarationName == null) {
System.out.println("declaration name not found " + declarationNode);
return Collections.emptyList();
}

if (!declarationName.equals(simpleName)) {
System.out.println("found declaration, name: " + declarationName);
} else {
System.out.println("already at declaration");
}

SketchInterval si = ps.mapJavaToSketch(declarationName);
if (si == SketchInterval.BEFORE_START) {
System.out.println("declaration is outside of the sketch");
return Collections.emptyList();
}

//Create a location for the found declaration
SketchCode code = ps.sketch.getCode(si.tabIndex);
String program = code.getProgram();
URI uri = PdeAdapter.pathToUri(code.getFile());

Location location =
PdeAdapter.toLocation(program, si.startTabOffset, si.stopTabOffset, uri);

List<Location> declarationList = new ArrayList<>();
declarationList.add(location);

return declarationList;
}
}
55 changes: 55 additions & 0 deletions java/src/processing/mode/java/lsp/PdeTextDocumentService.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,25 @@
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.jsonrpc.CompletableFutures;
import org.eclipse.lsp4j.DeclarationParams;
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
import org.eclipse.lsp4j.DocumentFormattingParams;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.LocationLink;

import java.util.Collections;
import java.net.URI;
import java.util.Optional;

import processing.mode.java.PreprocSketch;

import static org.eclipse.lsp4j.jsonrpc.CompletableFutures.computeAsync;
import static org.eclipse.lsp4j.jsonrpc.messages.Either.forLeft;


class PdeTextDocumentService implements TextDocumentService {
PdeLanguageServer pls;
Expand Down Expand Up @@ -82,4 +92,49 @@ public CompletableFuture<List<? extends TextEdit>> formatting(DocumentFormatting
})
.orElse(CompletableFuture.completedFuture(Collections.emptyList()));
}


@Override
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> declaration(DeclarationParams params) {
System.out.println("searching for declaration");

java.net.URI uri = java.net.URI.create(params.getTextDocument().getUri());
int lineNumber = params.getPosition().getLine();
int colNumber = params.getPosition().getCharacter();

Optional<PdeAdapter> adapterOptional =
pls.getAdapter(uri);

if(adapterOptional.isEmpty()){
System.out.println("pde adapter not found");
return CompletableFutures.computeAsync(_x -> Either
.forLeft(Collections.emptyList()));
}

PdeAdapter adapter = adapterOptional.get();
PreprocSketch preprocSketch = adapter.ps;
Optional<Integer> optionalJavaOffset = adapter.findJavaOffset(uri,
lineNumber, colNumber);

if(optionalJavaOffset.isEmpty()){
System.out.println("javaOffset not found");
return CompletableFutures.computeAsync(_x -> Either
.forLeft(Collections.emptyList()));
}
int javaOffset = optionalJavaOffset.get();

List<? extends Location> locations;
locations = PdeSymbolFinder.searchDeclaration(preprocSketch, javaOffset);

Optional<CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>>>
OptCompFutEit = Optional.ofNullable(CompletableFutures
.computeAsync(_x -> locations))
.map(_x -> _x.thenApply(Either::forLeft)
);

return OptCompFutEit.orElse(
computeAsync(_x -> forLeft(Collections.emptyList()))
);
}

}

0 comments on commit 14e0702

Please sign in to comment.