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.ide.ui.views;
  
  import org.eclipse.core.resources.IFile;
  import org.eclipse.core.runtime.IProgressMonitor;
  import org.eclipse.core.runtime.IStatus;
  import org.eclipse.core.runtime.Status;
  import org.eclipse.core.runtime.jobs.Job;
  import org.eclipse.jface.text.ITextSelection;
  import org.eclipse.jface.viewers.IPostSelectionProvider;
  import org.eclipse.jface.viewers.ISelection;
  import org.eclipse.jface.viewers.ISelectionChangedListener;
  import org.eclipse.jface.viewers.ISelectionProvider;
  import org.eclipse.jface.viewers.IStructuredContentProvider;
  import org.eclipse.jface.viewers.SelectionChangedEvent;
  import org.eclipse.jface.viewers.StructuredSelection;
  import org.eclipse.swt.widgets.Display;
  import org.eclipse.ui.IWorkbenchPart;
  import org.eclipse.ui.navigator.CommonViewer;
  import org.eclipse.ui.texteditor.ITextEditor;
  
  import tern.eclipse.ide.internal.ui.TernUIMessages;
  import tern.eclipse.ide.ui.utils.EditorUtils;
  import tern.server.protocol.outline.IJSNode;
  import tern.utils.StringUtils;
  
  /**
   * Job which is executed when there is a selection inside JavaScript Editor to
   * select the well node in the tern outline.
   *
   */
  class UpdateSelectionJob extends Job implements ISelectionChangedListener {
  
  	private static final int UPDATE_DELAY = 100;
  
  	private final AbstractTernOutlineView view;
  	private ISelection selection;
  	private ITextEditor textEditor;
  
  	public UpdateSelectionJob(AbstractTernOutlineView view) {
  		super(TernUIMessages.Link_With_Editor_Job_);
  		super.setSystem(true);
  		super.setPriority(Job.SHORT);
  		this.view = view;
  	}
  
  	@Override
  	protected IStatus run(IProgressMonitor monitor) {
  		BestNode bestNode = findBestNode(selection);
  		if (bestNode != null) {
  			try {
  				view.ignoreSelectionChanged = true;
  				final IJSNode node = bestNode.node;
  				final CommonViewer viewer = bestNode.viewer;
  				Display.getDefault().syncExec(new Runnable() {
  					@Override
  					public void run() {
  						viewer.setSelection(new StructuredSelection(node));
  					}
  				});
  			} finally {
  				view.ignoreSelectionChanged = false;
  			}
  		}
  		return Status.OK_STATUS;
  	}
  
  	@Override
  	public void selectionChanged(SelectionChangedEvent event) {
  		selectInTreeview(event.getSelection());
  	}
  
  	public void setCurrentPart(IWorkbenchPart part) {
  		if (this.textEditor != null) {
  			uninstall(this.textEditor.getSelectionProvider());
  		}
  		if (part instanceof ITextEditor) {
  			this.textEditor = (ITextEditor) part;
  			ISelectionProvider provider = this.textEditor.getSelectionProvider();
  			selectInTreeview(provider.getSelection());
  			install(provider);
  		} else {
  			this.textEditor = null;
  		}
  	}
  
  	private void selectInTreeview(ISelection selection) {
  		if (view.ignoreEditorActivation) {
  			return;
  		}
  		this.selection = selection;
  		super.schedule(UPDATE_DELAY);
  	}
  
  	private void install(ISelectionProvider selectionProvider) {
  		if (selectionProvider == null) {
  			return;
  		}
  		if (selectionProvider instanceof IPostSelectionProvider) {
  			IPostSelectionProvider provider = (IPostSelectionProvider) selectionProvider;
  			provider.addPostSelectionChangedListener(this);
  		} else {
  			selectionProvider.addSelectionChangedListener(this);
  		}
  	}
  
  	private void uninstall(ISelectionProvider selectionProvider) {
  		if (selectionProvider == null) {
  			return;
  		}
  		if (selectionProvider instanceof IPostSelectionProvider) {
  			IPostSelectionProvider provider = (IPostSelectionProvider) selectionProvider;
  			provider.removePostSelectionChangedListener(this);
  		} else {
  			selectionProvider.removeSelectionChangedListener(this);
  		}
  	}
  
  	private class BestNode {
  		public final IJSNode node;
  		public final CommonViewer viewer;
  
  		public BestNode(IJSNode node, CommonViewer viewer) {
  			this.node = node;
  			this.viewer = viewer;
  		}
  	}
  
  	private BestNode findBestNode(ISelection selection) {
  		if (!view.isLinkingEnabled() /* && !ignoreSelectionChanged */ ) {
  			return null;
  		}
  		CommonViewer viewer = view.getCurrentViewer();
  		if (viewer == null) {
  			return null;
  		}
  		IStructuredContentProvider contentProvider = (IStructuredContentProvider) viewer.getContentProvider();
  		if (contentProvider == null || selection == null || selection.isEmpty()
  				|| !(selection instanceof ITextSelection)) {
  			return null;
  		}
  		Object[] elements = contentProvider.getElements(viewer.getInput());
  		IFile currentFile = view.getCurrentTernPage().getCurrentFile();
  		if (elements != null) {
  			Object elt = null;
  			IJSNode bestNode = null;
  			for (int i = 0; i < elements.length; i++) {
  				elt = elements[i];
  				if (elt instanceof IJSNode) {
  					bestNode = findBestNode((IJSNode) elt, (ITextSelection) selection, currentFile);
  					if (bestNode != null) {
  						return new BestNode(bestNode, viewer);
  					}
  				}
  			}
  		}
  		return null;
  	}
  
  	private IJSNode findBestNode(IJSNode node, ITextSelection selection, IFile currentFile) {
  		int start = selection.getOffset(), end = start + selection.getLength();
  		if (node.getStart() != null) {
  			if (node.getStart() <= start && node.getEnd() >= end) {
  				if (!StringUtils.isEmpty(node.getFile()) && currentFile != null
  						&& !currentFile.equals(EditorUtils.getFile(node))) {
  					// doesn't match the file
  					return null;
  				}
  				// node is found, check if node is a container (like a function)
  				if (node.isContainer()) {
  					// check if there is a child which match the selection
  					IJSNode child = findBestNodeInChildren(node, selection, currentFile);
  					if (child != null) {
  						return child;
  					}
  				}
  				return node;
  			}
  		}
  		return findBestNodeInChildren(node, selection, currentFile);
  	}
  
  	protected IJSNode findBestNodeInChildren(IJSNode node, ITextSelection selection, IFile currentFile) {
  		if (node.hasChidren()) {
  			for (IJSNode child : node.getChildren()) {
  				IJSNode c = findBestNode(child, selection, currentFile);
  				if (c != null) {
  					return c;
  				}
  			}
  		}
  		return null;
  	}
  
  	public void dispose() {
  		if (this.textEditor != null) {
  			uninstall(this.textEditor.getSelectionProvider());
  		}
  		super.cancel();
  	}
  
  }
  