org.ballerinalang.langserver.completions.util.CompletionVisitorUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.ballerinalang.langserver.completions.util.CompletionVisitorUtil.java

Source

/*
 * Copyright (c) 2018, WSO2 Inc. (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 org.ballerinalang.langserver.completions.util;

import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.TokenStream;
import org.ballerinalang.langserver.common.UtilSymbolKeys;
import org.ballerinalang.langserver.common.utils.CommonUtil;
import org.ballerinalang.langserver.compiler.DocumentServiceKeys;
import org.ballerinalang.langserver.compiler.LSContext;
import org.ballerinalang.langserver.completions.CompletionKeys;
import org.ballerinalang.langserver.completions.TreeVisitor;
import org.ballerinalang.model.tree.statements.StatementNode;
import org.eclipse.lsp4j.Position;
import org.wso2.ballerinalang.compiler.parser.antlr4.BallerinaParser;
import org.wso2.ballerinalang.compiler.semantics.model.Scope;
import org.wso2.ballerinalang.compiler.semantics.model.SymbolEnv;
import org.wso2.ballerinalang.compiler.tree.BLangNode;
import org.wso2.ballerinalang.compiler.tree.BLangResource;
import org.wso2.ballerinalang.compiler.tree.BLangSimpleVariable;
import org.wso2.ballerinalang.compiler.tree.statements.BLangBlockStmt;
import org.wso2.ballerinalang.compiler.tree.statements.BLangSimpleVariableDef;
import org.wso2.ballerinalang.compiler.tree.types.BLangObjectTypeNode;
import org.wso2.ballerinalang.compiler.util.Name;
import org.wso2.ballerinalang.compiler.util.diagnotic.DiagnosticPos;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;

/**
 * Utility methods for Completion Tree Visiting.
 * 
 * @since 0.985.0
 */
public class CompletionVisitorUtil {

    private CompletionVisitorUtil() {
    }

    /**
     * Check whether the cursor is located within the given node's block scope.
     *
     * Note: This method should only be used to check and terminate the visitor when the content within the block
     *       is empty.
     *
     * @param nodePosition      Position of the current node
     * @param symbolEnv         Symbol Environment
     * @param lsContext         Language Server Operation Context
     * @param treeVisitor       Completion tree visitor instance
     * @return {@link Boolean}  Whether the cursor within the block scope
     */
    public static boolean isCursorWithinBlock(DiagnosticPos nodePosition, @Nonnull SymbolEnv symbolEnv,
            LSContext lsContext, TreeVisitor treeVisitor) {
        DiagnosticPos zeroBasedPosition = CommonUtil.toZeroBasedPosition(nodePosition);
        int line = lsContext.get(DocumentServiceKeys.POSITION_KEY).getPosition().getLine();
        int nodeSLine = zeroBasedPosition.sLine;
        int nodeELine = zeroBasedPosition.eLine;

        if ((nodeSLine <= line && nodeELine >= line)) {
            Map<Name, Scope.ScopeEntry> visibleSymbolEntries = new HashMap<>();
            if (symbolEnv.scope != null) {
                visibleSymbolEntries.putAll(treeVisitor.resolveAllVisibleSymbols(symbolEnv));
            }
            treeVisitor.populateSymbols(visibleSymbolEntries, symbolEnv);
            treeVisitor.forceTerminateVisitor();
            return true;
        }

        return false;
    }

