// Copyright 2012 Google Inc. 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 com.google.collide.client.code.autocomplete.codegraph;
import com.google.collide.client.code.autocomplete.AbstractTrie;
import com.google.collide.client.code.autocomplete.PrefixIndex;
import com.google.collide.client.code.autocomplete.codegraph.js.JsCodeScope;
import com.google.collide.client.code.autocomplete.codegraph.js.JsIndexUpdater;
import com.google.collide.client.code.autocomplete.codegraph.py.PyCodeScope;
import com.google.collide.client.code.autocomplete.codegraph.py.PyIndexUpdater;
import com.google.collide.client.code.autocomplete.integration.TaggableLineUtil;
import com.google.collide.client.documentparser.ParseResult;
import com.google.collide.client.util.logging.Log;
import com.google.collide.codemirror2.State;
import com.google.collide.codemirror2.SyntaxType;
import com.google.collide.dto.CodeBlock;
import com.google.collide.dto.CodeGraph;
import com.google.collide.dto.CodeBlock.Type;
import com.google.collide.json.client.JsoStringSet;
import com.google.collide.json.shared.JsonArray;
import com.google.collide.shared.document.Line;
import com.google.collide.shared.document.Position;
import com.google.collide.shared.grok.GrokUtils;
import com.google.collide.shared.util.JsonCollections;
import com.google.common.base.Preconditions;
import javax.annotation.Nonnull;
/**
* Builds a list of completion proposals from a few sources
* (context file, external files and language constructs)
*
*/
public class ScopeTrieBuilder {
private static void addLexicalPrefixes(
JsoStringSet prefixes, JsonArray<String> path, String commonSuffix) {
for (int i = 0; i <= path.size(); i++) {
String currentPrefix = (i == 0) ? "" : path.slice(0, i).join(".") + ".";
prefixes.add(currentPrefix + commonSuffix);
}
}
private static void addThisPrefixes(JsoStringSet prefixes, JsonArray<String> path) {
String thisPrefix = path.slice(0, path.size() - 1).join(".") + ".";
prefixes.add(thisPrefix);
}
private static void debugLog(CodeBlock codeBlock, String indent) {
Log.debug(ScopeTrieBuilder.class, indent + CodeBlock.Type.valueOf(codeBlock.getBlockType())
+ " " + codeBlock.getName()
+ "#" + codeBlock.getId()
+ "[" + codeBlock.getStartLineNumber() + ":" + codeBlock.getStartColumn() + ","
+ codeBlock.getEndLineNumber() + ":" + codeBlock.getEndColumn() + "]");
}
private static void debugLogTree(CodeBlock root, String indent) {
if (root == null) {
Log.debug(ScopeTrieBuilder.class, "null code block");
return;
}
debugLog(root, indent);
indent = " " + indent;
for (int i = 0; i < root.getChildren().size(); i++) {
debugLogTree(root.getChildren().get(i), indent);
}
}
private PrefixIndex<CodeGraphProposal> externalTrie = new AbstractTrie<CodeGraphProposal>();
private final CodeFile contextFile;
private final SyntaxType mode;
public ScopeTrieBuilder(CodeFile contextFile, SyntaxType mode) {
this.contextFile = contextFile;
this.mode = mode;
}
public JsoStringSet calculateScopePrefixes(CompletionContext context, Position cursor) {
JsoStringSet result = JsoStringSet.create();
boolean isThisContext = context.isThisContext();
Line line = cursor.getLine();
if (!isThisContext) {
result.add(context.getPreviousContext());
}
//PY specific.
PyCodeScope pyScope = line.getTag(PyIndexUpdater.TAG_SCOPE);
if (pyScope != null) {
JsonArray<String> path = PyCodeScope.buildPrefix(pyScope);
if (isThisContext && pyScope.getType() == PyCodeScope.Type.DEF && path.size() > 1) {
addThisPrefixes(result, path);
} else {
addLexicalPrefixes(result, path, context.getPreviousContext());
}
return result;
}
// Fallback - use pre-calculated results (valid at the end of line).
JsCodeScope jsScope = line.getTag(JsIndexUpdater.TAG_SCOPE);
@SuppressWarnings("unchecked")
// Trying to get results up to cursor position.
ParseResult<State> parseResult = context.getParseResult();
if (parseResult != null) {
jsScope = JsIndexUpdater.calculateContext(
TaggableLineUtil.getPreviousLine(line), parseResult.getTokens()).getScope();
}
if (jsScope != null) {
JsonArray<String> path = JsCodeScope.buildPrefix(jsScope);
if (isThisContext && path.size() > 1) {
addThisPrefixes(result, path);
} else {
addLexicalPrefixes(result, path, context.getPreviousContext());
}
}
int lineNumber = cursor.getLineNumber();
int column = cursor.getColumn();
column = Math.max(0, column - context.getPreviousContext().length());
final Scope scope = contextFile.findScope(lineNumber, column, true);
// Can't calculate scope or matching codeBlock or it is root scope.
if (scope == null || scope == contextFile.getRootScope()) {
return result;
}
CodeBlock codeBlock = scope.getCodeBlock();
JsonArray<String> prefix = buildPrefix(codeBlock);
// Add prefixes corresponding to outer scopes.
if (isThisContext && Type.VALUE_FUNCTION == codeBlock.getBlockType() && prefix.size() > 1) {
addThisPrefixes(result, prefix);
} else {
addLexicalPrefixes(result, prefix, context.getPreviousContext());
}
return result;
}
/**
* Builds sequence on names that represents path to given {@link CodeBlock}.
*
* <p>Given block name is the last item in resulting sequence.
*/
private JsonArray<String> buildPrefix(@Nonnull CodeBlock codeBlock) {
Preconditions.checkNotNull(codeBlock);
CodeBlock current = codeBlock;
JsonArray<String> prefix = JsonCollections.createArray();
while (current != null && Type.VALUE_FILE != current.getBlockType()) {
prefix.add(current.getName());
current = contextFile.getReferences().getParent(current);
}
prefix.reverse();
return prefix;
}
public void setCodeGraph(CodeGraph codeGraph) {
CodeBlock contextFileCodeBlock = GrokUtils.findFileCodeBlock(codeGraph,
contextFile.getFilePath().getPathString());
contextFile.setRootCodeBlock(contextFileCodeBlock);
externalTrie = new CodeGraphPrefixIndex(codeGraph, mode, contextFile.getFilePath());
}
public PrefixIndex<CodeGraphProposal> getCodeGraphTrie() {
Preconditions.checkNotNull(externalTrie);
return externalTrie;
}
}