/*
* Created on Apr 19, 2007 Copyright (C) 2001-5, Anthony Harrison anh23@pitt.edu
* (jactr.org) This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the License,
* or (at your option) any later version. This library is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU Lesser General Public License for more details. You should have
* received a copy of the GNU Lesser General Public License along with this
* library; if not, write to the Free Software Foundation, Inc., 59 Temple
* Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.jactr.eclipse.ui.editor.assist;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import org.antlr.runtime.tree.CommonTree;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
import org.eclipse.swt.graphics.Point;
import org.jactr.eclipse.ui.UIPlugin;
import org.jactr.eclipse.ui.content.ACTRLabelProvider;
import org.jactr.eclipse.ui.editor.ACTRModelEditor;
import org.jactr.eclipse.ui.editor.markers.ASTPosition;
import org.jactr.eclipse.ui.editor.markers.PositionMarker;
import org.jactr.eclipse.ui.preferences.UIPreferences;
import org.jactr.io.antlr3.misc.ASTSupport;
public class ACTRContentAssistProposer implements IContentAssistProcessor
{
/**
* Logger definition
*/
static private final transient Log LOGGER = LogFactory
.getLog(ACTRContentAssistProposer.class);
private final ACTRModelEditor _editor;
private boolean _autoActivationEnabled = false;
static private char[] AUTO_ACTIVATION_CHARS = "abcdefghijklmnopqrstuvwxyz=-:.ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
.toCharArray();
public ACTRContentAssistProposer(ACTRModelEditor editor)
{
_editor = editor;
_autoActivationEnabled = UIPlugin.getDefault().getPluginPreferences()
.getBoolean(UIPreferences.ENABLE_AUTO_ACTIVATE_PREF);
}
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer,
int offset)
{
try
{
IRegion region = getPrefixRegion(viewer, offset);
String prefix = getPrefix(viewer, region).toLowerCase();
ASTPosition position = getContextualPosition(viewer, offset);
if (LOGGER.isDebugEnabled())
LOGGER
.debug(String
.format(
"computing proposals for %d in (%d-%d), prefixed:%s, ASTPosition:%d",
offset, region.getOffset(),
region.getOffset() + region.getLength(), prefix,
position != null ? position.getNode().getType() : -1));
Map<String, CommonTree> recommendations = getRecommendationsUsingPositions(
position, viewer, offset, prefix);
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format("Yielded %d recommendations",
recommendations.size()));
if (recommendations.size() == 0) return null;
if (LOGGER.isDebugEnabled())
LOGGER.debug("Proposing " + recommendations);
Point selection = viewer.getSelectedRange();
TreeMap<CommonTree, ICompletionProposal> proposals = new TreeMap<CommonTree, ICompletionProposal>(
new Comparator<CommonTree>() {
public int compare(CommonTree o1, CommonTree o2)
{
if (o1 == o2) return 0;
try
{
int compare = ASTSupport.getName(o1).compareToIgnoreCase(
ASTSupport.getName(o2));
if (compare != 0) return compare;
}
catch (Exception e)
{
// its a variable (no name node)
}
// compare types..
int o1Type = o1.getType();
int o2Type = o2.getType();
if (o1Type < o2Type) return -1;
if (o1Type > o2Type) return 1;
return o1.hashCode() < o2.hashCode() ? -1 : 1;
}
});
// TreeMap<String, ICompletionProposal> proposals = new TreeMap<String,
// ICompletionProposal>();
for (Map.Entry<String, CommonTree> entry : recommendations.entrySet())
{
int start = Math.min(region.getOffset(), selection.x);
int length = region.getLength() + selection.y;
CommonTree node = entry.getValue();
String textToInsert = entry.getKey();
if (LOGGER.isDebugEnabled())
LOGGER.debug("Initial proposal (" + prefix + ") : " + textToInsert
+ " at " + start + " replacing " + length);
if (node != null)
try
{
textToInsert = ASTSupport.getName(entry.getValue());
if (prefix.length() > 0 && textToInsert.startsWith(prefix))
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("shifting start and length " + region.getLength());
textToInsert = textToInsert.substring(region.getLength());
start += region.getLength();
length -= region.getLength();
}
}
catch (Exception e)
{
}
if (textToInsert.length() == 0) continue;
if (textToInsert.equals(prefix)) continue;
if (LOGGER.isDebugEnabled())
LOGGER.debug("Proposing : " + textToInsert + " at " + start
+ " replacing " + length);
/*
* note: the displayString is not being used, but rather the
* textToInsert. this is because for some reason, the display string is
* being used for the intermediary completion (say, when there are
* multiple completions that might work). don't know why..
*/
ACTRCompletionProposal proposal = new ACTRCompletionProposal(
textToInsert, start, length, textToInsert.length(),
ACTRLabelProvider.getImageOfAST(node), textToInsert, null, null,
true);
proposals.put(node, proposal);
}
return proposals.values().toArray(
new ICompletionProposal[proposals.size()]);
}
catch (Exception e)
{
if (LOGGER.isDebugEnabled()) LOGGER.debug("something went wrong ", e);
}
return null;
}
protected ASTPosition getContextualPosition(ITextViewer viewer, int offset)
{
ASTPosition position = PositionMarker.getPosition(viewer.getDocument(),
_editor.getBase(), offset);
return position;
}
protected Map<String, CommonTree> getRecommendationsUsingPositions(
ASTPosition position, ITextViewer viewer, int offset, String prefix)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("returned " + position + " for offset: " + offset
+ " in document " + _editor.getBase());
if (position == null) return Collections.EMPTY_MAP;
Map<String, CommonTree> props = ProposalGenerator.generateProposals(
position, offset, _editor.getCompilationUnit());
Iterator<String> keys = props.keySet().iterator();
while (keys.hasNext())
{
String key = keys.next();
if (key.length() == 0 || prefix != null && prefix.length() != 0
&& !key.toLowerCase().startsWith(prefix)) keys.remove();
}
return props;
}
private IRegion getPrefixRegion(ITextViewer viewer, int offset)
throws BadLocationException
{
IDocument doc = viewer.getDocument();
if (doc == null || offset > doc.getLength()) return null;
int length = 0;
char current = 0;
while (--offset >= 0
&& (current = doc.getChar(offset)) != 0
&& (Character.isJavaIdentifierPart(current) || current == '-' || current == '='))
length++;
return new Region(offset + 1, length);
}
private String getPrefix(ITextViewer viewer, IRegion region)
throws BadLocationException
{
return viewer.getDocument().get(region.getOffset(), region.getLength());
}
public IContextInformation[] computeContextInformation(ITextViewer viewer,
int offset)
{
return null;
}
public char[] getCompletionProposalAutoActivationCharacters()
{
if (_autoActivationEnabled) return AUTO_ACTIVATION_CHARS;
return null;
}
public char[] getContextInformationAutoActivationCharacters()
{
return null;
}
public IContextInformationValidator getContextInformationValidator()
{
return null;
}
public String getErrorMessage()
{
return null;
}
}