/* * 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.CompletionContext; import com.google.dart.tools.core.completion.CompletionProposal; import com.google.dart.tools.core.model.DartModelException; import com.google.dart.tools.ui.DartToolsPlugin; import com.google.dart.tools.ui.internal.text.dart.DartTextMessages; import com.google.dart.tools.ui.internal.text.dart.ParameterGuesser; import com.google.dart.tools.ui.internal.text.editor.DartEditor; import com.google.dart.tools.ui.internal.text.editor.EditorHighlightingSynchronizer; import com.google.dart.tools.ui.text.dart.DartContentAssistInvocationContext; import com.google.dart.tools.ui.text.editor.tmp.Signature; import org.eclipse.core.runtime.Platform; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.BadPositionCategoryException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IPositionUpdater; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.text.link.ILinkedModeListener; import org.eclipse.jface.text.link.InclusivePositionUpdater; import org.eclipse.jface.text.link.LinkedModeModel; import org.eclipse.jface.text.link.LinkedModeUI; import org.eclipse.jface.text.link.LinkedPosition; import org.eclipse.jface.text.link.LinkedPositionGroup; import org.eclipse.jface.text.link.ProposalPosition; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.texteditor.link.EditorLinkedModeUI; /** * This is a {@link DartCompletionProposal} which includes templates that represent the best guess * completion for each parameter of a method. */ public final class ParameterGuessingProposal extends DartMethodCompletionProposal { /** Tells whether this class is in debug mode. */ private static final boolean DEBUG = "true".equalsIgnoreCase(Platform.getDebugOption("com.google.dart.tools.core/debug/ResultCollector")); //$NON-NLS-1$//$NON-NLS-2$ /** * Creates a {@link ParameterGuessingProposal} or <code>null</code> if the core context isn't * available or extended. * * @param proposal the original completion proposal * @param context the current context * @param fillBestGuess if set, the best guess will be filled in * @return a proposal or <code>null</code> */ public static ParameterGuessingProposal createProposal(CompletionProposal proposal, DartContentAssistInvocationContext context, boolean fillBestGuess) { CompletionContext coreContext = context.getCoreContext(); if (coreContext != null && coreContext.isExtended()) { return new ParameterGuessingProposal(proposal, context, coreContext, fillBestGuess); } return null; } private ICompletionProposal[][] fChoices; // initialized by guessParameters() private Position[] fPositions; // initialized by guessParameters() private IRegion fSelectedRegion; // initialized by apply() private IPositionUpdater fUpdater; private final boolean fFillBestGuess; private final CompletionContext fCoreContext; private ParameterGuessingProposal(CompletionProposal proposal, DartContentAssistInvocationContext context, CompletionContext coreContext, boolean fillBestGuess) { super(proposal, context); fCoreContext = coreContext; fFillBestGuess = fillBestGuess; } @Override public void apply(IDocument document, char trigger, int offset) { try { super.apply(document, trigger, offset); int baseOffset = getReplacementOffset(); String replacement = getReplacementString(); if (fPositions != null && getTextViewer() != null) { LinkedModeModel model = new LinkedModeModel(); for (int i = 0; i < fPositions.length; i++) { LinkedPositionGroup group = new LinkedPositionGroup(); int positionOffset = fPositions[i].getOffset(); int positionLength = fPositions[i].getLength(); if (fChoices[i].length < 2) { group.addPosition(new LinkedPosition( document, positionOffset, positionLength, LinkedPositionGroup.NO_STOP)); } else { ensurePositionCategoryInstalled(document, model); document.addPosition(getCategory(), fPositions[i]); group.addPosition(new ProposalPosition( document, positionOffset, positionLength, LinkedPositionGroup.NO_STOP, fChoices[i])); } model.addGroup(group); } model.forceInstall(); DartEditor editor = getDartEditor(); if (editor != null) { model.addLinkingListener(new EditorHighlightingSynchronizer(editor)); } LinkedModeUI ui = new EditorLinkedModeUI(model, getTextViewer()); ui.setExitPosition(getTextViewer(), baseOffset + replacement.length(), 0, Integer.MAX_VALUE); ui.setExitPolicy(new ExitPolicy(')', document)); ui.setCyclingMode(LinkedModeUI.CYCLE_WHEN_NO_PARENT); ui.setDoContextInfo(true); ui.enter(); fSelectedRegion = ui.getSelectedRegion(); } else { fSelectedRegion = new Region(baseOffset + replacement.length(), 0); } } catch (BadLocationException e) { ensurePositionCategoryRemoved(document); DartToolsPlugin.log(e); openErrorDialog(e); } catch (BadPositionCategoryException e) { ensurePositionCategoryRemoved(document); DartToolsPlugin.log(e); openErrorDialog(e); } } @Override public Point getSelection(IDocument document) { if (fSelectedRegion == null) { return new Point(getReplacementOffset(), 0); } return new Point(fSelectedRegion.getOffset(), fSelectedRegion.getLength()); } @Override protected String computeReplacementString() { if (!hasParameters() || !hasArgumentList()) { return super.computeReplacementString(); } long millis = DEBUG ? System.currentTimeMillis() : 0; String replacement; try { replacement = computeGuessingCompletion(); } catch (DartModelException x) { fPositions = null; fChoices = null; DartToolsPlugin.log(x); openErrorDialog(x); return super.computeReplacementString(); } if (DEBUG) { System.err.println("Parameter Guessing: " + (System.currentTimeMillis() - millis)); //$NON-NLS-1$ } return replacement; } @Override protected boolean needsLinkedMode() { return false; // we handle it ourselves } /** * Creates the completion string. Offsets and Lengths are set to the offsets and lengths of the * parameters. * * @return the completion string * @throws DartModelException if parameter guessing failed */ private String computeGuessingCompletion() throws DartModelException { StringBuffer buffer = new StringBuffer(); appendMethodNameReplacement(buffer); FormatterPrefs prefs = getFormatterPrefs(); setCursorPosition(buffer.length()); if (prefs.afterOpeningParen) { buffer.append(SPACE); } char[][] parameterNames = fProposal.findParameterNames(null); fChoices = guessParameters(parameterNames); int count = fChoices.length; int replacementOffset = getReplacementOffset(); for (int i = 0; i < count; i++) { if (i != 0) { if (prefs.beforeComma) { buffer.append(SPACE); } buffer.append(COMMA); if (prefs.afterComma) { buffer.append(SPACE); } } ICompletionProposal proposal = fChoices[i][0]; String argument = proposal.getDisplayString(); Position position = fPositions[i]; position.setOffset(replacementOffset + buffer.length()); position.setLength(argument.length()); if (proposal instanceof DartCompletionProposal) { ((DartCompletionProposal) proposal).setReplacementOffset(replacementOffset + buffer.length()); } buffer.append(argument); } if (prefs.beforeClosingParen) { buffer.append(SPACE); } buffer.append(RPAREN); return buffer.toString(); } private void ensurePositionCategoryInstalled(final IDocument document, LinkedModeModel model) { if (!document.containsPositionCategory(getCategory())) { document.addPositionCategory(getCategory()); fUpdater = new InclusivePositionUpdater(getCategory()); document.addPositionUpdater(fUpdater); model.addLinkingListener(new ILinkedModeListener() { @Override public void left(LinkedModeModel environment, int flags) { ensurePositionCategoryRemoved(document); } @Override public void resume(LinkedModeModel environment, int flags) { } @Override public void suspend(LinkedModeModel environment) { } }); } } private void ensurePositionCategoryRemoved(IDocument document) { if (document.containsPositionCategory(getCategory())) { try { document.removePositionCategory(getCategory()); } catch (BadPositionCategoryException e) { // ignore } document.removePositionUpdater(fUpdater); } } private String getCategory() { return "ParameterGuessingProposal_" + toString(); //$NON-NLS-1$ } /** * Returns the currently active java editor, or <code>null</code> if it cannot be determined. * * @return the currently active java editor, or <code>null</code> */ private DartEditor getDartEditor() { IEditorPart part = DartToolsPlugin.getActivePage().getActiveEditor(); if (part instanceof DartEditor) { return (DartEditor) part; } else { return null; } } private String[] getParameterTypes() { char[] signature = fProposal.getSignature(); char[][] types = Signature.getParameterTypes(signature); String[] ret = new String[types.length]; for (int i = 0; i < types.length; i++) { ret[i] = new String(Signature.toCharArray(types[i])); } return ret; } private ICompletionProposal[][] guessParameters(char[][] parameterNames) throws DartModelException { // find matches in reverse order. Do this because people tend to declare the variable meant for the last // parameter last. That is, local variables for the last parameter in the method completion are more // likely to be closer to the point of code completion. As an example consider a "delegation" completion: // // public void myMethod(int param1, int param2, int param3) { // someOtherObject.yourMethod(param1, param2, param3); // } // // The other consideration is giving preference to variables that have not previously been used in this // code completion (which avoids "someOtherObject.yourMethod(param1, param1, param1)"; int count = parameterNames.length; fPositions = new Position[count]; fChoices = new ICompletionProposal[count][]; String[] parameterTypes = getParameterTypes(); ParameterGuesser guesser = new ParameterGuesser(); for (int i = count - 1; i >= 0; i--) { String paramName = new String(parameterNames[i]); Position position = new Position(0, 0); ICompletionProposal[] argumentProposals = guesser.parameterProposals( parameterTypes[i], paramName, position, fFillBestGuess); if (argumentProposals.length == 0) { argumentProposals = new ICompletionProposal[] {new DartCompletionProposal( paramName, 0, paramName.length(), 0, null, paramName, 0, null, null)}; } fPositions[i] = position; fChoices[i] = argumentProposals; } return fChoices; } private void openErrorDialog(Exception e) { Shell shell = getTextViewer().getTextWidget().getShell(); MessageDialog.openError( shell, DartTextMessages.ParameterGuessingProposal_error_msg, e.getMessage()); } }