/* * Copyright 2009-2017 the original author or authors. * * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 * * 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 org.codehaus.groovy.eclipse.codeassist.completions; import org.codehaus.groovy.eclipse.codeassist.GroovyContentAssist; import org.codehaus.groovy.eclipse.codeassist.ProposalUtils; import org.eclipse.jdt.core.CompletionContext; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.internal.ui.JavaPlugin; import org.eclipse.jdt.internal.ui.javaeditor.EditorHighlightingSynchronizer; import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor; import org.eclipse.jdt.internal.ui.text.java.JavaCompletionProposal; import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext; 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.ITextViewer; 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.jface.viewers.StyledString; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.texteditor.link.EditorLinkedModeUI; /** * A content assist proposal for named parameters. */ public class NamedParameterProposal extends JavaCompletionProposal { private static final ICompletionProposal[] NO_COMPLETIONS = new ICompletionProposal[0]; private IRegion selectedRegion; private final CompletionContext coreContext; private final boolean tryParamGuessing; private ICompletionProposal[] choices; private Position paramNamePosition; private IPositionUpdater updater; private String paramSignature; private final String paramName; public NamedParameterProposal(String paramName, String paramSignature, int replacementOffset, int replacementLength, Image image, StyledString displayString, int relevance, boolean inJavadoc, JavaContentAssistInvocationContext invocationContext, boolean tryParamGuessing) { super(computeReplacementString(paramName), replacementOffset, replacementLength, image, displayString, relevance, inJavadoc, invocationContext); this.tryParamGuessing = tryParamGuessing; coreContext = invocationContext.getCoreContext(); this.paramName = paramName; this.paramSignature = paramSignature; this.setTriggerCharacters(ProposalUtils.VAR_TRIGGER); } private String computeReplacementChoices(String name) { if (shouldDoGuessing()) { try { choices = guessParameters(paramName.toCharArray()); } catch (JavaModelException e) { paramNamePosition = null; choices = null; GroovyContentAssist.logError(e); openErrorDialog(e); } } return computeReplacementString(name); } private static String computeReplacementString(String name) { return name + ": __, "; } private IRegion calculateArgumentRegion() { String repl = getReplacementString(); int replOffset = getReplacementOffset(); int start = replOffset + repl.indexOf(": ") + 2; int end = replOffset + repl.indexOf(","); if (start > 0 && end > 0) { return new Region(start, end - start); } else { // couldn't find region return null; } } /** * @return true iff parameter guessing should be performed. */ private boolean shouldDoGuessing() { return tryParamGuessing && coreContext.isExtended(); } private ICompletionProposal[] guessParameters(char[] parameterName) throws JavaModelException { if (paramSignature == null) { return NO_COMPLETIONS; } String type = Signature.toString(paramSignature); IJavaElement[] assignableElements = getAssignableElements(); Position position = new Position(selectedRegion.getOffset(), selectedRegion.getLength()); ICompletionProposal[] argumentProposals = new ParameterGuesserDelegate(getEnclosingElement()).parameterProposals(type, paramName, position, assignableElements, tryParamGuessing); if (argumentProposals.length == 0) { argumentProposals = new ICompletionProposal[] { new JavaCompletionProposal(paramName, 0, paramName.length(), null, paramName, 0) }; } paramNamePosition = position; return choices = argumentProposals; } private IJavaElement getEnclosingElement() { return coreContext.getEnclosingElement(); } private IJavaElement[] getAssignableElements() { return coreContext.getVisibleElements(paramSignature); } @Override public void apply(IDocument document, char trigger, int offset) { super.apply(document, trigger, offset); if (selectedRegion == null) { selectedRegion = calculateArgumentRegion(); } setUpLinkedMode(document, ','); } @Override protected void setUpLinkedMode(IDocument document, char closingCharacter) { ITextViewer textViewer = getTextViewer(); if (textViewer != null) { int baseOffset = getReplacementOffset(); String replacement = computeReplacementChoices(paramName); try { LinkedModeModel model = new LinkedModeModel(); IRegion argRegion = calculateArgumentRegion(); LinkedPositionGroup group = new LinkedPositionGroup(); if (shouldDoGuessing()) { ensurePositionCategoryInstalled(document, model); document.addPosition(getCategory(), paramNamePosition); group.addPosition(new ProposalPosition(document, paramNamePosition.getOffset(), paramNamePosition .getLength(), LinkedPositionGroup.NO_STOP, choices)); } else { group.addPosition(new LinkedPosition(document, argRegion.getOffset(), argRegion.getLength(), LinkedPositionGroup.NO_STOP)); } model.addGroup(group); model.forceInstall(); JavaEditor editor = getJavaEditor(); if (editor != null) { model.addLinkingListener(new EditorHighlightingSynchronizer(editor)); } LinkedModeUI ui = new EditorLinkedModeUI(model, textViewer); ui.setExitPosition(textViewer, baseOffset + replacement.length(), 0, Integer.MAX_VALUE); ui.setExitPolicy(new ExitPolicy(closingCharacter, document)); ui.setDoContextInfo(true); ui.setCyclingMode(LinkedModeUI.CYCLE_WHEN_NO_PARENT); ui.enter(); selectedRegion = ui.getSelectedRegion(); } catch (BadLocationException e) { ensurePositionCategoryRemoved(document); GroovyContentAssist.logError(e); openErrorDialog(e); } catch (BadPositionCategoryException e) { ensurePositionCategoryRemoved(document); GroovyContentAssist.logError(e); openErrorDialog(e); } } } /* * @see ICompletionProposal#getSelection(IDocument) */ @Override public Point getSelection(IDocument document) { if (selectedRegion == null) { return new Point(getReplacementOffset(), 0); } else { return new Point(selectedRegion.getOffset(), selectedRegion.getLength()); } } /** * 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 JavaEditor getJavaEditor() { IEditorPart part = JavaPlugin.getActivePage().getActiveEditor(); if (part instanceof JavaEditor) { return (JavaEditor) part; } else { return null; } } private void openErrorDialog(Exception e) { Shell shell = getTextViewer().getTextWidget().getShell(); MessageDialog.openError(shell, "Error inserting parameters", e.getMessage()); } private void ensurePositionCategoryInstalled(final IDocument document, LinkedModeModel model) { if (!document.containsPositionCategory(getCategory())) { document.addPositionCategory(getCategory()); updater = new InclusivePositionUpdater(getCategory()); document.addPositionUpdater(updater); model.addLinkingListener(new ILinkedModeListener() { /* * @see * org.eclipse.jface.text.link.ILinkedModeListener#left(org. * eclipse.jface.text.link.LinkedModeModel, int) */ public void left(LinkedModeModel environment, int flags) { ensurePositionCategoryRemoved(document); } public void suspend(LinkedModeModel environment) {} public void resume(LinkedModeModel environment, int flags) {} }); } } private void ensurePositionCategoryRemoved(IDocument document) { if (document.containsPositionCategory(getCategory())) { try { document.removePositionCategory(getCategory()); } catch (BadPositionCategoryException e) { // ignore } document.removePositionUpdater(updater); } } private String getCategory() { return "ParameterGuessingProposal_" + toString(); //$NON-NLS-1$ } /** * Not API, for testing only. * * @return * @throws JavaModelException */ public ICompletionProposal[] getChoices() throws JavaModelException { selectedRegion = calculateArgumentRegion(); return guessParameters(paramName.toCharArray()); } }