    /**
     * Check whether the cursor resides within the given node type's parameter context.
     * Node name is used to identify the correct node
     *
     * @param nodeName              Name of the node
     * @param nodeType              Node type (Function, Resource, Action or Connector)
     * @param env                   Symbol Environment
     * @param lsContext             Language Server Operation Context
     * @param treeVisitor           Completion tree visitor instance
     * @return {@link Boolean}      Whether the cursor is within the parameter context
     */
    public static boolean isWithinParameterContext(String nodeName, String nodeType, SymbolEnv env,
            LSContext lsContext, TreeVisitor treeVisitor) {
        ParserRuleContext parserRuleContext = lsContext.get(CompletionKeys.PARSER_RULE_CONTEXT_KEY);
        TokenStream tokenStream = lsContext.get(CompletionKeys.TOKEN_STREAM_KEY);
        String terminalToken = "";

        // If the parser rule context is not parameter context or parameter list context, we skipp the calculation
        if (!(parserRuleContext instanceof BallerinaParser.ParameterContext
                || parserRuleContext instanceof BallerinaParser.ParameterListContext)) {
            return false;
        }

        int startTokenIndex = parserRuleContext.getStart().getTokenIndex();
        ArrayList<String> terminalKeywords = new ArrayList<>(
                Arrays.asList(UtilSymbolKeys.ACTION_KEYWORD_KEY, UtilSymbolKeys.CONNECTOR_KEYWORD_KEY,
                        UtilSymbolKeys.FUNCTION_KEYWORD_KEY, UtilSymbolKeys.RESOURCE_KEYWORD_KEY));
        ArrayList<Token> filteredTokens = new ArrayList<>();
        Token openBracket = null;
        boolean isWithinParams = false;

        // Find the index of the closing bracket
        while (true) {
            if (startTokenIndex > tokenStream.size()) {
                // In the ideal case, should not reach this point
                startTokenIndex = -1;
                break;
            }
            Token token = tokenStream.get(startTokenIndex);
            String tokenString = token.getText();
            if (tokenString.equals(")")) {
                break;
            }
            startTokenIndex++;
        }

        // Backtrack the token stream to find a terminal token
        while (true) {
            if (startTokenIndex < 0) {
                break;
            }
            Token token = tokenStream.get(startTokenIndex);
            String tokenString = token.getText();
            if (terminalKeywords.contains(tokenString)) {
                terminalToken = tokenString;
                break;
            }
            if (token.getChannel() == Token.DEFAULT_CHANNEL) {
                filteredTokens.add(token);
            }
            startTokenIndex--;
        }

        Collections.reverse(filteredTokens);

        /*
        This particular logic identifies a matching pair of closing and opening bracket and then check whether the
        cursor is within those bracket pair
         */
        if (nodeName.equals(filteredTokens.get(0).getText()) && terminalToken.equals(nodeType)) {
            String tokenText;
            for (Token token : filteredTokens) {
                tokenText = token.getText();
                if (tokenText.equals("(")) {
                    openBracket = token;
                } else if (tokenText.equals(")") && openBracket != null) {
                    Position cursorPos = lsContext.get(DocumentServiceKeys.POSITION_KEY).getPosition();
                    int openBLine = openBracket.getLine() - 1;
                    int openBCol = openBracket.getCharPositionInLine();
                    int closeBLine = token.getLine() - 1;
                    int closeBCol = token.getCharPositionInLine();
                    int cursorLine = cursorPos.getLine();
                    int cursorCol = cursorPos.getCharacter();

                    isWithinParams = (cursorLine > openBLine && cursorLine < closeBLine)
                            || (cursorLine == openBLine && cursorCol > openBCol && cursorLine < closeBLine)
                            || (cursorLine > openBLine && cursorCol < closeBCol && cursorLine == closeBLine)
                            || (cursorLine == openBLine && cursorLine == closeBLine && cursorCol >= openBCol
                                    && cursorCol <= closeBCol);
                    if (isWithinParams) {
                        break;
                    } else {
                        openBracket = null;
                    }
                }
            }
        }

        if (isWithinParams) {
            treeVisitor.populateSymbols(treeVisitor.resolveAllVisibleSymbols(env), env);
            treeVisitor.forceTerminateVisitor();
        }

        return isWithinParams;
    }

    /**
     * Generate a variable Definition.
     *
     * @param var                           BLang Variable
     * @return {@link BLangSimpleVariableDef}     Generated BLang Variable Definition
     */
    public static BLangSimpleVariableDef createVarDef(BLangSimpleVariable var) {
        BLangSimpleVariableDef varDefNode = new BLangSimpleVariableDef();
        varDefNode.var = var;
        varDefNode.pos = var.pos;
        return varDefNode;
    }

    /**
     * Generate a Block statement from a given set of statements.
     *
     * @param statements                Statements to be populated
     * @return {@link BLangBlockStmt}   Generated block statement  
     */
    public static BLangBlockStmt generateCodeBlock(StatementNode... statements) {
        BLangBlockStmt block = new BLangBlockStmt();
        for (StatementNode stmt : statements) {
            block.addStatement(stmt);
        }
        return block;
    }

    /**
     * Check whether the cursor is at the resource identifier.
     * 
     * @param bLangResource     Resource to be consider
     * @param context           Language Server Operation Context
     * @param treeVisitor       Completion Tree Visitor instance
     * @return {@link Boolean}  Whether the cursor is at the resource identifier or not
     */
    public static boolean isCursorAtResourceIdentifier(BLangResource bLangResource, LSContext context,
            TreeVisitor treeVisitor) {
        Position position = context.get(DocumentServiceKeys.POSITION_KEY).getPosition();
        DiagnosticPos zeroBasedPo = CommonUtil.toZeroBasedPosition(bLangResource.getPosition());
        int line = position.getLine();
        int nodeSLine = zeroBasedPo.sLine;
        boolean status = line == nodeSLine;
        if (status) {
            treeVisitor.forceTerminateVisitor();
        }

        return status;
    }

    public static List<BLangNode> getObjectItemsOrdered(BLangObjectTypeNode objectTypeNode) {
        List<BLangNode> nodes = new ArrayList<>();

        nodes.addAll(
                objectTypeNode.getFields().stream().map(field -> (BLangNode) field).collect(Collectors.toList()));

        nodes.addAll(objectTypeNode.getFunctions().stream().map(function -> (BLangNode) function)
                .collect(Collectors.toList()));

        if (objectTypeNode.initFunction != null) {
            nodes.add(objectTypeNode.initFunction);
        }

        nodes.sort(Comparator.comparing(node -> node.getPosition().getStartLine()));

        return nodes;
    }
}