use of lspserver.OberonFile.IdentifierReference in project OberonEmulator by schierlm.
the class Server method getTextDocumentService.
@Override
public TextDocumentService getTextDocumentService() {
return new TextDocumentService() {
@Override
public void didSave(DidSaveTextDocumentParams params) {
}
@Override
public void didOpen(DidOpenTextDocumentParams params) {
OberonFile of = fileForURI(params.getTextDocument().getUri());
if (of == null) {
of = new OberonFile(params.getTextDocument().getUri(), params.getTextDocument().getText());
} else {
of.setContent(params.getTextDocument().getText());
}
openFiles.put(params.getTextDocument().getUri(), of);
fileChanged(of);
}
@Override
public void didClose(DidCloseTextDocumentParams params) {
fileClosed(openFiles.remove(params.getTextDocument().getUri()));
}
@Override
public void didChange(DidChangeTextDocumentParams params) {
OberonFile of = openFiles.get(params.getTextDocument().getUri());
if (params.getContentChanges().size() != 1)
throw new IllegalArgumentException("Incremental changes not supported)");
of.setContent(params.getContentChanges().get(0).getText());
fileChanged(of);
}
@Override
public CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>> documentSymbol(DocumentSymbolParams params) {
OberonFile of = fileForURI(params.getTextDocument().getUri());
if (of == null)
return CompletableFuture.completedFuture(new ArrayList<>());
return of.waitWhenDirty(backgroundExecutor, f -> new ArrayList<>(f.getOutline()));
}
@Override
public CompletableFuture<SemanticTokens> semanticTokensFull(SemanticTokensParams params) {
OberonFile of = fileForURI(params.getTextDocument().getUri());
return of.waitWhenDirty(backgroundExecutor, f -> new SemanticTokens(IntStream.of(f.getSemanticTokens()).mapToObj(i -> i).collect(Collectors.toList())));
}
@Override
public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completion(CompletionParams position) {
OberonFile of = fileForURI(position.getTextDocument().getUri());
int pos = of.getRawPos(position.getPosition());
if (pos == -1)
return CompletableFuture.completedFuture(Either.forLeft(new ArrayList<>()));
String prefix = of.getContent().substring(0, pos);
CompletableFuture<Either<List<CompletionItem>, CompletionList>> result = new CompletableFuture<>();
backgroundExecutor.submit(() -> {
try {
List<CompletionItem> completions = bridge.complete(prefix);
result.complete(Either.forLeft(completions));
} catch (Throwable ex) {
ex.printStackTrace();
result.completeExceptionally(ex);
}
});
return result;
}
@Override
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> definition(DefinitionParams params) {
OberonFile of = fileForURI(params.getTextDocument().getUri());
return of.waitWhenDirty(backgroundExecutor, ar -> {
if (debug && params.getPosition().getLine() == 0 && params.getPosition().getCharacter() == 0) {
// validate all references in this file!
int[] errors = { 0 };
for (Identifier id : ar.getIdDefinitions().values()) {
if (id.getKind() == SymbolKind.Module) {
continue;
}
try {
findRangeToDelete(id, ar, new HashSet<Integer>(Arrays.asList(id.getEndPos())));
} catch (RuntimeException ex) {
System.err.println("Unable to find delete range for pos " + id.getEndPos());
errors[0]++;
}
}
for (Identifier id : ar.getIdReferences().values()) {
String desc_ = id.getDefinition().toString();
IdentifierReference definition = lookupSymbolRef(id.getDefinition());
if (definition == null) {
System.err.println(desc_ + ": symbol reference lookup failed");
continue;
} else if (!definition.equals(id.getDefinition())) {
desc_ += "->" + definition;
}
desc_ += " '" + of.getContent().substring(id.getStartPos(), id.getEndPos()) + "'";
String desc = desc_;
final boolean[] found = { false };
for (OberonFile dof : allFiles(definition.getModule(), false)) {
try {
dof.waitWhenDirty(backgroundExecutor, dar -> {
if (dar.getModuleName().equals(definition.getModule())) {
found[0] = true;
if (!dar.getIdDefinitions().containsKey(definition.getEndPos())) {
System.err.println(desc + ": identifier not found");
errors[0]++;
}
}
return null;
}).get();
} catch (InterruptedException | ExecutionException ex) {
}
}
if (!found[0]) {
System.err.println(desc + ": module not loaded");
errors[0]++;
}
}
System.err.println("Reference validation finished, " + errors[0] + " errors detected.");
}
int pos = of.getRawPos(params.getPosition());
Identifier id = findAt(ar.getIdReferences(), pos);
return id != null ? id.getDefinition() : null;
}).thenApply((IdentifierReference sref) -> {
List<Location> result = new ArrayList<>();
if (sref != null) {
IdentifierReference definition = lookupSymbolRef(sref);
if (definition != null) {
Location loc = buildLocation(of, of.getCachedModuleName(), definition);
if (loc != null) {
result.add(loc);
}
}
}
return Either.forLeft(result);
});
}
private CallHierarchyItem buildCallHierarchyItem(OberonFile of, AnalysisResult ar, Identifier id) {
CallHierarchyItem chi = new CallHierarchyItem();
chi.setName(of.getContent().substring(id.getStartPos(), id.getEndPos()));
chi.setKind(id.getKind());
chi.setData(new Two<>(ar.getModuleName(), id.getEndPos()));
Range r = new Range(of.getPos(id.getStartPos()), of.getPos(id.getEndPos()));
chi.setSelectionRange(r);
if (id.getKind() == SymbolKind.Function) {
int[] func = findFunction(ar.getFunctionRanges(), id.getEndPos());
if (func != null && func[1] == id.getEndPos()) {
r = new Range(of.getPos(func[0]), of.getPos(func[1]));
}
}
chi.setRange(r);
return chi;
}
private Location buildLocation(OberonFile baseFile, String baseModule, IdentifierReference definition) {
String uri = null;
for (OberonFile of : allFiles(definition.getModule(), false)) {
try {
Either<Location, String> loc = of.<Either<Location, String>>waitWhenDirty(backgroundExecutor, ar -> {
if (ar.getModuleName().equals(definition.getModule())) {
Identifier id = ar.getIdDefinitions().get(definition.getEndPos());
if (id != null) {
return Either.forLeft(new Location(of.getUri(), new Range(of.getPos(id.getStartPos()), of.getPos(id.getEndPos()))));
}
return Either.forRight(of.getUri());
}
return null;
}).get();
if (loc != null && loc.isLeft())
return loc.getLeft();
else if (loc != null && loc.isRight())
uri = loc.getRight();
} catch (InterruptedException | ExecutionException ex) {
}
}
if (uri == null) {
String baseUri = baseFile.getUri();
int pos = baseUri.lastIndexOf("/" + baseModule + ".");
if (pos != -1) {
uri = baseUri.substring(0, pos + 1) + definition.getModule() + baseUri.substring(pos + baseModule.length() + 1);
}
}
Location location = uri == null ? null : new Location(uri, new Range(new Position(0, 0), new Position(0, 0)));
return location;
}
@Override
public CompletableFuture<List<? extends Location>> references(ReferenceParams params) {
OberonFile of = fileForURI(params.getTextDocument().getUri());
return of.waitWhenDirty(backgroundExecutor, ar -> {
int pos = of.getRawPos(params.getPosition());
Identifier def = findAt(ar.getIdDefinitions(), pos);
if (def != null) {
return new IdentifierReference(def.isExported() ? ar.getModuleName() : "<local>", def.getEndPos());
}
Identifier ref = findAt(ar.getIdReferences(), pos);
if (ref != null) {
if (ref.getDefinition().getModule().equals(ar.getModuleName())) {
if (!ar.getIdDefinitions().get(ref.getDefinition().getEndPos()).isExported() && ref.getDefinition().getEndPos() >= 0) {
return new IdentifierReference("<local>", ref.getDefinition().getEndPos());
}
}
return ref.getDefinition();
}
return (IdentifierReference) null;
}).thenApply(ref -> {
List<Location> result = new ArrayList<>();
if (ref == null)
return result;
IdentifierReference pendingRef = lookupSymbolRef(ref);
if (pendingRef == null)
return result;
List<IdentifierReference> pendingRefs = new ArrayList<>();
pendingRefs.add(ref);
while (!pendingRefs.isEmpty()) {
IdentifierReference reference = pendingRefs.remove(0);
if (reference == null)
continue;
Collection<OberonFile> files;
String defModuleLocal = reference.getModule();
if (defModuleLocal.equals("<local>")) {
defModuleLocal = of.getCachedModuleName();
files = Arrays.asList(of);
} else {
files = allFiles(defModuleLocal, true);
}
final String defModule = defModuleLocal;
for (OberonFile of2 : files) {
of2.waitToAddWhenDirty(result, backgroundExecutor, ar2 -> {
List<Location> locations = new ArrayList<>();
if (ar2.getModuleName().equals(defModule)) {
Identifier rid = findAt(ar2.getIdDefinitions(), reference.getEndPos());
if (rid != null) {
locations.add(new Location(of2.getUri(), new Range(of2.getPos(rid.getStartPos()), of2.getPos(rid.getEndPos()))));
}
}
Map<Integer, List<Integer>> modRefs = ar2.getModuleDeps().get(defModule);
if (modRefs != null) {
List<Integer> refs = modRefs.get(reference.getEndPos());
if (refs != null) {
for (Integer rend : refs) {
if (rend < 0) {
pendingRefs.add(new IdentifierReference(ar2.getModuleName(), rend));
} else {
Identifier rid = ar2.getIdReferences().get(rend);
if (rid != null) {
locations.add(new Location(of2.getUri(), new Range(of2.getPos(rid.getStartPos()), of2.getPos(rid.getEndPos()))));
}
}
}
}
}
return locations;
});
}
}
return result;
});
}
@Override
public CompletableFuture<List<? extends DocumentHighlight>> documentHighlight(DocumentHighlightParams params) {
OberonFile of = fileForURI(params.getTextDocument().getUri());
if (of == null)
return CompletableFuture.completedFuture(new ArrayList<>());
return of.waitWhenDirty(backgroundExecutor, ar -> {
int pos = of.getRawPos(params.getPosition());
IdentifierReference defReference;
Identifier def = findAt(ar.getIdDefinitions(), pos);
if (def != null) {
defReference = new IdentifierReference(ar.getModuleName(), def.getEndPos());
} else {
Identifier ref = findAt(ar.getIdReferences(), pos);
if (ref != null) {
defReference = ref.getDefinition();
} else {
defReference = null;
}
}
List<DocumentHighlight> result = new ArrayList<>();
if (defReference == null)
return result;
if (ar.getModuleName().equals(defReference.getModule())) {
Identifier rid = findAt(ar.getIdDefinitions(), defReference.getEndPos());
if (rid != null) {
result.add(new DocumentHighlight(new Range(of.getPos(rid.getStartPos()), of.getPos(rid.getEndPos())), DocumentHighlightKind.Read));
}
}
Map<Integer, List<Integer>> modRefs = ar.getModuleDeps().get(defReference.getModule());
if (modRefs != null) {
List<Integer> refs = modRefs.get(defReference.getEndPos());
if (refs != null) {
for (Integer rend : refs) {
if (rend < 0) {
continue;
}
Identifier rid = ar.getIdReferences().get(rend);
if (rid != null)
result.add(new DocumentHighlight(new Range(of.getPos(rid.getStartPos()), of.getPos(rid.getEndPos())), rid.isWrittenTo() ? DocumentHighlightKind.Write : DocumentHighlightKind.Read));
}
}
}
return result;
});
}
private CompletableFuture<List<Range>> editingRanges(String uri, Position position) {
OberonFile of = fileForURI(uri);
return of.waitWhenDirty(backgroundExecutor, ar -> {
final List<Range> ranges = new ArrayList<>();
int pos = of.getRawPos(position);
IdentifierReference defReference = null;
Identifier definition = findAt(ar.getIdDefinitions(), pos);
if (definition != null) {
defReference = new IdentifierReference(ar.getModuleName(), definition.getEndPos());
} else {
Identifier ref = findAt(ar.getIdReferences(), pos);
if (ref != null) {
defReference = ref.getDefinition();
}
}
if (defReference != null && defReference.getModule().equals(ar.getModuleName())) {
if (ar.getIdReferences().containsKey(defReference.getEndPos())) {
// do not allow to rename unaliased IMPORTs!
return ranges;
}
Identifier id = ar.getIdDefinitions().get(defReference.getEndPos());
if (id.isExported()) {
// do not allow to rename exported definitions
return ranges;
}
ranges.add(new Range(of.getPos(id.getStartPos()), of.getPos(id.getEndPos())));
Map<Integer, List<Integer>> modRefs = ar.getModuleDeps().get(defReference.getModule());
if (modRefs != null) {
List<Integer> refs = modRefs.get(defReference.getEndPos());
if (refs != null) {
for (Integer rend : refs) {
if (rend < 0)
continue;
Identifier ref = ar.getIdReferences().get(rend);
ranges.add(new Range(of.getPos(ref.getStartPos()), of.getPos(ref.getEndPos())));
}
}
}
}
return ranges;
});
}
@Override
public CompletableFuture<LinkedEditingRanges> linkedEditingRange(LinkedEditingRangeParams params) {
return editingRanges(params.getTextDocument().getUri(), params.getPosition()).thenApply(LinkedEditingRanges::new);
}
private boolean positionBefore(Position p1, Position p2) {
return p1.getLine() < p2.getLine() || (p1.getLine() == p2.getLine() && p1.getCharacter() <= p2.getCharacter());
}
@Override
public CompletableFuture<Either<Range, PrepareRenameResult>> prepareRename(PrepareRenameParams params) {
return editingRanges(params.getTextDocument().getUri(), params.getPosition()).thenApply(rs -> {
for (Range r : rs) {
if (positionBefore(r.getStart(), params.getPosition()) && positionBefore(params.getPosition(), r.getEnd()))
return Either.forLeft(r);
}
return null;
});
}
@Override
public CompletableFuture<WorkspaceEdit> rename(RenameParams params) {
return editingRanges(params.getTextDocument().getUri(), params.getPosition()).thenApply(rs -> {
List<TextEdit> edits = new ArrayList<>();
for (Range r : rs) {
edits.add(new TextEdit(r, params.getNewName()));
}
Map<String, List<TextEdit>> changes = new HashMap<>();
changes.put(params.getTextDocument().getUri(), edits);
return new WorkspaceEdit(changes);
});
}
@Override
public CompletableFuture<SignatureHelp> signatureHelp(SignatureHelpParams params) {
OberonFile of = fileForURI(params.getTextDocument().getUri());
CompletableFuture<SignatureHelp> result = new CompletableFuture<>();
backgroundExecutor.submit(new Callable<Void>() {
public Void call() throws Exception {
Thread.sleep(200);
of.waitWhenDirty(backgroundExecutor, ar -> {
int paramIndex = 0, startPos = -1;
{
SortedMap<Integer, ParamTag> tagsBefore = ar.getParamTags().headMap(of.getRawPos(params.getPosition()) + 1);
int depth = 0;
loop: while (!tagsBefore.isEmpty()) {
int p = tagsBefore.lastKey();
ParamTag t = tagsBefore.get(p);
tagsBefore = tagsBefore.headMap(p);
switch(t) {
case CALL_START:
if (depth == 0) {
startPos = p - 1;
break loop;
}
depth--;
break;
case END:
depth++;
break;
case NEXT:
if (depth == 0)
paramIndex++;
break;
case END_LAST:
case PROC_START:
default:
break loop;
}
}
}
if (startPos == -1)
return new Two<Integer, IdentifierReference>(paramIndex, null);
while (startPos > 0 && of.getContent().charAt(startPos - 1) <= ' ') startPos--;
Identifier refid = ar.getIdReferences().get(startPos);
return new Two<>(paramIndex, refid.getDefinition());
}).thenApply(pair -> {
final int paramIndex_ = pair.getFirst();
IdentifierReference ref = lookupSymbolRef(pair.getSecond());
if (ref == null)
return null;
for (OberonFile of2 : allFiles(ref.getModule(), false)) {
try {
SignatureHelp sh = of2.waitWhenDirty(backgroundExecutor, ar2 -> {
if (ar2.getModuleName().equals(ref.getModule())) {
Identifier id = ar2.getIdDefinitions().get(ref.getEndPos());
if (id != null) {
int ssPos = id.getStartPos();
SortedMap<Integer, ParamTag> tagsAfter = ar2.getParamTags().tailMap(ssPos);
int p = tagsAfter.firstKey();
ParamTag t = tagsAfter.get(p);
if (t != ParamTag.PROC_START)
return null;
int psPos = p;
tagsAfter = tagsAfter.tailMap(p + 1);
List<ParameterInformation> pis = new ArrayList<>();
while (!tagsAfter.isEmpty()) {
p = tagsAfter.firstKey();
t = tagsAfter.get(p);
tagsAfter = tagsAfter.tailMap(p + 1);
if (t != ParamTag.NEXT && t != ParamTag.END && t != ParamTag.END_LAST)
return null;
ParameterInformation pi = new ParameterInformation();
pi.setLabel(new Two<Integer, Integer>(psPos - ssPos, p - 1 - ssPos));
pis.add(pi);
psPos = p;
if (t != ParamTag.NEXT)
break;
}
SignatureInformation si = new SignatureInformation(of2.getContent().substring(ssPos, psPos));
si.setParameters(pis);
return new SignatureHelp(Arrays.asList(si), 0, paramIndex_);
}
}
return null;
}).get();
if (sh != null)
return sh;
} catch (InterruptedException | ExecutionException ex) {
}
}
return null;
}).thenApply(r -> result.complete(r));
return null;
}
});
return result;
}
@Override
public CompletableFuture<List<FoldingRange>> foldingRange(FoldingRangeRequestParams params) {
OberonFile of = fileForURI(params.getTextDocument().getUri());
if (of == null)
return CompletableFuture.completedFuture(new ArrayList<>());
return of.waitWhenDirty(backgroundExecutor, ar -> {
List<FoldingRange> result = new ArrayList<>();
Map<Integer, Integer> lastLineForOuterFunc = new HashMap<>();
for (Map.Entry<Integer, int[]> func : ar.getFunctionRanges().entrySet()) {
int outerFunc = func.getValue().length > 2 ? func.getValue()[2] : -1;
int lastLine = lastLineForOuterFunc.getOrDefault(outerFunc, -1);
int startLine = of.getPos(func.getKey()).getLine();
int endLine = of.getPos(func.getValue()[1]).getLine();
if (startLine > lastLine) {
result.add(new FoldingRange(startLine, endLine));
lastLineForOuterFunc.put(outerFunc, endLine);
}
}
return result;
});
}
@Override
public CompletableFuture<Hover> hover(HoverParams params) {
OberonFile of = fileForURI(params.getTextDocument().getUri());
if (of == null)
return CompletableFuture.completedFuture(null);
return of.waitWhenDirty(backgroundExecutor, ar -> {
int pos = of.getRawPos(params.getPosition());
Identifier def = findAt(ar.getIdDefinitions(), pos);
if (def != null) {
return new Two<>(def, new IdentifierReference(ar.getModuleName(), def.getEndPos()));
}
Identifier ref = findAt(ar.getIdReferences(), pos);
if (ref != null) {
return new Two<>(ref, ref.getDefinition());
}
return new Two<>((Identifier) null, (IdentifierReference) null);
}).thenApply(pair -> {
Identifier id = pair.getFirst();
IdentifierReference def = lookupSymbolRef(pair.getSecond());
List<Hover> hovers = new ArrayList<>();
if (def != null) {
for (OberonFile of2 : allFiles(def.getModule(), false)) {
of2.waitToAddWhenDirty(hovers, backgroundExecutor, ar2 -> {
if (ar2.getModuleName() != null && ar2.getModuleName().equals(def.getModule())) {
Identifier rid = findAt(ar2.getIdDefinitions(), def.getEndPos());
if (rid != null) {
String content = of2.getContent();
int startPos = rid.getStartPos();
while (startPos > 0 && content.charAt(startPos - 1) != '\n') startPos--;
int endPos = rid.getEndPos();
while (endPos < content.length() && content.charAt(endPos) != '\n') endPos++;
return Arrays.asList(new Hover(new MarkupContent(MarkupKind.PLAINTEXT, of2.getContent().substring(startPos, endPos)), new Range(of.getPos(id.getStartPos()), of.getPos(id.getEndPos()))));
}
}
return Collections.emptyList();
});
}
}
if (!hovers.isEmpty()) {
Hover hover = hovers.get(0);
hover.setRange(new Range(of.getPos(id.getStartPos()), of.getPos(id.getEndPos())));
return hover;
}
return null;
});
}
@Override
public CompletableFuture<List<? extends TextEdit>> formatting(DocumentFormattingParams params) {
OberonFile of = fileForURI(params.getTextDocument().getUri());
if (of == null)
return CompletableFuture.completedFuture(new ArrayList<>());
String content = of.getContent();
Range fullRange = new Range(of.getPos(0), of.getPos(of.getContent().length()));
CompletableFuture<List<? extends TextEdit>> result = new CompletableFuture<>();
backgroundExecutor.submit(() -> {
String newText;
try {
List<FormatTokenInfo> tokens = bridge.format(content);
OberonFormatter ofo = new OberonFormatter();
int pos = 0;
for (FormatTokenInfo token : tokens) {
if (pos < token.getStartPos()) {
ofo.appendWhitespace(content.substring(pos, token.getStartPos()));
}
ofo.appendToken(content.substring(token.getStartPos(), token.getEndPos()), token);
pos = token.getEndPos();
}
if (pos < content.length())
ofo.appendWhitespace(content.substring(pos));
newText = ofo.getResult();
} catch (Exception ex) {
ex.printStackTrace();
result.complete(new ArrayList<>());
return;
}
result.complete(Arrays.asList(new TextEdit(fullRange, newText)));
});
return result;
}
@Override
public CompletableFuture<List<CallHierarchyItem>> prepareCallHierarchy(CallHierarchyPrepareParams params) {
OberonFile of = fileForURI(params.getTextDocument().getUri());
return of.waitWhenDirty(backgroundExecutor, (Function<AnalysisResult, Either<List<CallHierarchyItem>, IdentifierReference>>) ar -> {
int pos = of.getRawPos(params.getPosition());
Identifier id = findAt(ar.getIdDefinitions(), pos);
IdentifierReference def;
if (id != null) {
def = new IdentifierReference(ar.getModuleName(), id.getEndPos());
} else {
id = findAt(ar.getIdReferences(), pos);
if (id != null) {
def = id.getDefinition();
} else {
int[] func = findFunction(ar.getFunctionRanges(), pos);
if (func != null) {
id = ar.getIdDefinitions().get(func[1]);
if (id != null) {
def = new IdentifierReference(ar.getModuleName(), func[1]);
} else {
return Either.forLeft(Collections.emptyList());
}
} else {
return Either.forLeft(Collections.emptyList());
}
}
}
if (def == null || !def.getModule().equals(ar.getModuleName())) {
return Either.forRight(def);
}
CallHierarchyItem chi = buildCallHierarchyItem(of, ar, id);
return Either.forLeft(Arrays.asList(chi));
}).thenApply((Either<List<CallHierarchyItem>, IdentifierReference> either) -> {
if (either.isLeft())
return either.getLeft();
IdentifierReference ref = lookupSymbolRef(either.getRight());
List<CallHierarchyItem> result = new ArrayList<>();
if (ref == null)
return result;
for (OberonFile of2 : allFiles(ref.getModule(), false)) {
of2.waitToAddWhenDirty(result, backgroundExecutor, ar2 -> {
CallHierarchyItem chi = null;
if (ar2.getModuleName().equals(ref.getModule())) {
Identifier id = ar2.getIdDefinitions().get(ref.getEndPos());
if (id != null)
chi = buildCallHierarchyItem(of2, ar2, id);
}
return chi == null ? Collections.emptyList() : Arrays.asList(chi);
});
}
return result;
});
}
@Override
public CompletableFuture<List<CallHierarchyIncomingCall>> callHierarchyIncomingCalls(CallHierarchyIncomingCallsParams params) {
@SuppressWarnings("unchecked") Two<String, Integer> details = (Two<String, Integer>) params.getItem().getData();
String defModule = details.getFirst();
int defEndPos = details.getSecond();
CompletableFuture<List<CallHierarchyIncomingCall>> ret = new CompletableFuture<>();
backgroundExecutor.submit(() -> {
List<CallHierarchyIncomingCall> result = new ArrayList<>();
for (OberonFile of : allFiles(defModule, true)) {
of.waitToAddWhenDirty(result, backgroundExecutor, ar -> {
Map<Integer, List<Range>> functionCallRanges = new TreeMap<>();
Map<Integer, List<Integer>> modRefs = ar.getModuleDeps().get(defModule);
if (modRefs != null) {
List<Integer> rends = modRefs.get(defEndPos);
if (rends != null) {
for (Integer rend : rends) {
if (rend < 0)
continue;
Identifier rid = ar.getIdReferences().get(rend);
if (rid == null)
continue;
int[] func = findFunction(ar.getFunctionRanges(), rend);
if (func != null) {
functionCallRanges.computeIfAbsent(func[1], x -> new ArrayList<>()).add(new Range(of.getPos(rid.getStartPos()), of.getPos(rid.getEndPos())));
}
}
}
}
List<CallHierarchyIncomingCall> calls = new ArrayList<>();
for (int funcPos : functionCallRanges.keySet()) {
Identifier id = ar.getIdDefinitions().get(funcPos);
calls.add(new CallHierarchyIncomingCall(buildCallHierarchyItem(of, ar, id), functionCallRanges.get(funcPos)));
}
return calls;
});
}
ret.complete(result);
});
return ret;
}
@Override
public CompletableFuture<List<CallHierarchyOutgoingCall>> callHierarchyOutgoingCalls(CallHierarchyOutgoingCallsParams params) {
@SuppressWarnings("unchecked") Two<String, Integer> details = (Two<String, Integer>) params.getItem().getData();
String defModule = details.getFirst();
int defEndPos = details.getSecond();
CompletableFuture<List<CallHierarchyOutgoingCall>> ret = new CompletableFuture<>();
backgroundExecutor.submit(() -> {
List<Map.Entry<String, Map<Integer, List<Range>>>> tempResult = new ArrayList<>();
for (OberonFile of : allFiles(defModule, false)) {
of.waitToAddWhenDirty(tempResult, backgroundExecutor, ar -> {
Map<String, Map<Integer, List<Range>>> functionRefRanges = new TreeMap<>();
if (ar.getModuleName().equals(defModule)) {
Identifier rid = findAt(ar.getIdDefinitions(), defEndPos);
if (rid != null && rid.getKind() == SymbolKind.Function) {
int[] func = findFunction(ar.getFunctionRanges(), defEndPos);
if (func != null && func[1] == defEndPos) {
for (Identifier ref : ar.getIdReferences().subMap(defEndPos, func[2]).values()) {
if (ref.getKind() == SymbolKind.Function) {
IdentifierReference def = lookupSymbolRef(ref.getDefinition());
if (def != null) {
functionRefRanges.computeIfAbsent(def.getModule(), x -> new HashMap<>()).computeIfAbsent(def.getEndPos(), x -> new ArrayList<>()).add(new Range(of.getPos(ref.getStartPos()), of.getPos(ref.getEndPos())));
}
}
}
}
}
}
return new ArrayList<>(functionRefRanges.entrySet());
});
}
List<CallHierarchyOutgoingCall> result = new ArrayList<>();
for (Map.Entry<String, Map<Integer, List<Range>>> entry : tempResult) {
for (OberonFile of : allFiles(entry.getKey(), false)) {
of.waitToAddWhenDirty(result, backgroundExecutor, ar -> {
List<CallHierarchyOutgoingCall> calls = new ArrayList<>();
if (ar.getModuleName().equals(entry.getKey())) {
for (Map.Entry<Integer, List<Range>> positions : entry.getValue().entrySet()) {
Identifier id = ar.getIdDefinitions().get(positions.getKey());
calls.add(new CallHierarchyOutgoingCall(buildCallHierarchyItem(of, ar, id), positions.getValue()));
}
}
return calls;
});
}
}
ret.complete(result);
});
return ret;
}
@Override
public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(CodeActionParams params) {
OberonFile of = fileForURI(params.getTextDocument().getUri());
return of.waitWhenDirty(backgroundExecutor, ar -> {
return fillCodeActions(params, of, ar);
});
}
@Override
public CompletableFuture<CodeAction> resolveCodeAction(CodeAction unresolved) {
return fillResolvedCodeAction(unresolved);
}
};
}
use of lspserver.OberonFile.IdentifierReference in project OberonEmulator by schierlm.
the class Server method lookupSymbolRef.
protected final IdentifierReference lookupSymbolRef(IdentifierReference definition) {
while (definition != null && definition.getEndPos() < 0) {
List<IdentifierReference> result = new ArrayList<>();
IdentifierReference definition_ = definition;
for (OberonFile dof : allFiles(definition.getModule(), false)) {
dof.waitToAddWhenDirty(result, backgroundExecutor, dar -> {
if (definition_.getModule().equals(dar.getModuleName())) {
IdentifierReference lookup = dar.getExportedSymbolRefs().get(definition_.getEndPos());
if (lookup != null)
return Arrays.asList(lookup);
}
return Collections.emptyList();
});
}
definition = result.size() == 1 ? result.get(0) : null;
}
return definition;
}
use of lspserver.OberonFile.IdentifierReference in project OberonEmulator by schierlm.
the class Bridge method analyze.
/**
* Return whether dependent modules should be analyzed again.
*/
public synchronized boolean analyze(OberonFile file, AnalysisResult ar) throws IOException {
ar.setModuleName(null);
ar.getModuleDeps().clear();
ar.getErrors().clear();
ar.setSemanticTokens(new int[0]);
ar.getOutline().clear();
ar.getIdDefinitions().clear();
ar.getIdReferences().clear();
ar.getFunctionDefinitions().clear();
ar.getFunctionRanges().clear();
ar.getParamTags().clear();
ar.getExportedSymbolRefs().clear();
String content = file.getContent();
ar.setContentHash(OberonFile.hashText(content));
if (content.length() == 0) {
ar.getErrors().add(new Diagnostic(new Range(new Position(0, 0), new Position(0, 1)), "Empty file"));
return false;
}
ProtocolConstant.INST_GetModuleInfo.send(dos);
writeIntLE(content.length());
dos.write(content.getBytes(StandardCharsets.ISO_8859_1));
dos.flush();
boolean result = false, functionPending = false, importAliasPending = false;
int prevSymbolEnd = 0, lastSymbolEnd = 0, declarationBlockStart = -1, paramDepth = 0;
List<List<DocumentSymbol>> outlineStack = new ArrayList<>();
List<Range> procRangeStack = new ArrayList<>();
List<Integer> functionNamePosStack = new ArrayList<>();
outlineStack.add(new ArrayList<>());
SortedMap<Integer, int[]> semanticTokenInformation = new TreeMap<>();
List<int[]> definitionLists = new ArrayList<>();
boolean inProcParam = false;
Identifier lastUndefinedSymbol = null;
loop: while (true) {
ProtocolConstant res = ProtocolConstant.read(dis);
switch(res) {
case STATUS_OK:
break loop;
case STATUS_Invalid:
throw new IOException("Invalid status received");
case ANSWER_ModuleName:
ar.setModuleName(readCStr());
break;
case ANSWER_ModuleImport:
ar.getModuleDeps().putIfAbsent(readCStr(), new HashMap<>());
break;
case ANSWER_Error:
int pos = adjustPos(readIntLE(), content.length());
String msg = readCStr();
ar.getErrors().add(new Diagnostic(new Range(file.getPos(pos == 0 ? 0 : pos - 1), file.getPos(pos + 1)), msg));
break;
case ANSWER_Warning:
int pos_W = adjustPos(readIntLE(), content.length());
if (pos_W == content.length())
pos_W--;
String msg_W = readCStr();
Diagnostic diag_W = new Diagnostic(new Range(file.getPos(pos_W == 0 ? 0 : pos_W - 1), file.getPos(pos_W + 1)), msg_W);
diag_W.setSeverity(DiagnosticSeverity.Warning);
ar.getErrors().add(diag_W);
break;
case ANSWER_Information:
int pos_I = adjustPos(readIntLE(), content.length());
String msg_I = readCStr();
Diagnostic diag_I = new Diagnostic(new Range(file.getPos(pos_I == 0 ? 0 : pos_I - 1), file.getPos(pos_I + 1)), msg_I);
diag_I.setSeverity(DiagnosticSeverity.Information);
ar.getErrors().add(diag_I);
break;
case ANSWER_SymbolFileChanged:
result = true;
break;
case ANSWER_SymbolFileIndex:
int symIdx = readIntLE();
String symMod = readCStr();
int symEndPos = readIntLE();
if (ar.getExportedSymbolRefs().containsKey(symIdx))
throw new IllegalStateException("Duplicate symbol index " + symIdx);
ar.getExportedSymbolRefs().put(symIdx, new IdentifierReference(symMod, symEndPos));
break;
case ANSWER_SyntaxElement:
int start = readIntLE();
int end = readIntLE();
SyntaxElement synElem = SyntaxElement.read(dis);
int defEnd = readIntLE();
String defModName = defEnd == -1 ? null : readCStr();
prevSymbolEnd = lastSymbolEnd;
lastSymbolEnd = end;
if (end > file.getContent().length())
end = file.getContent().length();
String name = file.getContent().substring(start, end);
IdentifierReference definition = defEnd == -1 ? null : new IdentifierReference(defModName, defEnd);
boolean declaration = defEnd == end && (defModName.equals(ar.getModuleName()) || ar.getModuleName() == null);
int[] semanticInfo = new int[] { end - start, synElem.tokenType, synElem.tokenModifiers | (declaration ? (1 << TOKEN_MODIFIERS.indexOf(SemanticTokenModifiers.Declaration)) : 0) };
if (name.contains("\n")) {
int currPos = name.indexOf('\n'), lastPos = currPos + 1;
semanticInfo[0] = currPos;
semanticTokenInformation.put(start + currPos, new int[] { currPos, semanticInfo[1], semanticInfo[2] });
while ((currPos = name.indexOf('\n', lastPos)) != -1) {
semanticTokenInformation.put(start + currPos, new int[] { currPos - lastPos, semanticInfo[1], semanticInfo[2] });
lastPos = currPos + 1;
}
semanticInfo[0] = name.length() - lastPos;
}
semanticTokenInformation.put(end, semanticInfo);
if (declaration && synElem.kind != null) {
List<DocumentSymbol> outline = outlineStack.get(outlineStack.size() - 1);
Range range = new Range(file.getPos(start), file.getPos(end));
DocumentSymbol ds = new DocumentSymbol(name, synElem.kind, range, range);
if (synElem.kind == SymbolKind.Function && functionPending) {
functionPending = false;
ds.setRange(procRangeStack.get(procRangeStack.size() - 1));
ds.setChildren(new ArrayList<>());
ar.getFunctionDefinitions().put(end, new OberonFile.Identifier(start, end, synElem.kind, null));
ar.getFunctionRanges().get(functionNamePosStack.get(functionNamePosStack.size() - 1))[0] = end;
outlineStack.add(ds.getChildren());
}
outline.add(ds);
OberonFile.Identifier newId = new OberonFile.Identifier(start, end, synElem.kind, null);
if (inProcParam)
newId.setProcedureParameter(true);
ar.getIdDefinitions().put(end, newId);
if (synElem == SyntaxElement.SynModule && name.equals(defModName) && ar.getModuleName() == null) {
ar.getIdDefinitions().put(1, newId);
}
} else if (defEnd != -1 && synElem.kind != null) {
OberonFile.Identifier id = new OberonFile.Identifier(start, end, synElem.kind, definition);
ar.getIdReferences().put(end, id);
ar.getModuleDeps().computeIfAbsent(defModName, x -> new HashMap<>()).computeIfAbsent(defEnd, x -> new ArrayList<>()).add(end);
if (defEnd == 1) {
if (!defModName.equals(ar.getModuleName())) {
// import statement
if (importAliasPending) {
importAliasPending = false;
} else {
ar.getIdDefinitions().put(end, id);
}
} else {
// Module END.
ar.getModuleDeps().get(defModName).computeIfAbsent(ar.getIdDefinitions().get(1).getEndPos(), x -> new ArrayList<>()).add(end);
}
}
} else if (synElem == SyntaxElement.SynUndefined) {
lastUndefinedSymbol = new OberonFile.Identifier(start, end, null, null);
}
break;
case ANSWER_ProcedureStart:
int procPos = lastSymbolEnd - "PROCEDURE".length();
procRangeStack.add(new Range(file.getPos(procPos), file.getPos(lastSymbolEnd)));
if (functionNamePosStack.size() == 0) {
ar.getFunctionRanges().put(procPos, new int[] { -1, -1 });
} else {
ar.getFunctionRanges().put(procPos, new int[] { -1, -1, functionNamePosStack.get(functionNamePosStack.size() - 1) });
}
functionNamePosStack.add(procPos);
functionPending = true;
break;
case ANSWER_ProcedureEnd:
int pos0 = adjustPos(readIntLE(), file.getContent().length());
procRangeStack.remove(procRangeStack.size() - 1).setEnd(file.getPos(pos0));
ar.getFunctionRanges().get(functionNamePosStack.remove(functionNamePosStack.size() - 1))[1] = lastSymbolEnd;
if (!functionPending) {
outlineStack.remove(outlineStack.size() - 1);
}
functionPending = false;
break;
case ANSWER_ImportAlias:
// find last reference and remove it
int lastPos = ar.getIdReferences().lastKey();
Identifier id = ar.getIdReferences().remove(lastPos);
ar.getModuleDeps().get(id.getDefinition().getModule()).remove(id.getDefinition().getEndPos());
if (id.getKind() != SymbolKind.Module) {
throw new IllegalStateException("Last symbol to remove is not an imported module");
}
importAliasPending = true;
break;
case ANSWER_ProcParamStart:
int pos1 = readIntLE();
paramDepth++;
ar.getParamTags().put(pos1, ParamTag.PROC_START);
inProcParam = true;
break;
case ANSWER_CallParamStart:
int pos2 = readIntLE();
paramDepth++;
ar.getParamTags().put(pos2, ParamTag.CALL_START);
inProcParam = false;
break;
case ANSWER_ParamNext:
int pos3 = readIntLE();
ar.getParamTags().put(pos3, ParamTag.NEXT);
break;
case ANSWER_ParamEnd:
int pos4 = readIntLE();
paramDepth--;
ar.getParamTags().put(pos4, paramDepth == 0 ? ParamTag.END_LAST : ParamTag.END);
if (paramDepth == 0)
inProcParam = false;
break;
case ANSWER_ForwardPointer:
if (lastUndefinedSymbol != null && lastUndefinedSymbol.getEndPos() == lastSymbolEnd) {
semanticTokenInformation.get(lastSymbolEnd)[1] = SyntaxElement.SynType.tokenType;
ar.getIdReferences().put(lastSymbolEnd, new OberonFile.Identifier(lastUndefinedSymbol.getStartPos(), lastSymbolEnd, SyntaxElement.SynType.kind, new IdentifierReference(ar.getModuleName(), lastSymbolEnd)));
}
break;
case ANSWER_ForwardPointerFixup:
int pointerPos = readIntLE();
int targetPos = readIntLE();
OberonFile.Identifier pointerId = ar.getIdReferences().get(pointerPos);
pointerId.setDefinition(new IdentifierReference(pointerId.getDefinition().getModule(), targetPos));
ar.getModuleDeps().computeIfAbsent(pointerId.getDefinition().getModule(), x -> new HashMap<>()).computeIfAbsent(targetPos, x -> new ArrayList<>()).add(pointerPos);
break;
case ANSWER_VarModified:
Identifier varId = ar.getIdReferences().get(lastSymbolEnd);
if (varId != null) {
int tokenType = semanticTokenInformation.get(lastSymbolEnd)[1];
if (tokenType == SyntaxElement.SynVariable.tokenType || tokenType == SyntaxElement.SynParameter.tokenType) {
semanticTokenInformation.get(lastSymbolEnd)[2] |= 1 << TOKEN_MODIFIERS.indexOf(SemanticTokenModifiers.Modification);
}
varId.setWrittenTo(true);
}
break;
case ANSWER_NameExported:
Identifier expoId = ar.getIdDefinitions().get(prevSymbolEnd);
if (expoId != null) {
expoId.setExportedPos(lastSymbolEnd);
}
break;
case ANSWER_DefinitionRepeat:
Identifier repeatId = ar.getIdReferences().get(lastSymbolEnd);
if (repeatId != null) {
repeatId.setDefinitionRepeat(true);
}
break;
case ANSWER_DefinitionUsed:
Identifier usedId = ar.getIdDefinitions().get(lastSymbolEnd);
if (usedId != null) {
usedId.setUsed(true);
}
break;
case ANSWER_DeclarationBlockStart:
if (declarationBlockStart != -1)
throw new IllegalStateException("Nested declaration blocks");
declarationBlockStart = readIntLE();
break;
case ANSWER_DeclarationBlockEnd:
if (declarationBlockStart == -1)
throw new IllegalStateException("No declaration block open");
int declarationBlockEnd = readIntLE();
ar.getDeclarationBlocks().put(declarationBlockStart, declarationBlockEnd);
declarationBlockStart = -1;
break;
case ANSWER_DefinitionListStart:
int[] definitionListS = new int[] { readIntLE(), -1, -1 };
definitionLists.add(definitionListS);
ar.getDefinitionLists().put(definitionListS[0], definitionListS);
break;
case ANSWER_DefinitionListValue:
if (definitionLists.isEmpty())
throw new IllegalStateException("No definition list open");
definitionLists.get(definitionLists.size() - 1)[1] = readIntLE();
break;
case ANSWER_DefinitionListEnd:
if (definitionLists.isEmpty())
throw new IllegalStateException("No definition list open");
int[] definitionListE = definitionLists.remove(definitionLists.size() - 1);
definitionListE[2] = readIntLE();
if (definitionListE[1] == -1)
definitionListE[1] = definitionListE[2];
break;
case ANSWER_RecordStart:
List<DocumentSymbol> newOutline = new ArrayList<>(), oldOutline = outlineStack.get(outlineStack.size() - 1);
if (!oldOutline.isEmpty()) {
oldOutline.get(oldOutline.size() - 1).setChildren(newOutline);
}
outlineStack.add(newOutline);
break;
case ANSWER_RecordEnd:
outlineStack.remove(outlineStack.size() - 1);
break;
default:
throw new IOException("Invalid response: " + res);
}
}
for (int[] pendingDef : definitionLists) {
ar.getErrors().add(new Diagnostic(new Range(file.getPos(pendingDef[0]), file.getPos(pendingDef[0] + 1)), "LSP: Definition list not closed"));
ar.getDefinitionLists().remove(pendingDef[0]);
}
for (Identifier id : ar.getIdDefinitions().values()) {
if (id.isExported() || id.isUsed())
continue;
if (id.getEndPos() == 1 || id.getEndPos() == ar.getIdDefinitions().get(1).getEndPos())
continue;
Map<Integer, List<Integer>> deps = ar.getModuleDeps().get(ar.getModuleName());
if (deps == null || deps.get(id.getEndPos()) == null || deps.get(id.getEndPos()).stream().map(ep -> ar.getIdReferences().get(ep)).allMatch(rid -> rid.isDefinitionRepeat())) {
Diagnostic diag = new Diagnostic(new Range(file.getPos(id.getStartPos()), file.getPos(id.getEndPos())), id.isProcedureParameter() ? OberonFile.UNUSED_PARAM : OberonFile.UNUSED_DEFINITION);
diag.setSeverity(DiagnosticSeverity.Hint);
diag.setTags(Arrays.asList(DiagnosticTag.Unnecessary));
ar.getErrors().add(diag);
}
}
int[] semanticTokenBuffer = new int[5 * semanticTokenInformation.size()];
int semanticTokenLength = 0, semanticTokenLine = 0, semanticTokenChar = 0;
for (Map.Entry<Integer, int[]> entry : semanticTokenInformation.entrySet()) {
int start = entry.getKey() - entry.getValue()[0];
Position startPos = file.getPos(start);
semanticTokenBuffer[semanticTokenLength++] = startPos.getLine() - semanticTokenLine;
semanticTokenBuffer[semanticTokenLength++] = startPos.getCharacter() - (startPos.getLine() == semanticTokenLine ? semanticTokenChar : 0);
semanticTokenBuffer[semanticTokenLength++] = entry.getValue()[0];
semanticTokenBuffer[semanticTokenLength++] = entry.getValue()[1];
semanticTokenBuffer[semanticTokenLength++] = entry.getValue()[2];
semanticTokenLine = startPos.getLine();
semanticTokenChar = startPos.getCharacter();
}
if (semanticTokenLength != semanticTokenBuffer.length)
throw new IllegalStateException();
ar.setSemanticTokens(semanticTokenBuffer);
if (outlineStack.size() == 1) {
for (DocumentSymbol ds : outlineStack.get(0)) {
ar.getOutline().add(Either.forRight(ds));
}
}
return result;
}
use of lspserver.OberonFile.IdentifierReference in project OberonEmulator by schierlm.
the class CachingServer method getErrors.
@Override
protected List<Diagnostic> getErrors(OberonFile of) throws InterruptedException, ExecutionException {
class ExportInfo {
private final int exportedPos, endPos, symbolIndex;
private boolean used;
private ExportInfo(int exportedPos, int endPos, int symbolIndex) {
this.exportedPos = exportedPos;
this.endPos = endPos;
this.symbolIndex = symbolIndex;
}
}
List<Diagnostic> errors = super.getErrors(of);
of.setUnusedExportsFound(false);
if (of.getCachedModuleName() == null)
return errors;
List<Diagnostic> exports = of.waitWhenDirty(backgroundExecutor, ar -> {
Map<Integer, Integer> symRefs = new HashMap<>();
for (Map.Entry<Integer, IdentifierReference> entry : ar.getExportedSymbolRefs().entrySet()) {
if (entry.getValue().getModule().equals(ar.getModuleName())) {
symRefs.put(entry.getValue().getEndPos(), entry.getKey());
}
}
return ar.getIdDefinitions().values().stream().filter(id -> id.isExported()).map(id -> new ExportInfo(id.getExportedPos(), id.getEndPos(), symRefs.getOrDefault(id.getEndPos(), id.getEndPos()))).collect(Collectors.toList());
}).thenApply(eilist -> {
List<Diagnostic> diags = new ArrayList<>();
for (OberonFile of2 : allFiles(of.getCachedModuleName(), true)) {
of2.waitToAddWhenDirty(diags, backgroundExecutor, ar2 -> {
Map<Integer, List<Integer>> deps = ar2.getModuleDeps().get(of.getCachedModuleName());
if (deps == null)
return Collections.emptyList();
for (ExportInfo ei : eilist) {
if (deps.containsKey(ei.endPos) || deps.containsKey(ei.symbolIndex))
ei.used = true;
}
return Collections.emptyList();
});
}
for (ExportInfo ei : eilist) {
if (!ei.used) {
Diagnostic diag = new Diagnostic(new Range(of.getPos(ei.exportedPos - 1), of.getPos(ei.exportedPos)), OberonFile.UNUSED_EXPORT);
diag.setSeverity(DiagnosticSeverity.Hint);
diag.setTags(Arrays.asList(DiagnosticTag.Unnecessary));
diags.add(diag);
}
}
return diags;
}).get();
if (!exports.isEmpty()) {
of.setUnusedExportsFound(true);
}
errors.addAll(exports);
return errors;
}
Aggregations