/*******************************************************************************
* Copyright (c) 2000, 2007 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
*******************************************************************************/
package org.eclipse.dltk.ui.text.completion;
import org.eclipse.dltk.core.CompletionProposal;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.IScriptProject;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.IType;
import org.eclipse.dltk.core.ModelException;
import org.eclipse.dltk.internal.corext.util.QualifiedTypeNameHistory;
import org.eclipse.dltk.ui.DLTKUIPlugin;
import org.eclipse.dltk.ui.PreferenceConstants;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
/**
* If passed compilation unit is not null, the replacement string will be seen
* as a qualified type name.
*/
public abstract class LazyScriptTypeCompletionProposal extends
LazyScriptCompletionProposal {
protected final ISourceModule fSourceModule;
// private String fQualifiedName;
private String fSimpleName;
public LazyScriptTypeCompletionProposal(CompletionProposal proposal,
ScriptContentAssistInvocationContext context) {
super(proposal, context);
fSourceModule = context.getSourceModule();
// fQualifiedName = null;
}
public final String getQualifiedTypeName() {
// if (fQualifiedName == null)
// fQualifiedName = String.valueOf(Signature.toCharArray(Signature
// .getTypeErasure(fProposal.getSignature())));
// return fQualifiedName;
return null;
}
protected final String getSimpleTypeName() {
return fSimpleName;
}
@Override
protected String computeReplacementString() {
String replacement = super.computeReplacementString();
/*
* Always use the simple name for non-formal scriptdoc references to
* types.
*/
// TODO fix
if (fProposal.getKind() == CompletionProposal.TYPE_REF
&& fInvocationContext.getCoreContext() != null
&& fInvocationContext.getCoreContext().isInDoc())
return getSimpleTypeName();
String qualifiedTypeName = getQualifiedTypeName();
if (qualifiedTypeName.indexOf('.') == -1)
// default package - no imports needed
return qualifiedTypeName;
/*
* If the user types in the qualification, don't force import rewriting
* on him - insert the qualified name.
*/
IDocument document = fInvocationContext.getDocument();
if (document != null) {
String prefix = getPrefix(document, getReplacementOffset()
+ getReplacementLength());
int dotIndex = prefix.lastIndexOf('.');
// match up to the last dot in order to make higher level matching
// still work (camel case...)
if (dotIndex != -1
&& qualifiedTypeName.toLowerCase().startsWith(
prefix.substring(0, dotIndex + 1).toLowerCase()))
return qualifiedTypeName;
}
/*
* The replacement does not contain a qualification (e.g. an inner type
* qualified by its parent) - use the replacement directly.
*/
if (replacement.indexOf('.') == -1) {
if (isInDoc())
return getSimpleTypeName(); // don't use the braces added for
// javadoc link proposals
return replacement;
}
return qualifiedTypeName;
}
@Override
public void apply(IDocument document, char trigger, int offset) {
boolean insertClosingParenthesis = trigger == '('
&& autocloseBrackets();
if (insertClosingParenthesis) {
updateReplacementWithParentheses();
trigger = '\0';
}
super.apply(document, trigger, offset);
if (insertClosingParenthesis) {
setUpLinkedMode(document, ')');
}
try {
rememberSelection();
} catch (ModelException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void updateReplacementWithParentheses() {
StringBuffer replacement = new StringBuffer(getReplacementString());
// FormatterPrefs prefs= getFormatterPrefs();
// if (prefs.beforeOpeningParen)
// replacement.append(SPACE);
replacement.append(LPAREN);
// if (prefs.afterOpeningParen)
// replacement.append(SPACE);
setCursorPosition(replacement.length());
// if (prefs.afterOpeningParen)
// replacement.append(SPACE);
replacement.append(RPAREN);
setReplacementString(replacement.toString());
}
/**
* Remembers the selection in the content assist history.
*
* @throws ModelException
* if anything goes wrong
*
*/
protected final void rememberSelection() throws ModelException {
IType lhs = fInvocationContext.getExpectedType();
IType rhs = (IType) getModelElement();
if (lhs != null && rhs != null)
DLTKUIPlugin.getDefault().getContentAssistHistory().remember(lhs,
rhs);
QualifiedTypeNameHistory.remember(getQualifiedTypeName());
}
/**
* Returns <code>true</code> if imports may be added. The return value
* depends on the context and preferences only and does not take into
* account the contents of the compilation unit or the kind of proposal.
* Even if <code>true</code> is returned, there may be cases where no
* imports are added for the proposal. For example:
* <ul>
* <li>when completing within the import section</li>
* <li>when completing informal javadoc references (e.g. within
* <code><code></code> tags)</li>
* <li>when completing a type that conflicts with an existing import</li>
* <li>when completing an implicitly imported type (same package,
* <code>java.lang</code> types)</li>
* </ul>
* <p>
* The decision whether a qualified type or the simple type name should be
* inserted must take into account these different scenarios.
* </p>
* <p>
* Subclasses may extend.
* </p>
*
* @return <code>true</code> if imports may be added, <code>false</code>
* if not
*/
protected boolean allowAddingImports() {
if (isInDoc()) {
// TODO fix
// if (!fContext.isInJavadocFormalReference())
// return false;
if (fProposal.getKind() == CompletionProposal.TYPE_REF
&& fInvocationContext.getCoreContext() != null
&& fInvocationContext.getCoreContext().isInDoc())
return false;
if (!isDocProcessingEnabled())
return false;
}
IPreferenceStore preferenceStore = DLTKUIPlugin.getDefault()
.getPreferenceStore();
return preferenceStore
.getBoolean(PreferenceConstants.CODEASSIST_ADDIMPORT);
}
private boolean isDocProcessingEnabled() {
// IScriptProject project = fSourceModule.getScriptProject();
// boolean processJavadoc;
if (DLTKCore.DEBUG) {
System.out.println("TODO: Add documentation support."); //$NON-NLS-1$
}
// if (project == null)
// processJavadoc=
// DLTKCore.ENABLED.equals(DLTKCore.getOption(DLTKCore.COMPILER_DOC_COMMENT_SUPPORT));
// else
// processJavadoc=
// DLTKCore.ENABLED.equals(project.getOption(DLTKCore.COMPILER_DOC_COMMENT_SUPPORT,
// true));
// return processJavadoc;
return false;
}
@Override
protected boolean isValidPrefix(String prefix) {
return isPrefix(prefix, getSimpleTypeName())
|| isPrefix(prefix, getQualifiedTypeName());
}
@Override
public CharSequence getPrefixCompletionText(IDocument document,
int completionOffset) {
String prefix = getPrefix(document, completionOffset);
String completion;
// return the qualified name if the prefix is already qualified
if (prefix.indexOf('.') != -1)
completion = getQualifiedTypeName();
else
completion = getSimpleTypeName();
if (isCamelCaseMatching())
return getCamelCaseCompound(prefix, completion);
return completion;
}
protected abstract char[] getTypeTriggers();
protected abstract char[] getDocTriggers();
@Override
protected char[] computeTriggerCharacters() {
return isInDoc() ? getDocTriggers() : getTypeTriggers();
}
@Override
protected ICompletionProposalInfo computeProposalInfo() {
if (fSourceModule != null) {
IScriptProject project = fSourceModule.getScriptProject();
if (project != null)
return new TypeProposalInfo(project, fProposal);
}
return super.computeProposalInfo();
}
@Override
protected int computeRelevance() {
/*
* There are two histories: the RHS history remembers types used for the
* current expected type (left hand side), while the type history
* remembers recently used types in general).
*
* The presence of an RHS ranking is a much more precise sign for
* relevance as it proves the subtype relationship between the proposed
* type and the expected type.
*
* The "recently used" factor (of either the RHS or general history) is
* less important, it should not override other relevance factors such
* as if the type is already imported etc.
*/
float rhsHistoryRank = fInvocationContext
.getHistoryRelevance(getQualifiedTypeName());
float typeHistoryRank = QualifiedTypeNameHistory.getDefault()
.getNormalizedPosition(getQualifiedTypeName());
int recencyBoost = Math.round((rhsHistoryRank + typeHistoryRank) * 5);
int rhsBoost = rhsHistoryRank > 0.0f ? 50 : 0;
int baseRelevance = super.computeRelevance();
return baseRelevance + rhsBoost + recencyBoost;
}
}