content: /**
   *  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;
  	}
  
  }
  