/*
* 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.codehaus.groovy.eclipse.codeassist.processors.GroovyCompletionProposal;
import org.codehaus.groovy.eclipse.codeassist.proposals.ProposalFormattingOptions;
import org.eclipse.jdt.core.CompletionProposal;
import org.eclipse.jdt.core.compiler.CharOperation;
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.JavaMethodCompletionProposal;
import org.eclipse.jdt.internal.ui.text.java.MethodProposalInfo;
import org.eclipse.jdt.internal.ui.text.java.ProposalContextInformation;
import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.contentassist.IContextInformation;
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.viewers.StyledString;
import org.eclipse.swt.graphics.Point;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.texteditor.link.EditorLinkedModeUI;
public class GroovyJavaMethodCompletionProposal extends JavaMethodCompletionProposal {
private static final char[] CLOSURE_TYPE_NAME = "Closure".toCharArray();
private final ProposalFormattingOptions options;
private final String contributor;
// if true, shows the context only and does not perform completion
private boolean contextOnly;
private boolean methodPointer;
private int[] fArgumentOffsets;
private int[] fArgumentLengths;
private IRegion fSelectedRegion; // initialized by apply()
public GroovyJavaMethodCompletionProposal(GroovyCompletionProposal proposal, JavaContentAssistInvocationContext context, ProposalFormattingOptions options) {
this(proposal, context, options, null);
}
public GroovyJavaMethodCompletionProposal(GroovyCompletionProposal proposal, JavaContentAssistInvocationContext context, ProposalFormattingOptions options, String contributor) {
super(proposal, context);
this.options = options;
this.contributor = contributor;
this.setRelevance(proposal.getRelevance());
this.setProposalInfo(new MethodProposalInfo(context.getProject(), proposal));
this.setTriggerCharacters(!proposal.hasParameters() ? ProposalUtils.METHOD_TRIGGERS : ProposalUtils.METHOD_WITH_ARGUMENTS_TRIGGERS);
}
public void contextOnly() {
contextOnly = true;
}
@Override
protected StyledString computeDisplayString() {
StyledString displayString = super.computeDisplayString();
if (contributor != null && contributor.trim().length() > 0) {
displayString.append(new StyledString(" (" + contributor + ")", StyledString.DECORATIONS_STYLER));
}
return displayString;
}
@Override
protected IContextInformation computeContextInformation() {
if (hasParameters() && (fProposal.getKind() == CompletionProposal.METHOD_REF ||
fProposal.getKind() == CompletionProposal.CONSTRUCTOR_INVOCATION)) {
ProposalContextInformation contextInformation = new ProposalContextInformation(fProposal);
if (fContextInformationPosition != 0 && fProposal.getCompletion().length == 0)
contextInformation.setContextInformationPosition(fContextInformationPosition);
return contextInformation;
}
return super.computeContextInformation();
}
/**
* @see ICompletionProposalExtension#apply(IDocument, char)
*/
@Override
public void apply(IDocument document, char trigger, int offset) {
methodPointer = ProposalUtils.isMethodPointerCompletion(document, getReplacementOffset());
super.apply(document, trigger, offset);
if (fArgumentOffsets != null && fArgumentOffsets.length > 0 && fArgumentLengths != null && fArgumentLengths.length > 0) {
fSelectedRegion = new Region(getReplacementOffset() + fArgumentOffsets[0], fArgumentLengths[0]);
} else {
fSelectedRegion = new Region(getReplacementOffset() + getReplacementString().length(), 0);
}
}
@Override
protected void setUpLinkedMode(IDocument document, char closingCharacter) {
ITextViewer textViewer = getTextViewer();
if (textViewer != null && fArgumentOffsets != null) {
int baseOffset = getReplacementOffset();
String replacement = getReplacementString();
try {
LinkedModeModel model = new LinkedModeModel();
for (int i = 0, n = fArgumentOffsets.length; i < n; i += 1) {
LinkedPositionGroup group = new LinkedPositionGroup();
group.addPosition(new LinkedPosition(document, baseOffset + fArgumentOffsets[i], fArgumentLengths[i], 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();
fSelectedRegion = ui.getSelectedRegion();
} catch (BadLocationException e) {
GroovyContentAssist.logError(e);
}
}
}
@Override
protected String computeReplacementString() {
if (contextOnly) {
return "";
}
char[] proposalName = fProposal.getName();
boolean hasWhitespace = ProposalUtils.hasWhitespace(proposalName);
if (methodPointer) {
// complete the name only for a method pointer expression
return String.valueOf(!hasWhitespace ? proposalName : CharOperation.concat('"', proposalName, '"'));
}
// with no arguments there is nothing groovy to do
if ((!hasParameters() || !hasArgumentList()) && !hasWhitespace) {
String replacementString = super.computeReplacementString();
if (replacementString.endsWith(");")) {
replacementString = replacementString.substring(0, replacementString.length() - 1);
}
return replacementString;
}
StringBuffer buffer = new StringBuffer();
char[] newProposalName = !hasWhitespace ? proposalName : CharOperation.concat('"', proposalName, '"');
fProposal.setName(newProposalName);
appendMethodNameReplacement(buffer);
fProposal.setName(proposalName);
if (!hasParameters()) {
if (getFormatterPrefs().inEmptyList) {
buffer.append(SPACE);
}
buffer.append(RPAREN);
} else {
int indexOfLastClosure = -1;
char[][] regularParameterTypes = ((GroovyCompletionProposal) fProposal).getRegularParameterTypeNames();
char[][] namedParameterTypes = ((GroovyCompletionProposal) fProposal).getNamedParameterTypeNames();
if (options.noParensAroundClosures) {
// need to check both regular and named parameters for closure
if (lastArgIsClosure(regularParameterTypes, namedParameterTypes)) {
indexOfLastClosure = regularParameterTypes.length + namedParameterTypes.length - 1;
}
// remove the opening paren only if there is a single closure parameter
if (indexOfLastClosure == 0) {
buffer.replace(buffer.length() - 1, buffer.length(), "");
// add space if not already there would be added by call to appendMethodNameReplacement
if (!getFormatterPrefs().beforeOpeningParen) {
buffer.append(SPACE);
}
}
} else if (getFormatterPrefs().afterOpeningParen) {
buffer.append(SPACE);
}
// now add the parameters; named parameters go first
char[][] namedParameterNames = ((GroovyCompletionProposal) fProposal).getNamedParameterNames();
char[][] regularParameterNames = ((GroovyCompletionProposal) fProposal).getRegularParameterNames();
int namedCount = namedParameterNames.length;
int argCount = regularParameterNames.length;
int allCount = argCount + namedCount;
fArgumentOffsets = new int[allCount];
fArgumentLengths = new int[allCount];
for (int i = 0; i < allCount; i += 1) {
// check for named args (either all of them, or the explicitly named ones)
char[] nextName;
char[] nextTypeName;
if (i < namedCount) { // named arg
nextName = namedParameterNames[i];
nextTypeName = namedParameterNames[i];
} else {
nextName = regularParameterNames[i - namedCount];
nextTypeName = regularParameterTypes[i - namedCount];
}
if (options.useNamedArguments || i < namedCount) {
buffer.append(nextName).append(":");
}
fArgumentOffsets[i] = buffer.length();
if (i == 0) {
setCursorPosition(buffer.length());
}
// handle the argument name
if (options.useBracketsForClosures && CharOperation.equals(nextTypeName, CLOSURE_TYPE_NAME)) {
fArgumentOffsets[i] = buffer.length() + 2;
fArgumentLengths[i] = 2; // select "it"
buffer.append("{ it }");
} else {
fArgumentOffsets[i] = buffer.length();
fArgumentLengths[i] = nextName.length;
buffer.append(nextName);
}
if (i == indexOfLastClosure - 1 || (i != indexOfLastClosure && i == allCount - 1)) {
if (getFormatterPrefs().beforeClosingParen) {
buffer.append(SPACE);
}
buffer.append(RPAREN);
if (i == indexOfLastClosure - 1) {
buffer.append(SPACE);
}
} else if (i < allCount - 1) {
if (getFormatterPrefs().beforeComma)
buffer.append(SPACE);
buffer.append(COMMA);
if (getFormatterPrefs().afterComma)
buffer.append(SPACE);
}
}
}
return buffer.toString();
}
private boolean lastArgIsClosure(char[][] regularParameterTypes, char[][] namedParameterTypes) {
char[] lastArgType;
if (namedParameterTypes != null && namedParameterTypes.length > 0) {
lastArgType = namedParameterTypes[namedParameterTypes.length - 1];
} else if (regularParameterTypes != null && regularParameterTypes.length > 0) {
lastArgType = regularParameterTypes[regularParameterTypes.length - 1];
} else { // no args
return false;
}
// we should be comparing against a fully qualified type name, but it is not always available so a simple name is close enough
return CharOperation.equals(lastArgType, CLOSURE_TYPE_NAME);
}
/**
* @see org.eclipse.jdt.internal.ui.text.java.JavaMethodCompletionProposal#needsLinkedMode()
*/
@Override
protected boolean needsLinkedMode() {
return super.needsLinkedMode();
}
/**
* 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;
}
return null;
}
/**
* @see ICompletionProposal#getSelection(IDocument)
*/
@Override
public Point getSelection(IDocument document) {
if (fSelectedRegion == null)
return new Point(getReplacementOffset(), 0);
return new Point(fSelectedRegion.getOffset(), fSelectedRegion.getLength());
}
}