content: /*******************************************************************************
   * Copyright (c) 2000, 2008 IBM Corporation and others.
   * 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:
   *     IBM Corporation - initial API and implementation
   *******************************************************************************/
  package org.eclipse.wst.jsdt.internal.ui.text.correction;
  
  import java.util.Arrays;
  import java.util.Comparator;
  
  import org.eclipse.jface.text.BadLocationException;
  import org.eclipse.jface.text.DocumentEvent;
  import org.eclipse.jface.text.IDocument;
  import org.eclipse.jface.text.IRegion;
  import org.eclipse.jface.text.ITextViewer;
  import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2;
  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.text.link.LinkedModeUI.ExitFlags;
  import org.eclipse.jface.text.link.LinkedModeUI.IExitPolicy;
  import org.eclipse.swt.SWT;
  import org.eclipse.swt.events.VerifyEvent;
  import org.eclipse.swt.graphics.Image;
  import org.eclipse.swt.graphics.Point;
  import org.eclipse.ui.IEditorPart;
  import org.eclipse.ui.texteditor.link.EditorLinkedModeUI;
  import org.eclipse.wst.jsdt.core.IJavaScriptUnit;
  import org.eclipse.wst.jsdt.core.dom.ASTNode;
  import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit;
  import org.eclipse.wst.jsdt.core.dom.SimpleName;
  import org.eclipse.wst.jsdt.internal.corext.dom.LinkedNodeFinder;
  import org.eclipse.wst.jsdt.internal.corext.dom.NodeFinder;
  import org.eclipse.wst.jsdt.internal.corext.util.Messages;
  import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
  import org.eclipse.wst.jsdt.internal.ui.JavaPluginImages;
  import org.eclipse.wst.jsdt.internal.ui.javaeditor.ASTProvider;
  import org.eclipse.wst.jsdt.internal.ui.javaeditor.EditorHighlightingSynchronizer;
  import org.eclipse.wst.jsdt.internal.ui.javaeditor.JavaEditor;
  import org.eclipse.wst.jsdt.ui.text.java.IJavaCompletionProposal;
  
  /**
   * A template proposal.
   */
  public class LinkedNamesAssistProposal implements IJavaCompletionProposal, ICompletionProposalExtension2, ICommandAccess {
  
  	/**
  	 * An exit policy that skips Backspace and Delete at the beginning and at the end
  	 * of a linked position, respectively.
  	 * 
  	 * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=183925 .  
  	 */
  	public static class DeleteBlockingExitPolicy implements IExitPolicy {
  		private IDocument fDocument;
  
  		public DeleteBlockingExitPolicy(IDocument document) {
  			fDocument= document;
  		}
  
  		public ExitFlags doExit(LinkedModeModel model, VerifyEvent event, int offset, int length) {
  			if (length == 0 && (event.character == SWT.BS || event.character == SWT.DEL)) {
  				LinkedPosition position= model.findPosition(new LinkedPosition(fDocument, offset, 0, LinkedPositionGroup.NO_STOP));
  				if (position != null) {
  					if (event.character == SWT.BS) {
  						if (offset - 1 < position.getOffset()) {
  							//skip backspace at beginning of linked position
  							event.doit= false;
  						}
  					} else /* event.character == SWT.DEL */ {
  						if (offset + 1 > position.getOffset() + position.getLength()) {
  							//skip delete at end of linked position
  							event.doit= false;
  						}
  					}
  				}
  			}
  			
  			return null; // don't change behavior
  		}
  	}
  
  	
  	public static final String ASSIST_ID= "org.eclipse.wst.jsdt.ui.correction.renameInFile.assist"; //$NON-NLS-1$
  	
  	private SimpleName fNode;
  	private IJavaScriptUnit fCompilationUnit;
  	private String fLabel;
  	private String fValueSuggestion;
  	private int fRelevance;
  
  	public LinkedNamesAssistProposal(IJavaScriptUnit cu, SimpleName node) {
  		this(CorrectionMessages.LinkedNamesAssistProposal_description, cu, node, null);
  		fNode= node;
  		fCompilationUnit= cu;
  		fRelevance= 8;
  	}
  
  	public LinkedNamesAssistProposal(String label, IJavaScriptUnit cu, SimpleName node, String valueSuggestion) {
  		fLabel= label;
  		fNode= node;
  		fCompilationUnit= cu;
  		fValueSuggestion= valueSuggestion;
  		fRelevance= 8;
  	}
  
  	/* (non-Javadoc)
  	 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#apply(org.eclipse.jface.text.ITextViewer, char, int, int)
  	 */
  	public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) {
  		try {
  			Point seletion= viewer.getSelectedRange();
  
  			// get full ast
  			JavaScriptUnit root= JavaScriptPlugin.getDefault().getASTProvider().getAST(fCompilationUnit, ASTProvider.WAIT_YES, null);
  
  			ASTNode nameNode= NodeFinder.perform(root, fNode.getStartPosition(), fNode.getLength());
  			final int pos= fNode.getStartPosition();
  
  			ASTNode[] sameNodes;
  			if (nameNode instanceof SimpleName) {
  				sameNodes= LinkedNodeFinder.findByNode(root, (SimpleName) nameNode);
  			} else {
  				sameNodes= new ASTNode[] { nameNode };
  			}
  
  			// sort for iteration order, starting with the node @ offset
  			Arrays.sort(sameNodes, new Comparator() {
  
  				public int compare(Object o1, Object o2) {
  					return rank((ASTNode) o1) - rank((ASTNode) o2);
  				}
  
  				/**
  				 * Returns the absolute rank of an <code>ASTNode</code>. Nodes
  				 * preceding <code>offset</code> are ranked last.
  				 *
  				 * @param node the node to compute the rank for
  				 * @return the rank of the node with respect to the invocation offset
  				 */
  				private int rank(ASTNode node) {
  					int relativeRank= node.getStartPosition() + node.getLength() - pos;
  					if (relativeRank < 0)
  						return Integer.MAX_VALUE + relativeRank;
  					else
  						return relativeRank;
  				}
  
  			});
  
  			IDocument document= viewer.getDocument();
  			LinkedPositionGroup group= new LinkedPositionGroup();
  			for (int i= 0; i < sameNodes.length; i++) {
  				ASTNode elem= sameNodes[i];
  				group.addPosition(new LinkedPosition(document, elem.getStartPosition(), elem.getLength(), i));
  			}
  
  			LinkedModeModel model= new LinkedModeModel();
  			model.addGroup(group);
  			model.forceInstall();
  			JavaEditor editor= getJavaEditor();
  			if (editor != null) {
  				model.addLinkingListener(new EditorHighlightingSynchronizer(editor));
  			}
  
  			LinkedModeUI ui= new EditorLinkedModeUI(model, viewer);
  			ui.setExitPolicy(new DeleteBlockingExitPolicy(document));
  			ui.setExitPosition(viewer, offset, 0, LinkedPositionGroup.NO_STOP);
  			ui.enter();
  
  			if (fValueSuggestion != null) {
  				document.replace(nameNode.getStartPosition(), nameNode.getLength(), fValueSuggestion);
  				IRegion selectedRegion= ui.getSelectedRegion();
  				seletion= new Point(selectedRegion.getOffset(), fValueSuggestion.length());
  			}
  			
  			viewer.setSelectedRange(seletion.x, seletion.y); // by default full word is selected, restore original selection
  
  		} catch (BadLocationException e) {
  			JavaScriptPlugin.log(e);
  		}
  	}
  
  	/**
  	 * 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= JavaScriptPlugin.getActivePage().getActiveEditor();
  		if (part instanceof JavaEditor)
  			return (JavaEditor) part;
  		else
  			return null;
  	}
  
  	/*
  	 * @see ICompletionProposal#apply(IDocument)
  	 */
  	public void apply(IDocument document) {
  		// can't do anything
  	}
  
  	/*
  	 * @see ICompletionProposal#getSelection(IDocument)
  	 */
  	public Point getSelection(IDocument document) {
  		return null;
  	}
  
  	/*
  	 * @see ICompletionProposal#getAdditionalProposalInfo()
  	 */
  	public String getAdditionalProposalInfo() {
  		return CorrectionMessages.LinkedNamesAssistProposal_proposalinfo;
  	}
  
  	/*
  	 * @see ICompletionProposal#getDisplayString()
  	 */
  	public String getDisplayString() {
  		String shortCutString= CorrectionCommandHandler.getShortCutString(getCommandId());
  		if (shortCutString != null) {
  			return Messages.format(CorrectionMessages.ChangeCorrectionProposal_name_with_shortcut, new String[] { fLabel, shortCutString });
  		}
  		return fLabel;
  	}
  
  	/*
  	 * @see ICompletionProposal#getImage()
  	 */
  	public Image getImage() {
  		return JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_LINKED_RENAME);
  	}
  
  	/*
  	 * @see ICompletionProposal#getContextInformation()
  	 */
  	public IContextInformation getContextInformation() {
  		return null;
  	}
  
  	/*
  	 * @see IJavaCompletionProposal#getRelevance()
  	 */
  	public int getRelevance() {
  		return fRelevance;
  	}
  
  
  	/* (non-Javadoc)
  	 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#selected(org.eclipse.jface.text.ITextViewer, boolean)
  	 */
  	public void selected(ITextViewer textViewer, boolean smartToggle) {
  	}
  
  	/* (non-Javadoc)
  	 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#unselected(org.eclipse.jface.text.ITextViewer)
  	 */
  	public void unselected(ITextViewer textViewer) {
  	}
  
  	/* (non-Javadoc)
  	 * @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) {
  		return false;
  	}
  
  	/* (non-Javadoc)
  	 * @see org.eclipse.wst.jsdt.internal.ui.text.correction.IShortcutProposal#getProposalId()
  	 */
  	public String getCommandId() {
  		return ASSIST_ID;
  	}
  
  	public void setRelevance(int relevance) {
  		fRelevance= relevance;
  	}
  
  }
  