Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP towards better hover / completion support #58

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.opencds.cqf.cql.ls.server.manager.CqlTranslationManager;
import org.opencds.cqf.cql.ls.server.manager.TranslatorOptionsManager;
import org.opencds.cqf.cql.ls.server.plugin.CommandContribution;
import org.opencds.cqf.cql.ls.server.provider.CompletionProvider;
import org.opencds.cqf.cql.ls.server.provider.FormattingProvider;
import org.opencds.cqf.cql.ls.server.provider.HoverProvider;
import org.opencds.cqf.cql.ls.server.service.ActiveContentService;
Expand Down Expand Up @@ -68,10 +69,10 @@ public CompletableFuture<LanguageClient> languageClient() {

@Bean
public CqlTextDocumentService cqlTextDocumentService(
CompletableFuture<LanguageClient> languageClient, HoverProvider hoverProvider,
FormattingProvider formattingProvider, EventBus eventBus) {
return new CqlTextDocumentService(languageClient, hoverProvider, formattingProvider,
eventBus);
CompletableFuture<LanguageClient> languageClient, CompletionProvider completionProvider,
HoverProvider hoverProvider, FormattingProvider formattingProvider, EventBus eventBus) {
return new CqlTextDocumentService(languageClient, completionProvider, hoverProvider,
formattingProvider, eventBus);
}

@Bean
Expand All @@ -96,8 +97,11 @@ TranslatorOptionsManager translatorOptionsManager(ContentService contentService,
CqlTranslationManager cqlTranslationManager(ContentService contentService,
TranslatorOptionsManager translatorOptionsManager) {
return new CqlTranslationManager(contentService, translatorOptionsManager);
}


@Bean
CompletionProvider completionProvider(CqlTranslationManager cqlTranslationManager) {
return new CompletionProvider(cqlTranslationManager);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.opencds.cqf.cql.ls.server.provider;

import java.util.List;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.CompletionParams;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.opencds.cqf.cql.ls.server.manager.CqlTranslationManager;

public class CompletionProvider {

private CqlTranslationManager cqlTranslationManager;

public CompletionProvider(CqlTranslationManager cqlTranslationManager) {
this.cqlTranslationManager = cqlTranslationManager;
}

public Either<List<CompletionItem>, CompletionList> completion(
CompletionParams completionParams) {
return null;
}

}
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
package org.opencds.cqf.cql.ls.server.provider;

import java.net.URI;
import org.apache.commons.lang3.tuple.Pair;
import org.cqframework.cql.cql2elm.CqlTranslator;
import org.cqframework.cql.elm.tracking.TrackBack;
import org.cqframework.cql.cql2elm.model.TranslatedLibrary;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.HoverParams;
import org.eclipse.lsp4j.MarkupContent;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.hl7.cql.model.DataType;
import org.hl7.elm.r1.Element;
import org.hl7.elm.r1.ExpressionDef;
import org.hl7.elm.r1.Library.Statements;
import org.hl7.elm.r1.ExpressionRef;
import org.hl7.elm.r1.Library;
import org.hl7.elm.r1.Retrieve;
import org.opencds.cqf.cql.ls.core.utility.Uris;
import org.opencds.cqf.cql.ls.server.manager.CqlTranslationManager;
import org.opencds.cqf.cql.ls.server.utility.TrackBacks;
import org.opencds.cqf.cql.ls.server.visitor.ExpressionTrackBackVisitor;

public class HoverProvider {
private CqlTranslationManager cqlTranslationManager;
Expand All @@ -35,87 +38,63 @@ public Hover hover(HoverParams position) {
return null;
}

// The ExpressionTrackBackVisitor is supposed to replace this eventually.
// Basically, for any given position in the text document there's a graph of nodes
// that represent the parents nodes for that position. For example:
//
// define: "EncounterExists":
// exists([Encounter])
//
// ExpressionDef -> Expression -> Exists -> Retrieve
//
// For that given position, we want to select the most specific node we support generating
// hover information for and return that.
//
// (maybe.. the alternative is to select the specific node under the cursor, but that may be
// less user friendly)
//
// The current code always picks the first ExpressionDef in the graph.
Pair<Range, ExpressionDef> exp = getExpressionDefForPosition(position.getPosition(),
translator.getTranslatedLibrary().getLibrary().getStatements());

if (exp == null) {
var elm = elementForPosition(translator.getTranslatedLibrary().getLibrary(),
position.getPosition());

if (elm == null) {
return null;
}

MarkupContent markup = markup(exp.getRight());
MarkupContent markup = markup(elm, translator.getTranslatedLibrary());
if (markup == null) {
return null;
}

return new Hover(markup, exp.getLeft());
return new Hover(markup, TrackBacks.toRange(elm.getTrackbacks().get(0)));
}

private Pair<Range, ExpressionDef> getExpressionDefForPosition(Position position,
Statements statements) {
if (statements == null || statements.getDef() == null || statements.getDef().isEmpty()) {
return null;
}

for (ExpressionDef def : statements.getDef()) {
if (def.getTrackbacks() == null || def.getTrackbacks().isEmpty()) {
continue;
}

for (TrackBack tb : def.getTrackbacks()) {
if (positionInTrackBack(position, tb)) {
Range range =
new Range(new Position(tb.getStartLine() - 1, tb.getStartChar() - 1),
new Position(tb.getEndLine() - 1, tb.getEndChar()));
return Pair.of(range, def);
}
}
public MarkupContent markup(Element elm, TranslatedLibrary translatedLibrary) {
if (elm instanceof ExpressionDef) {
return markup((ExpressionDef) elm);
} else if (elm instanceof Retrieve) {
return markup((Retrieve) elm);
} else if (elm instanceof ExpressionRef) {
var resolved = translatedLibrary.resolveExpressionRef(((ExpressionDef) elm).getName());
return markup(resolved);
}

return null;
}

private boolean positionInTrackBack(Position p, TrackBack tb) {
int startLine = tb.getStartLine() - 1;
int endLine = tb.getEndLine() - 1;

// Just kidding. We need intervals.
if (p.getLine() >= startLine && p.getLine() <= endLine) {
return true;
} else {
return false;
}
}

public MarkupContent markup(ExpressionDef def) {
if (def == null || def.getExpression() == null) {
return null;
}

/*
* // This def has comments // @andtags define "Expression" returns "Whatever"
*/


DataType resultType = def.getExpression().getResultType();
if (resultType == null) {
return null;
}


// Specifying the Markdown type as cql allows the client to apply
// cql syntax highlighting the resulting pop-up
String result = String.join("\n", "```cql", resultType.toString(), "```");

return new MarkupContent("markdown", result);
}

public MarkupContent markup(Retrieve retrieve) {
return null;
}

public Element elementForPosition(Library library, Position position) {
var visitor = new ExpressionTrackBackVisitor();
return visitor.visitLibrary(library, position);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.CompletionOptions;
import org.eclipse.lsp4j.CompletionParams;
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
Expand All @@ -15,17 +19,20 @@
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.TextDocumentService;
import org.greenrobot.eventbus.EventBus;
import org.opencds.cqf.cql.ls.server.event.DidChangeTextDocumentEvent;
import org.opencds.cqf.cql.ls.server.event.DidCloseTextDocumentEvent;
import org.opencds.cqf.cql.ls.server.event.DidOpenTextDocumentEvent;
import org.opencds.cqf.cql.ls.server.event.DidSaveTextDocumentEvent;
import org.opencds.cqf.cql.ls.server.provider.CompletionProvider;
import org.opencds.cqf.cql.ls.server.provider.FormattingProvider;
import org.opencds.cqf.cql.ls.server.provider.HoverProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableList;

public class CqlTextDocumentService implements TextDocumentService {
private static final Logger log = LoggerFactory.getLogger(CqlTextDocumentService.class);
Expand All @@ -34,21 +41,25 @@ public class CqlTextDocumentService implements TextDocumentService {
private final CompletableFuture<LanguageClient> client;
private final FormattingProvider formattingProvider;
private final HoverProvider hoverProvider;
private final CompletionProvider completionProvider;
private final EventBus eventBus;

public CqlTextDocumentService(CompletableFuture<LanguageClient> client,
HoverProvider hoverProvider, FormattingProvider formattingProvider, EventBus eventBus) {
CompletionProvider completionProvider, HoverProvider hoverProvider,
FormattingProvider formattingProvider, EventBus eventBus) {
this.client = client;
this.formattingProvider = formattingProvider;
this.completionProvider = completionProvider;
this.hoverProvider = hoverProvider;
this.formattingProvider = formattingProvider;
this.eventBus = eventBus;
}

@SuppressWarnings("java:S125") // Keeping the commented code for future reference
public void initialize(InitializeParams params, ServerCapabilities serverCapabilities) {
serverCapabilities.setTextDocumentSync(TextDocumentSyncKind.Full);
// c.setDefinitionProvider(true);
// c.setCompletionProvider(new CompletionOptions(true, ImmutableList.of(".")));
serverCapabilities
.setCompletionProvider(new CompletionOptions(false, ImmutableList.of(".")));
serverCapabilities.setDocumentFormattingProvider(true);
// serverCapabilities.setDocumentRangeFormattingProvider(false);
serverCapabilities.setHoverProvider(true);
Expand All @@ -65,6 +76,14 @@ public CompletableFuture<Hover> hover(HoverParams position) {
.exceptionally(this::notifyClient);
}

@Override
public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completion(
CompletionParams position) {
return CompletableFuture.supplyAsync(() -> this.completionProvider.completion(position))
.exceptionally(this::notifyClient);
}



@Override
public CompletableFuture<List<? extends TextEdit>> formatting(DocumentFormattingParams params) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import org.cqframework.cql.cql2elm.CqlTranslatorException;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DiagnosticSeverity;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;

public class Diagnostics {
Expand All @@ -14,7 +13,7 @@ private Diagnostics() {}

public static Diagnostic convert(CqlTranslatorException error) {
if (error.getLocator() != null) {
Range range = position(error);
Range range = TrackBacks.toRange(error.getLocator());
Diagnostic diagnostic = new Diagnostic();
DiagnosticSeverity severity = severity(error.getSeverity());

Expand Down Expand Up @@ -50,12 +49,4 @@ private static DiagnosticSeverity severity(CqlTranslatorException.ErrorSeverity
return DiagnosticSeverity.Information;
}
}

private static Range position(CqlTranslatorException error) {
// The Language server API assumes 0 based indices and an exclusive range
return new Range(
new Position(error.getLocator().getStartLine() - 1,
Math.max(error.getLocator().getStartChar() - 1, 0)),
new Position(error.getLocator().getEndLine() - 1, error.getLocator().getEndChar()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.opencds.cqf.cql.ls.server.utility;

import org.cqframework.cql.elm.tracking.TrackBack;
import org.eclipse.lsp4j.Position;
import org.hl7.elm.r1.Element;

public class Elements {

private Elements() {}

public static boolean containsTrackBack(Element elm, TrackBack context) {
for (TrackBack tb : elm.getTrackbacks()) {
if (TrackBacks.containsTrackBack(tb, context)) {
return true;
}
}

return false;
}

public static boolean containsPosition(Element elm, Position position) {
for (TrackBack tb : elm.getTrackbacks()) {
if (TrackBacks.containsPosition(tb, position)) {
return true;
}
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.opencds.cqf.cql.ls.server.utility;

import org.cqframework.cql.elm.tracking.TrackBack;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.util.Ranges;

public class TrackBacks {

private TrackBacks() {}

public static Range toRange(TrackBack trackBack) {
// The Language server API assumes 0 based indices and an exclusive range
return new Range(
new Position(trackBack.getStartLine() - 1,
Math.max(trackBack.getStartChar() - 1, 0)),
new Position(trackBack.getEndLine() - 1, trackBack.getEndChar()));
}

public static boolean containsTrackBack(TrackBack bigger, TrackBack smaller) {
var range1 = toRange(bigger);
var range2 = toRange(smaller);

return Ranges.containsRange(range1, range2);
}

public static boolean containsPosition(TrackBack trackBack, Position p) {
Range range = toRange(trackBack);
return Ranges.containsPosition(range, p);
}
}
Loading