/******************************************************************************* * Copyright (c) 2000, 2010 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 * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.wst.jsdt.internal.ui.text.java; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.text.edits.TextEdit; import org.eclipse.wst.jsdt.core.CompletionProposal; import org.eclipse.wst.jsdt.core.IJavaScriptProject; import org.eclipse.wst.jsdt.core.IJavaScriptUnit; import org.eclipse.wst.jsdt.core.IType; import org.eclipse.wst.jsdt.core.JavaScriptCore; import org.eclipse.wst.jsdt.core.JavaScriptModelException; import org.eclipse.wst.jsdt.core.Signature; import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit; import org.eclipse.wst.jsdt.core.dom.rewrite.ImportRewrite; import org.eclipse.wst.jsdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext; import org.eclipse.wst.jsdt.internal.corext.codemanipulation.StubUtility; import org.eclipse.wst.jsdt.internal.corext.util.JavaModelUtil; import org.eclipse.wst.jsdt.internal.corext.util.QualifiedTypeNameHistory; import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin; import org.eclipse.wst.jsdt.internal.ui.javaeditor.ASTProvider; import org.eclipse.wst.jsdt.ui.PreferenceConstants; import org.eclipse.wst.jsdt.ui.text.java.JavaContentAssistInvocationContext; /** * If passed compilation unit is not null, the replacement string will be seen as a qualified type name. */ public class LazyJavaTypeCompletionProposal extends LazyJavaCompletionProposal { /** Triggers for types. Do not modify. */ protected static final char[] TYPE_TRIGGERS= new char[] { '\t', '[', '(', ' ' }; /** Triggers for types in javadoc. Do not modify. */ protected static final char[] JDOC_TYPE_TRIGGERS= new char[] { '#', '}', ' ', '.' }; /** The compilation unit, or <code>null</code> if none is available. */ protected final IJavaScriptUnit fCompilationUnit; private String fQualifiedName; private String fSimpleName; private ImportRewrite fImportRewrite; private ContextSensitiveImportRewriteContext fImportContext; public LazyJavaTypeCompletionProposal(CompletionProposal proposal, JavaContentAssistInvocationContext context) { super(proposal, context); fCompilationUnit= context.getCompilationUnit(); fQualifiedName= null; } public final String getQualifiedTypeName() { if (fQualifiedName == null) fQualifiedName= String.valueOf(Signature.toCharArray(fProposal.getSignature())); return fQualifiedName; } protected final String getSimpleTypeName() { if (fSimpleName == null) fSimpleName= Signature.getSimpleName(getQualifiedTypeName()); return fSimpleName; } /* * @see org.eclipse.wst.jsdt.internal.ui.text.java.LazyJavaCompletionProposal#computeReplacementString() */ protected String computeReplacementString() { String replacement= super.computeReplacementString(); // /* No import rewriting ever from within the import section. */ // if (isImportCompletion()) // return replacement; /* Always use the simple name for non-formal javadoc references to types. */ // TODO fix if (fProposal.getKind() == CompletionProposal.TYPE_REF && fInvocationContext.getCoreContext().isInJsdocText()) return getSimpleTypeName(); String qualifiedTypeName= replacement; // 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 (isInJavadoc()) return getSimpleTypeName(); // don't use the braces added for javadoc link proposals return replacement; } /* Add imports if the preference is on. */ fImportRewrite= createImportRewrite(); // if (fImportRewrite != null) { // String packageName=null; // try { // IJavaScriptElement javaElement = this.getProposalInfo().getJavaElement(); // packageName=JavaModelUtil.getFilePackage(javaElement); // } catch (JavaScriptModelException e) { // JavaScriptPlugin.log(e); // } // return fImportRewrite.addImport(qualifiedTypeName,packageName, fImportContext); // } // fall back for the case we don't have an import rewrite (see allowAddingImports) /* No imports for implicit imports. */ if (fCompilationUnit != null && JavaModelUtil.isImplicitImport(Signature.getQualifier(qualifiedTypeName), fCompilationUnit)) { return Signature.getSimpleName(qualifiedTypeName); } /* Default: use the fully qualified type name. */ return qualifiedTypeName; } protected final boolean isImportCompletion() { char[] completion= fProposal.getCompletion(); if (completion.length == 0) return false; char last= completion[completion.length - 1]; /* * Proposals end in a semicolon when completing types in normal imports or when completing * static members, in a period when completing types in static imports. */ return last == ';' || last == '.'; } private ImportRewrite createImportRewrite() { if (fCompilationUnit != null && allowAddingImports()) { try { JavaScriptUnit cu= getASTRoot(fCompilationUnit); if (cu == null) { ImportRewrite rewrite= StubUtility.createImportRewrite(fCompilationUnit, true); fImportContext= null; return rewrite; } else { ImportRewrite rewrite= StubUtility.createImportRewrite(cu, true); fImportContext= new ContextSensitiveImportRewriteContext(cu, fInvocationContext.getInvocationOffset(), rewrite); return rewrite; } } catch (CoreException x) { JavaScriptPlugin.log(x); } } return null; } private JavaScriptUnit getASTRoot(IJavaScriptUnit compilationUnit) { return JavaScriptPlugin.getDefault().getASTProvider().getAST(compilationUnit, ASTProvider.WAIT_NO, new NullProgressMonitor()); } /* * @see org.eclipse.wst.jsdt.internal.ui.text.java.LazyJavaCompletionProposal#apply(org.eclipse.jface.text.IDocument, char, int) */ public void apply(IDocument document, char trigger, int offset) { try { boolean insertClosingParenthesis= trigger == '(' && autocloseBrackets(); if (insertClosingParenthesis) { StringBuffer replacement= new StringBuffer(getReplacementString()); updateReplacementWithParentheses(replacement); setReplacementString(replacement.toString()); trigger= '\0'; } super.apply(document, trigger, offset); if (fImportRewrite != null && fImportRewrite.hasRecordedChanges()) { int oldLen= document.getLength(); fImportRewrite.rewriteImports(new NullProgressMonitor()).apply(document, TextEdit.UPDATE_REGIONS); setReplacementOffset(getReplacementOffset() + document.getLength() - oldLen); } if (insertClosingParenthesis) setUpLinkedMode(document, ')'); rememberSelection(); } catch (CoreException e) { JavaScriptPlugin.log(e); } catch (BadLocationException e) { JavaScriptPlugin.log(e); } } protected void updateReplacementWithParentheses(StringBuffer replacement) { 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); } /** * Remembers the selection in the content assist history. * * @throws JavaScriptModelException if anything goes wrong * */ protected final void rememberSelection() throws JavaScriptModelException { IType lhs= fInvocationContext.getExpectedType(); IType rhs= (IType) getJavaElement(); if (lhs != null && rhs != null) JavaScriptPlugin.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 (isInJavadoc()) { // TODO fix // if (!fContext.isInJavadocFormalReference()) // return false; if (fProposal.getKind() == CompletionProposal.TYPE_REF && fInvocationContext.getCoreContext().isInJsdocText()) return false; if (!isJavadocProcessingEnabled()) return false; } IPreferenceStore preferenceStore= JavaScriptPlugin.getDefault().getPreferenceStore(); return preferenceStore.getBoolean(PreferenceConstants.CODEASSIST_ADDIMPORT); } private boolean isJavadocProcessingEnabled() { IJavaScriptProject project= fCompilationUnit.getJavaScriptProject(); boolean processJavadoc; if (project == null) processJavadoc= JavaScriptCore.ENABLED.equals(JavaScriptCore.getOption(JavaScriptCore.COMPILER_DOC_COMMENT_SUPPORT)); else processJavadoc= JavaScriptCore.ENABLED.equals(project.getOption(JavaScriptCore.COMPILER_DOC_COMMENT_SUPPORT, true)); return processJavadoc; } /* * @see org.eclipse.wst.jsdt.internal.ui.text.java.LazyJavaCompletionProposal#isValidPrefix(java.lang.String) */ public boolean isValidPrefix(String prefix) { return isPrefix(prefix, getSimpleTypeName()) || isPrefix(prefix, getQualifiedTypeName()); } /* * @see org.eclipse.wst.jsdt.internal.ui.text.java.JavaCompletionProposal#getCompletionText() */ 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; } /* * @see org.eclipse.wst.jsdt.internal.ui.text.java.LazyJavaCompletionProposal#computeTriggerCharacters() */ protected char[] computeTriggerCharacters() { return isInJavadoc() ? JDOC_TYPE_TRIGGERS : TYPE_TRIGGERS; } /* * @see org.eclipse.wst.jsdt.internal.ui.text.java.LazyJavaCompletionProposal#computeProposalInfo() */ protected ProposalInfo computeProposalInfo() { if (fCompilationUnit != null) { IJavaScriptProject project= fCompilationUnit.getJavaScriptProject(); if (project != null) return new TypeProposalInfo(project, fProposal); } return super.computeProposalInfo(); } /* * @see org.eclipse.wst.jsdt.internal.ui.text.java.LazyJavaCompletionProposal#computeSortString() */ protected String computeSortString() { // try fast sort string to avoid display string creation return getQualifiedTypeName(); } /* * @see org.eclipse.wst.jsdt.internal.ui.text.java.LazyJavaCompletionProposal#computeRelevance() */ 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; } }