/**
* Copyright (c) 2013-2016 Angelo ZERR.
* 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:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package tern.eclipse.jface.contentassist;
import java.util.List;
import org.eclipse.jface.internal.text.html.BrowserInformationControl;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.jface.text.contentassist.CompletionProposal;
import org.eclipse.jface.text.contentassist.ContextInformation;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension3;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Shell;
import tern.eclipse.jface.images.TernImagesRegistry;
import tern.eclipse.jface.text.HoverControlCreator;
import tern.eclipse.jface.text.PresenterControlCreator;
import tern.server.protocol.completions.Parameter;
import tern.server.protocol.completions.TernCompletionItem;
import tern.server.protocol.completions.TernCompletionProposalRec;
public class TernCompletionProposal extends TernCompletionItem implements
ICompletionProposal, ICompletionProposalExtension,
ICompletionProposalExtension2, ICompletionProposalExtension3
/* , IRelevanceCompletionProposal */{
private String fDisplayString;
private String fReplacementString;
private int fReplacementOffset;
private int fReplacementLength;
private int fCursorPosition;
private Image fImage;
private IContextInformation fContextInformation;
private boolean fContextInformationComputed;
private String fAdditionalProposalInfo;
private boolean fUpdateLengthOnValidate;
private String fAlternateMatch;
private char[] fTriggers;
private IInformationControlCreator ternControlCreator;
public TernCompletionProposal(TernCompletionProposalRec proposal) {
super(proposal);
String text = super.getSignature();
this.fReplacementString = text;
this.fReplacementOffset = proposal.start;
this.fReplacementLength = proposal.end - proposal.start;
this.fCursorPosition = text.length();
this.fDisplayString = super.getText();
this.fAdditionalProposalInfo = proposal.doc != null ? proposal.doc.toString() : null;
}
/**
* Create context information for function parameters.
*
* @return
*/
private synchronized IContextInformation createContextInformation() {
List<Parameter> parameters = getParameters();
if (parameters != null) {
StringBuilder info = new StringBuilder();
for (int i = 0; i < parameters.size(); i++) {
Parameter parameter = parameters.get(i);
if (i > 0) {
info.append(", ");
}
info.append(parameter.getName());
if (!parameter.isRequired()) {
info.append("?");
}
info.append(": ");
info.append(parameter.getType());
}
return new ContextInformation("", info.toString());
}
return null;
}
protected Image getDefaultImage() {
return TernImagesRegistry.getImage(this, false);
}
// public void apply(IDocument document) {
// try {
// document.replace(this.fReplacementOffset, this.fReplacementLength,
// this.fReplacementString);
// } catch (BadLocationException localBadLocationException) {
// }
// }
public void apply(IDocument document) {
CompletionProposal proposal = new CompletionProposal(
getReplacementString(), getReplacementOffset(),
getReplacementLength(), getCursorPosition(), getImage(),
getDisplayString(), getContextInformation(),
getAdditionalProposalInfo());
proposal.apply(document);
}
private int getCursorPosition() {
return fCursorPosition;
}
protected int getReplacementLength() {
return fReplacementLength;
}
protected int getReplacementOffset() {
return fReplacementOffset;
}
protected String getReplacementString() {
return fReplacementString;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jface.text.contentassist.ICompletionProposalExtension#apply
* (org.eclipse.jface.text.IDocument, char, int)
*/
public void apply(IDocument document, char trigger, int offset) {
CompletionProposal proposal = new CompletionProposal(
getReplacementString(), getReplacementOffset(),
getReplacementLength(), getCursorPosition(), getImage(),
getDisplayString(), getContextInformation(),
getAdditionalProposalInfo());
// we currently don't do anything special for which character
// selected the proposal, and where the cursor offset is
// but we might in the future...
proposal.apply(document);
// we want to ContextInformationPresenter.updatePresentation() here
}
public void apply(ITextViewer viewer, char trigger, int stateMask,
int offset) {
IDocument document = viewer.getDocument();
// CMVC 252634 to compensate for "invisible" initial region
int caretOffset = viewer.getTextWidget().getCaretOffset();
if (viewer instanceof ITextViewerExtension5) {
ITextViewerExtension5 extension = (ITextViewerExtension5) viewer;
caretOffset = extension.widgetOffset2ModelOffset(caretOffset);
} else {
caretOffset = viewer.getTextWidget().getCaretOffset()
+ viewer.getVisibleRegion().getOffset();
}
if (caretOffset == getReplacementOffset()) {
apply(document);
} else {
// replace the text without affecting the caret Position as this
// causes the cursor to move on its own
try {
int endOffsetOfChanges = getReplacementString().length()
+ getReplacementOffset();
// Insert the portion of the new text that comes after the
// current caret position
if (endOffsetOfChanges >= caretOffset) {
int postCaretReplacementLength = getReplacementOffset()
+ getReplacementLength() - caretOffset;
int preCaretReplacementLength = getReplacementString()
.length() - (endOffsetOfChanges - caretOffset);
if (postCaretReplacementLength < 0) {
/*
* if (Debug.displayWarnings) { System.out.println(
* "** postCaretReplacementLength was negative: " +
* postCaretReplacementLength); //$NON-NLS-1$ }
*/
// This is just a quick fix while I figure out what
// replacement length is supposed to be
// in each case, otherwise we'll get negative
// replacment length sometimes
postCaretReplacementLength = 0;
}
document.replace(
caretOffset,
postCaretReplacementLength,
getReplacementString().substring(
preCaretReplacementLength));
}
// Insert the portion of the new text that comes before the
// current caret position
// Done second since offsets would change for the post text
// otherwise
// Outright insertions are handled here
if (caretOffset > getReplacementOffset()) {
int preCaretTextLength = caretOffset
- getReplacementOffset();
document.replace(getReplacementOffset(),
preCaretTextLength, getReplacementString()
.substring(0, preCaretTextLength));
}
} catch (BadLocationException x) {
apply(document);
} catch (StringIndexOutOfBoundsException e) {
apply(document);
}
}
}
public Point getSelection(IDocument document) {
CompletionProposal proposal = new CompletionProposal(
getReplacementString(), getReplacementOffset(),
getReplacementLength(), getCursorPosition(), getImage(),
getDisplayString(), getContextInformation(),
getAdditionalProposalInfo());
return proposal.getSelection(document);
// return new Point(this.fReplacementOffset + this.fCursorPosition, 0);
}
@Override
public Image getImage() {
if (this.fImage == null) {
this.fImage = getDefaultImage();
}
return this.fImage;
}
public void setImage(Image fImage) {
this.fImage = fImage;
}
@Override
public String getDisplayString() {
if (this.fDisplayString != null)
return this.fDisplayString;
return this.fReplacementString;
}
@Override
public String getAdditionalProposalInfo() {
return this.fAdditionalProposalInfo;
}
@Override
public IContextInformation getContextInformation() {
if (!fContextInformationComputed) {
fContextInformation = createContextInformation();
fContextInformationComputed = true;
}
return fContextInformation;
}
public void setContextInformation(IContextInformation contextInfo) {
fContextInformation = contextInfo;
}
@Override
public int getContextInformationPosition() {
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=110355
// return getCursorPosition();
if (getContextInformation() == null)
return getReplacementOffset() - 1;
return getReplacementOffset() + getCursorPosition();
}
public void setCursorPosition(int pos) {
fCursorPosition = pos;
}
public void setDisplayString(String newDisplayString) {
fDisplayString = newDisplayString;
}
public void setReplacementLength(int newReplacementLength) {
fReplacementLength = newReplacementLength;
}
// public Point getSelection(IDocument document) {
// // return fProposal.getSelection(document);
// CompletionProposal proposal = new
// CompletionProposal(getReplacementString(), getReplacementOffset(),
// getReplacementLength(), getCursorPosition(), getImage(),
// getDisplayString(), getContextInformation(),
// getAdditionalProposalInfo());
// return proposal.getSelection(document);
// }
@Override
public char[] getTriggerCharacters() {
return fTriggers;
}
public void setTriggerCharacters(char[] triggers) {
fTriggers = triggers;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jface.text.contentassist.ICompletionProposalExtension#isValidFor
* (org.eclipse.jface.text.IDocument, int)
*/
public boolean isValidFor(IDocument document, int offset) {
return validate(document, offset, null);
}
/**
* @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#selected(org.eclipse.jface.text.ITextViewer,
* boolean)
*/
public void selected(ITextViewer viewer, boolean smartToggle) {
}
// code is borrowed from JavaCompletionProposal
protected boolean startsWith(IDocument document, int offset, String word) {
int wordLength = word == null ? 0 : word.length();
if (offset > fReplacementOffset + wordLength)
return false;
try {
int length = offset - fReplacementOffset;
String start = document.get(fReplacementOffset, length);
return (word != null && word.substring(0, length).equalsIgnoreCase(
start))
|| (fAlternateMatch != null
&& length <= fAlternateMatch.length() && fAlternateMatch
.substring(0, length).equalsIgnoreCase(start));
} catch (BadLocationException x) {
}
return false;
}
/**
* @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#unselected(org.eclipse.jface.text.ITextViewer)
*/
public void unselected(ITextViewer viewer) {
}
/**
* borrowed from JavaCompletionProposal
*
* @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#validate(org.eclipse.jface.text.IDocument,
* int, org.eclipse.jface.text.DocumentEvent)
*/
public boolean validate(IDocument document, int offset, DocumentEvent event) {
if (offset < fReplacementOffset)
return false;
boolean validated = startsWith(document, offset, fReplacementString);
if (fUpdateLengthOnValidate && event != null) {
fReplacementLength += event.fText.length() - event.fLength; // adjust
// the
// replacement
// length
// by
// the
// event's
// text
// replacement
}
return validated;
}
/**
* @param replacementOffset
* The fReplacementOffset to set.
*/
public void setReplacementOffset(int replacementOffset) {
fReplacementOffset = replacementOffset;
}
/**
* @param replacementString
* The fReplacementString to set.
*/
public void setReplacementString(String replacementString) {
fReplacementString = replacementString;
}
@Override
public IInformationControlCreator getInformationControlCreator() {
Shell shell = getActiveWorkbenchShell();
if (shell == null || !BrowserInformationControl.isAvailable(shell))
return null;
if (ternControlCreator == null) {
PresenterControlCreator presenterControlCreator = new PresenterControlCreator();
ternControlCreator = new HoverControlCreator(
presenterControlCreator, true);
}
return ternControlCreator;
}
protected Shell getActiveWorkbenchShell() {
return null;
}
@Override
public int getPrefixCompletionStart(IDocument document, int completionOffset) {
return fReplacementOffset;
}
@Override
public CharSequence getPrefixCompletionText(IDocument document,
int completionOffset) {
return null;
}
}