Skip to content

Commit

Permalink
Merge pull request #40284 from malinthar/fix-#39892
Browse files Browse the repository at this point in the history
Add completion support for compiler plugins
  • Loading branch information
malinthar authored Jun 23, 2023
2 parents 5022051 + efebe67 commit ce1bec3
Show file tree
Hide file tree
Showing 43 changed files with 3,332 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.ballerina.projects.plugins.CompilerLifecycleListener;
import io.ballerina.projects.plugins.CompilerPluginContext;
import io.ballerina.projects.plugins.codeaction.CodeAction;
import io.ballerina.projects.plugins.completion.CompletionProvider;

import java.util.ArrayList;
import java.util.List;
Expand All @@ -41,6 +42,8 @@ class CompilerPluginContextIml implements CompilerPluginContext {
private final List<CompilerLifecycleManager.LifecycleListenerInfo> lifecycleListeners = new ArrayList<>();
private final List<CodeAction> codeActions = new ArrayList<>();

private final List<CompletionProvider> completionProviders = new ArrayList<>();

CompilerPluginContextIml(CompilerPluginInfo compilerPluginInfo) {
this.compilerPluginInfo = compilerPluginInfo;
}
Expand Down Expand Up @@ -68,6 +71,11 @@ public void addCodeAction(CodeAction codeAction) {
codeActions.add(codeAction);
}

@Override
public void addCompletionProvider(CompletionProvider completionProvider) {
completionProviders.add(completionProvider);
}

List<CodeAnalyzerManager.CodeAnalyzerInfo> codeAnalyzers() {
return codeAnalyzers;
}
Expand All @@ -88,6 +96,10 @@ public List<CodeAction> codeActions() {
return codeActions;
}

public List<CompletionProvider> completionProviders() {
return completionProviders;
}

public CompilerPluginInfo compilerPluginInfo() {
return compilerPluginInfo;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class CompilerPluginManager {
private CodeModifierManager codeModifierManager;
private CompilerLifecycleManager compilerLifecycleListenerManager;
private CodeActionManager codeActionManager;
private CompletionManager completionManager;

private CompilerPluginManager(PackageCompilation compilation,
List<CompilerPluginContextIml> compilerPluginContexts) {
Expand Down Expand Up @@ -123,6 +124,15 @@ CodeActionManager getCodeActionManager() {
return codeActionManager;
}

CompletionManager getCompletionManager() {
if (completionManager != null) {
return completionManager;
}

completionManager = CompletionManager.from(compilerPluginContexts);
return completionManager;
}

int engagedCodeGeneratorCount() {
int count = 0;
for (CompilerPluginContextIml compilerPluginContext : compilerPluginContexts) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/*
* Copyright (c) 2023, WSO2 LLC. (http://wso2.com) All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.ballerina.projects;

import io.ballerina.compiler.api.symbols.ModuleSymbol;
import io.ballerina.compiler.api.symbols.ServiceDeclarationSymbol;
import io.ballerina.compiler.api.symbols.Symbol;
import io.ballerina.compiler.api.symbols.TypeSymbol;
import io.ballerina.compiler.syntax.tree.Node;
import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode;
import io.ballerina.compiler.syntax.tree.SyntaxKind;
import io.ballerina.projects.plugins.completion.CompletionContext;
import io.ballerina.projects.plugins.completion.CompletionException;
import io.ballerina.projects.plugins.completion.CompletionProvider;
import io.ballerina.tools.text.LinePosition;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
* Manages interaction with completion providers via compiler plugins.
*
* @since 2201.7.0
*/
public class CompletionManager {
Map<Class<?>, List<CompletionProviderDescriptor>> completionProviders;

private CompletionManager(List<CompilerPluginContextIml> compilerPluginContexts) {
completionProviders = new HashMap<>();
compilerPluginContexts.forEach(compilerPluginContextIml -> {
for (CompletionProvider<Node> completionProvider : compilerPluginContextIml.completionProviders()) {
for (Class<?> attachmentPoint : completionProvider.getSupportedNodes()) {
List<CompletionProviderDescriptor> completionProviderList =
completionProviders.computeIfAbsent(attachmentPoint, k -> new ArrayList<>());
completionProviderList.add(new CompletionProviderDescriptor(completionProvider,
compilerPluginContextIml.compilerPluginInfo()));
}
}
});
}

/**
* Create a CompletionManager instance from the provided compiler plugin contexts.
*
* @param compilerPluginContexts compiler plugin contexts.
* @return CompletionManager instance.
*/
public static CompletionManager from(List<CompilerPluginContextIml> compilerPluginContexts) {
return new CompletionManager(compilerPluginContexts);
}

/**
* Get completions for the provided context from all available compiler plugins.
*
* @param context completion context.
* @return Result object containing completion items and errors occurred while processing completion providers
*/
public CompletionResult completions(CompletionContext context) {
CompletionResult result = new CompletionResult();

//If there are no completion items for the referenceNode resolve completion items for the parent node
List<CompletionProviderDescriptor> completionProviderDescriptors = new ArrayList<>();
Node referenceNode = context.nodeAtCursor();
List<Node> resolverChain = new ArrayList<>();
while ((referenceNode != null)) {
completionProviderDescriptors =
completionProviders.getOrDefault(referenceNode.getClass(), Collections.emptyList());
if (!completionProviderDescriptors.isEmpty() && !resolverChain.contains(referenceNode)) {
break;
}
resolverChain.add(referenceNode);
referenceNode = referenceNode.parent();
}

//Atm, we allow completions only inside service declarations
if (referenceNode == null || !isInServiceBodyNodeContext(context, referenceNode)) {
return result;
}

ServiceDeclarationNode serviceDeclarationNode = (ServiceDeclarationNode) referenceNode;
List<ModuleSymbol> moduleSymbols = getModulesOfActiveListeners(context, serviceDeclarationNode);
completionProviderDescriptors.stream().filter(descriptor -> {
if (descriptor.compilerPluginInfo.kind() == CompilerPluginKind.PACKAGE_PROVIDED) {
PackageProvidedCompilerPluginInfo compilerPluginInfo =
(PackageProvidedCompilerPluginInfo) descriptor.compilerPluginInfo();
return moduleSymbols.stream().anyMatch(moduleSymbol ->
moduleSymbol.id().orgName().equals(compilerPluginInfo.packageDesc().org().value())
&& moduleSymbol.id().packageName()
.equals(compilerPluginInfo.packageDesc().name().value()));
}
return moduleSymbols.stream().anyMatch(moduleSymbol ->
moduleSymbol.id().orgName()
.equals(context.currentDocument().module().descriptor().org().value())
&& moduleSymbol.id().packageName()
.equals(context.currentDocument().module().descriptor().packageName().value()));
})
.forEach(descriptor -> {
try {
result.addCompletionItems(descriptor.completionProvider()
.getCompletions(context, serviceDeclarationNode));
} catch (Throwable t) {
String name;
if (descriptor.compilerPluginInfo.kind() == CompilerPluginKind.PACKAGE_PROVIDED) {
PackageProvidedCompilerPluginInfo compilerPluginInfo =
(PackageProvidedCompilerPluginInfo) descriptor.compilerPluginInfo();
name = compilerPluginInfo.packageDesc().org().value() + "/" +
compilerPluginInfo.packageDesc().name().value() + ":" +
compilerPluginInfo.packageDesc().version().value()
+ ":" + descriptor.completionProvider().name();
} else {
name = descriptor.completionProvider().name();
}
CompletionException ex = new CompletionException(t, name);
result.addError(ex);
}
});
return result;
}

private List<ModuleSymbol> getModulesOfActiveListeners(CompletionContext context, ServiceDeclarationNode node) {
Optional<Symbol> serviceSymbol = context.currentSemanticModel().symbol(node);
Optional<LinePosition> linePosition = context.cursorPosition();
if (serviceSymbol.isEmpty() && linePosition.isEmpty()) {
return Collections.emptyList();
}
List<TypeSymbol> listenerTypes = ((ServiceDeclarationSymbol) serviceSymbol.get()).listenerTypes();
return listenerTypes.stream().filter(listenerType -> listenerType.getModule().isPresent())
.map(listenerType -> listenerType.getModule().get()).collect(Collectors.toList());
}

private boolean isInServiceBodyNodeContext(CompletionContext context, Node referenceNode) {
Optional<LinePosition> cursorPosition = context.cursorPosition();
if (referenceNode.kind() != SyntaxKind.SERVICE_DECLARATION || cursorPosition.isEmpty()) {
return false;
}

ServiceDeclarationNode serviceDeclarationNode = (ServiceDeclarationNode) referenceNode;
LinePosition openBrace = serviceDeclarationNode.openBraceToken().lineRange().endLine();
LinePosition closeBrace = serviceDeclarationNode.closeBraceToken().lineRange().startLine();
int cursorLine = cursorPosition.get().line();
int cursorOffset = cursorPosition.get().offset();

//Ensure that the cursor is within braces (in service body)
if (cursorLine < openBrace.line() || cursorLine > closeBrace.line()
|| cursorLine == openBrace.line() && cursorOffset < openBrace.offset()
|| cursorLine == closeBrace.line() && cursorOffset > closeBrace.offset()) {
return false;
}

/* Covers
service on new http:Listener(9090) {
r<cursor>
resource function get test(http:Caller caller, http:Request req) {
}
}
*/
return serviceDeclarationNode.members().stream()
.filter(member -> member.kind() == SyntaxKind.RESOURCE_ACCESSOR_DEFINITION
|| member.kind() == SyntaxKind.FUNCTION_DEFINITION)
.noneMatch(member -> member.lineRange().startLine().line() <= cursorLine
&& cursorLine <= member.lineRange().endLine().line());

}

/**
* Descriptor for a completion provider.
*/
static class CompletionProviderDescriptor {

private final CompletionProvider<Node> completionProvider;
private final CompilerPluginInfo compilerPluginInfo;

public CompletionProviderDescriptor(CompletionProvider completionProvider,
CompilerPluginInfo compilerPluginInfo) {
this.completionProvider = completionProvider;
this.compilerPluginInfo = compilerPluginInfo;
}

public CompletionProvider<Node> completionProvider() {
return completionProvider;
}

public CompilerPluginInfo compilerPluginInfo() {
return compilerPluginInfo;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright (c) 2023, WSO2 LLC. (http://wso2.com) All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.ballerina.projects;

import io.ballerina.projects.plugins.completion.CompletionException;
import io.ballerina.projects.plugins.completion.CompletionItem;

import java.util.ArrayList;
import java.util.List;

/**
* The result of the completion operation.
*
* @since 2201.7.0
*/
public class CompletionResult {

private final List<CompletionItem> completionItems = new ArrayList<>();
private final List<CompletionException> errors = new ArrayList<>();

public void addCompletionItems(List<CompletionItem> completionItems) {
this.completionItems.addAll(completionItems);
}

public void addError(CompletionException ex) {
errors.add(ex);
}

/**
* Get completion items provided by compiler plugins.
*
* @return List of completion items
*/
public List<CompletionItem> getCompletionItems() {
return completionItems;
}

/**
* Get errors catch while processing compiler plugin completion providers.
*
* @return List of errors
*/
public List<CompletionException> getErrors() {
return errors;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ public SemanticModel getSemanticModel(ModuleId moduleId) {
public CodeActionManager getCodeActionManager() {
return compilerPluginManager.getCodeActionManager();
}

public CompletionManager getCompletionManager() {
return compilerPluginManager.getCompletionManager();
}

CompilerPluginManager compilerPluginManager() {
return compilerPluginManager;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package io.ballerina.projects.plugins;

import io.ballerina.projects.plugins.codeaction.CodeAction;
import io.ballerina.projects.plugins.completion.CompletionProvider;

/**
* This class can be used to add various compiler plugin tasks to the current compilation.
Expand Down Expand Up @@ -60,4 +61,11 @@ public interface CompilerPluginContext {
* @param codeAction the {@link CodeAction} instance
*/
void addCodeAction(CodeAction codeAction);

/**
* Add a {@link CompletionProvider} to the current compilation.
*
* @param completionProvider the {@link CompletionProvider} instance
*/
void addCompletionProvider(CompletionProvider completionProvider);
}
Loading

0 comments on commit ce1bec3

Please sign in to comment.