/*
* Copyright (c) 2012 Sam Harwell, Tunnel Vision Laboratories LLC
* All rights reserved.
*
* The source code of this document is proprietary work, and is not licensed for
* distribution. For information about licensing, contact Sam Harwell at:
* sam@tunnelvisionlabs.com
*/
package org.antlr.works.editor.antlr4.completion;
import com.tvl.spi.editor.completion.CompletionProvider;
import com.tvl.spi.editor.completion.CompletionTask;
import com.tvl.spi.editor.completion.support.AsyncCompletionTask;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import org.antlr.v4.runtime.Token;
import org.netbeans.editor.Utilities;
import org.openide.util.NbBundle;
/**
*
* @author Sam Harwell
*/
@NbBundle.Messages({
"GCP-imported-items=",
"GCP-instance-members="
})
public abstract class AbstractCompletionProvider implements CompletionProvider {
// -J-Dorg.antlr.works.editor.antlr4.completion.AbstractCompletionProvider.level=FINE
private static final Logger LOGGER = Logger.getLogger(AbstractCompletionProvider.class.getName());
public static final int AUTO_QUERY_TYPE = 0x00010000;
public static final int TRIGGERED_QUERY_TYPE = 0x00020000;
@Override
public int getAutoQueryTypes(JTextComponent component, String typedText) {
if (typedText == null || typedText.length() != 1) {
return 0;
}
int queryType = COMPLETION_QUERY_TYPE | AUTO_QUERY_TYPE;
boolean triggered = getCompletionAutoPopupTriggers().indexOf(typedText.charAt(0)) >= 0;
if (triggered) {
queryType |= TRIGGERED_QUERY_TYPE;
}
if (triggered || (autoPopupOnIdentifierPart() && isIdentifierPart(typedText))) {
int offset = component.getSelectionStart() - 1;
Token contextToken = getContext(component, offset);
if (contextToken == null) {
return 0;
}
if (!triggered) {
// the caret must be at the end of the identifier. note that the
// offset is already 1 position before the caret, so no need to
// add 1 to contextToken.getStopIndex().
if (offset != contextToken.getStopIndex()) {
return 0;
}
// only trigger for the first character of the identifier
if (contextToken.getStopIndex() > contextToken.getStartIndex()) {
return 0;
}
}
if (isContext(contextToken, offset, queryType)) {
return queryType;
}
}
return 0;
}
@Override
public CompletionTask createTask(int queryType, JTextComponent component) {
if ((queryType & COMPLETION_QUERY_TYPE) != 0 || (queryType & TOOLTIP_QUERY_TYPE) != 0 || (queryType & DOCUMENTATION_QUERY_TYPE) != 0) {
int caretOffset = component.getSelectionStart();
boolean extend;
try {
int[] identifier = Utilities.getIdentifierBlock(component, caretOffset);
extend = identifier != null && caretOffset > identifier[0] && caretOffset <= identifier[1];
} catch (BadLocationException ex) {
// this happens when the caret is before the first word of the document
LOGGER.log(Level.WARNING, ex.getMessage(), ex);
extend = false;
}
return new AsyncCompletionTask(createCompletionQuery(queryType, caretOffset, extend), component);
}
return null;
}
public Future<CompletionQueryResult> executeQuery(int queryType, JTextComponent component, int caretOffset, boolean extend) {
final AbstractCompletionQuery query = createCompletionQuery(queryType, caretOffset, extend);
query.prepareQuery(component);
final Future<Void> task = query.query(component.getDocument(), caretOffset);
if (task == null) {
return null;
}
return new Future<CompletionQueryResult>() {
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return task.cancel(mayInterruptIfRunning);
}
@Override
public boolean isCancelled() {
return task.isCancelled();
}
@Override
public boolean isDone() {
return task.isDone();
}
@Override
public CompletionQueryResult get() throws InterruptedException, ExecutionException {
task.get();
return new CompletionQueryResult(query);
}
@Override
public CompletionQueryResult get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
task.get(timeout, unit);
return new CompletionQueryResult(query);
}
};
}
public abstract boolean autoPopupOnIdentifierPart();
public boolean isIdentifierPart(String text) {
for (int i = 0; i < text.length(); i++) {
if (!Character.isJavaIdentifierPart(text.charAt(i))) {
return false;
}
}
return true;
}
public abstract String getCompletionAutoPopupTriggers();
public abstract String getCompletionSelectors();
protected abstract AbstractCompletionQuery createCompletionQuery(int queryType, int caretOffset, boolean extend);
public Token getContext(JTextComponent component, int offset) {
return getContext(component.getDocument(), offset);
}
public abstract Token getContext(Document document, int offset);
public boolean isContext(JTextComponent component, int offset, int queryType) {
return isContext(getContext(component, offset), offset, queryType);
}
public abstract boolean isContext(Token token, int offset, int queryType);
}