/* * Copyright (c) 2012, the Dart project authors. * * Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html * * 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.dart.tools.ui.internal.text.completion; import com.google.dart.tools.core.completion.CompletionProposal; import com.google.dart.tools.core.model.DartModelException; import com.google.dart.tools.internal.corext.util.QualifiedTypeNameHistory; import com.google.dart.tools.ui.DartToolsPlugin; import com.google.dart.tools.ui.internal.text.dart.ProposalContextInformation; import com.google.dart.tools.ui.text.dart.DartContentAssistInvocationContext; import com.google.dart.tools.ui.text.editor.tmp.Signature; import org.eclipse.core.runtime.CoreException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.contentassist.IContextInformation; /** * If passed compilation unit is not null, the replacement string will be seen as a qualified type * name. */ public class LazyDartTypeCompletionProposal extends LazyDartCompletionProposal { /** Triggers for types. Do not modify. */ protected static final char[] TYPE_TRIGGERS = new char[] {'.', '\t', '[', '(', ' '}; /** Triggers for types Dart doc. Do not modify. */ protected static final char[] JDOC_TYPE_TRIGGERS = new char[] {'#', '}', ' ', '.'}; private String fQualifiedName; private String fSimpleName; public LazyDartTypeCompletionProposal(CompletionProposal proposal, DartContentAssistInvocationContext context) { super(proposal, context); fQualifiedName = null; } @Override 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 (insertClosingParenthesis) { setUpLinkedMode(document, ')'); } rememberSelection(); } catch (CoreException e) { DartToolsPlugin.log(e); } } @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; } public final String getQualifiedTypeName() { if (fQualifiedName == null) { fQualifiedName = String.valueOf(fProposal.getSignature()); } return fQualifiedName; } @Override protected IContextInformation computeContextInformation() { char[][] typeParameters = fProposal.getParameterNames(); if (typeParameters == null || typeParameters.length == 0) { return super.computeContextInformation(); } ProposalContextInformation contextInformation = new ProposalContextInformation(fProposal); if (fContextInformationPosition != 0 && fProposal.getCompletion().length == 0) { contextInformation.setContextInformationPosition(fContextInformationPosition); } return contextInformation; } @Override protected ProposalInfo computeProposalInfo() { // TODO(scheglov) implement documentation comment // if (fCompilationUnit != null) { // DartProject project = fCompilationUnit.getDartProject(); // 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; } @SuppressWarnings("deprecation") @Override 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 Dart doc references to types. */ // TODO fix if (fProposal.getKind() == CompletionProposal.TYPE_REF && fInvocationContext.getCoreContext().isInJavadocText()) { 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 (isInDartDoc()) { return getSimpleTypeName(); // don't use the braces added for Dart doc link proposals } return replacement; } // fall back for the case we don't have an import rewrite (see allowAddingImports) /* Default: use the fully qualified type name. */ return qualifiedTypeName; } @Override protected String computeSortString() { // try fast sort string to avoid display string creation return getSimpleTypeName() + Character.MIN_VALUE + getQualifiedTypeName(); } @Override protected char[] computeTriggerCharacters() { return isInDartDoc() ? JDOC_TYPE_TRIGGERS : TYPE_TRIGGERS; } protected final String getSimpleTypeName() { if (fSimpleName == null) { fSimpleName = Signature.getSimpleName(getQualifiedTypeName()); } return fSimpleName; } 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 == '.'; } @Override protected boolean isValidPrefix(String prefix) { return isPrefix(prefix, getSimpleTypeName()) || isPrefix(prefix, getQualifiedTypeName()); } /** * Remembers the selection in the content assist history. * * @throws DartModelException if anything goes wrong */ protected final void rememberSelection() throws DartModelException { // Type lhs = fInvocationContext.getExpectedType(); // Type rhs = (Type) getDartElement(); // if (lhs != null && rhs != null) { // DartToolsPlugin.getDefault().getContentAssistHistory().remember(lhs, rhs); // } // // QualifiedTypeNameHistory.remember(getQualifiedTypeName()); } 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); } }