Package ccw.editors.outline

Source Code of ccw.editors.outline.ClojureOutlinePage$TreeSelectionChangedListener

/*******************************************************************************
* Copyright (c) 2009 Manuel Woelker.
* 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:
*    Manuel Woelker - initial prototype implementation
*******************************************************************************/
package ccw.editors.outline;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

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.action.Action;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider;
import org.eclipse.jface.viewers.IPostSelectionProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.views.contentoutline.ContentOutlinePage;

import ccw.CCWPlugin;
import ccw.ClojureCore;
import ccw.editors.clojure.ClojureEditor;
import clojure.lang.IMapEntry;
import clojure.lang.Keyword;
import clojure.lang.LineNumberingPushbackReader;
import clojure.lang.LispReader;
import clojure.lang.LispReader.ReaderException;
import clojure.lang.Obj;
import clojure.lang.RT;
import clojure.lang.Symbol;

public class ClojureOutlinePage extends ContentOutlinePage {

  private final class OutlineLabelProvider extends LabelProvider implements
      IStyledLabelProvider {

    private String getSymbol(List<?> list) {
      // TODO: smarter behavior when this is not a symbol
      String symbol = NOT_AVAILABLE;
      if (list.size() > 1) {
        symbol = safeToString(RT.second(list));
      }
      return symbol;
    }

    private String getKind(List<?> list) {
      // TODO: "smarter" behavior in general
      String kind = NOT_AVAILABLE;
      if (list.size() > 0) {
        kind = safeToString(RT.first(list));
      }
      return kind;
    }

    public Image getImage(Object element) {
      // TODO: different images for macros, fns, etc.
        // this is using results of reading analysis only... :-(
        if (isPrivate((List)element)) {
            return CCWPlugin.getDefault().getImageRegistry().get(CCWPlugin.PRIVATE_FUNCTION);
        } else {
            return CCWPlugin.getDefault().getImageRegistry().get(CCWPlugin.PUBLIC_FUNCTION);
        }
    }

    public StyledString getStyledText(Object element) {
      StyledString result;
      if (element instanceof List<?>) {
        List<?> list = (List<?>) element;
        result = new StyledString(getSymbol(list));
        StyledString kindString = new StyledString(
            " : " + getKind(list), //$NON-NLS-1$
            StyledString.QUALIFIER_STYLER);
        result.append(kindString);
      } else {
        // TODO: handle non-lists...
        result = new StyledString(safeToString(element));
      }
      return result;
    }

  }

  private final class DocumentChangedListener implements IDocumentListener {
    public void documentChanged(DocumentEvent event) {
      refreshInput();
    }

    public void documentAboutToBeChanged(DocumentEvent event) {
    }
  }

  private final class EditorSelectionChangedListener implements
      ISelectionChangedListener {

    private EditorSelectionChangedListener(TreeViewer viewer) {
    }

    public void selectionChanged(SelectionChangedEvent event) {
      ISelection selection = event.getSelection();
      selectInOutline(selection);

    }
  }

  private final class TreeSelectionChangedListener implements
      ISelectionChangedListener {
    public void selectionChanged(SelectionChangedEvent event) {
      ISelection selection = event.getSelection();
      if (isActivePart()) {
        // only when this is the active part, i.e. is user initiated
        selectInEditor(selection);
      }
    }
  }

  private static final String OUTLINE_VIEW_ID = "org.eclipse.ui.views.ContentOutline"; //$NON-NLS-1$
  private static final Keyword KEYWORD_LINE = Keyword.intern(null, "line"); //$NON-NLS-1$

  private final String NOT_AVAILABLE = "N/A"; //$NON-NLS-1$
  private final Object REFRESH_OUTLINE_JOB_FAMILY = new Object();
  private final IDocumentProvider documentProvider;
  private final ClojureEditor editor;
 
  private List<List> forms = new ArrayList<List>(0);
  private boolean sort = CCWPlugin.getDefault().getPreferenceStore().getBoolean("LexicalSortingAction.isChecked");

  private IDocument document;
  private TreeSelectionChangedListener treeSelectionChangedListener;
  private EditorSelectionChangedListener editorSelectionChangedListener;
  private DocumentChangedListener documentChangedListener;
  private ISelection lastSelection;
  private TreeViewer treeViewer;

  public ClojureOutlinePage(IDocumentProvider documentProvider,
      ClojureEditor editor) {
    this.documentProvider = documentProvider;
    this.editor = editor;
  }

  @Override
  public void createControl(Composite parent) {
    super.createControl(parent);
    treeViewer = getTreeViewer();
    treeViewer.setContentProvider(new ITreeContentProvider() {

      public void inputChanged(Viewer viewer, Object oldInput,
          Object newInput) {

      }

      public void dispose() {

      }

      public Object[] getElements(Object input) {
        return ((List<?>) input).toArray();
      }

      public boolean hasChildren(Object arg0) {
        return false;
      }

      public Object getParent(Object arg0) {
        return null;
      }

      public Object[] getChildren(Object arg0) {
        // TODO: handle children? Granularity, Bindings?
        return null;
      }
    });
    treeViewer.setLabelProvider(new DelegatingStyledCellLabelProvider(
        new OutlineLabelProvider()));
    treeViewer.addSelectionChangedListener(this);
    treeViewer.setInput(forms);
    treeSelectionChangedListener = new TreeSelectionChangedListener();
    treeViewer.addSelectionChangedListener(treeSelectionChangedListener);

    IPostSelectionProvider selectionProvider = (IPostSelectionProvider) editor
        .getSelectionProvider();
    editorSelectionChangedListener = new EditorSelectionChangedListener(
        treeViewer);
    selectionProvider.addPostSelectionChangedListener(editorSelectionChangedListener);
    ISelection selection = selectionProvider.getSelection();
    selectInOutline(selection);
   
    registerToolbarActions();
  }

  private void registerToolbarActions() {
    IActionBars actionBars = getSite().getActionBars();
    IToolBarManager toolBarManager= actionBars.getToolBarManager();
    toolBarManager.add(new LexicalSortingAction());
  }

  private class LexicalSortingAction extends Action {
    public LexicalSortingAction() {
      setText("Sort");
      setImageDescriptor(CCWPlugin.getDefault().getImageRegistry().getDescriptor(CCWPlugin.SORT));
      setToolTipText("Sort");
      setDescription("Sort alphabetically");
      setChecked(sort);
    }

    public void run() {
        CCWPlugin.getDefault().getPreferenceStore().setValue("LexicalSortingAction.isChecked", sort = isChecked());
        setInputInUiThread(forms);
    }
  }
 
  private static Symbol symbol (Object o) {
      return o instanceof Symbol ? (Symbol)o : null;
  }
 
  private static boolean isPrivate (List form) {
      if (form.size() < 2) return false;
      Symbol def = symbol(RT.first(form));
      Symbol name = symbol(RT.second(form));
      if (def == null || name == null) return false;
      return def.getName().matches("(defn-|defvar-)") ||
          (name.meta() != null && name.meta().valAt(Keyword.intern("private"), false).equals(Boolean.TRUE));
  }

  /**
   * Find closest matching element to line
   *
   * @param toFind
   *            line to find
   * @return
   */
  protected StructuredSelection findClosest(int toFind) {
    Object selected = null;
    for (Object o : forms) {
      if (o instanceof Obj) {
        Obj obj = (Obj) o;
        int lineNr = getLineNr(obj);
        if (lineNr >= 0 && lineNr <= toFind) {
          selected = obj;
        }

      }
    }
    if (selected != null) {
      return new StructuredSelection(selected);
    }
    return StructuredSelection.EMPTY;
  }

  public void setInput(IEditorInput editorInput) {
    document = documentProvider.getDocument(editorInput);
    documentChangedListener = new DocumentChangedListener();
    document.addDocumentListener(documentChangedListener);
    refreshInput();
    ISelection selection = editor.getSelectionProvider().getSelection();
    selectInOutline(selection);
  }

  private void refreshInput() {
    Job job = new Job("Outline browser tree refresh") {
      @Override
      protected IStatus run(IProgressMonitor monitor) {
        String string = document.get();
        LineNumberingPushbackReader pushbackReader = new LineNumberingPushbackReader(
            new StringReader(string));
        Object EOF = new Object();
        ArrayList<List> input = new ArrayList<List>();
        Object result = null;
        while (true) {
          try {
            result = LispReader.read(pushbackReader, false, EOF, false);
            if (result == EOF) break;
            if (result instanceof List) input.add((List)result);
          } catch (ReaderException e) {
              // once a syntax error occurs (often because of a namespaced keyword)
              // there's little chance that the rest of the data will be worthwhile...
              CCWPlugin.logWarning(String.format("Failed to read file %s (%s)",
                      editor.getEditorInput().getName(), e.getMessage()));
              break;
          } catch (Exception e) {
            throw new RuntimeException(e);
          }
        }
        ClojureOutlinePage.this.forms = input;
        setInputInUiThread(input);
        return Status.OK_STATUS;
      }

      @Override
      public boolean belongsTo(Object family) {
        return REFRESH_OUTLINE_JOB_FAMILY.equals(family);
      }
    };
    job.setSystem(true);
    Job.getJobManager().cancel(REFRESH_OUTLINE_JOB_FAMILY);
    job.schedule(500);
  }

  protected void setInputInUiThread (List<List> forms) {
      if (sort) {
          List<List> sorted = new ArrayList(forms);
            Collections.sort(sorted, new Comparator<List> () {
                public int compare(List o1, List o2) {
                    Symbol s1 = symbol(RT.first(o1)),
                           s2 = symbol(RT.first(o2));
                   
                    if (s1 == null && s2 == null) return 0; // mandatory for Comparator contract
                    if (s1 == null) return 1;
                    if (s2 == null) return -1;
                   
                    if (s1.getName().equals("ns") && s2.getName().equals("ns")) {
                      // fall through to order ns decls correctly
                    } else {
                      if (s1.getName().equals("ns")) return -1;
                      if (s2.getName().equals("ns")) return 1;
                    }
                   
                    if (isPrivate(o1) != isPrivate(o2)) {
                        return isPrivate(o1) ? -1 : 1;
                    }
                   
                    s1 = symbol(RT.second(o1));
                    s2 = symbol(RT.second(o2));
                   
                    if (s1 == null && s2 == null) return 0; // mandatory for Comparator contract
                    if (s1 == null) return 1;
                    if (s2 == null) return -1;
                   
                    return s1.getName().compareToIgnoreCase(s2.getName());
                }
            });
            forms = sorted;
      }
      final List<List> theForms = forms;
     
      if (getControl() != null) {
        getControl().getDisplay().asyncExec(new Runnable() {
          public void run() {
            if (getControl().isDisposed())
              return;
           
            TreeViewer treeViewer = getTreeViewer();
            if (treeViewer != null) {
              treeViewer.getTree().setRedraw(false);
              treeViewer.setInput(theForms);
              ISelection treeSelection = treeViewer.getSelection();
              if (treeSelection == null || treeSelection.isEmpty()) {
                selectInOutline(lastSelection);
              }
              treeViewer.getTree().setRedraw(true);
            }
          }
        });
      }
  }

  private void selectInEditor(ISelection selection) {
    IStructuredSelection sel = (IStructuredSelection) selection;
    if (sel.size() == 0)
      return;

    Obj obj = (Obj) sel.getFirstElement();
    int lineNr = getLineNr(obj);
    if (lineNr >= 0) {
      ClojureCore.gotoEditorLine(editor, lineNr);
    }
  }

  private int getLineNr(Obj obj) {
    int lineNr = -1;
    if (obj.meta() == null) {
      return lineNr;
    }
    IMapEntry line = obj.meta().entryAt(KEYWORD_LINE);
    if (line != null && line.val() instanceof Number) {
      lineNr = ((Number) line.val()).intValue();
    }
    return lineNr;
  }

  private String safeToString(Object value) {
    try {
      return value == null ? NOT_AVAILABLE : value.toString();
    } catch (Exception e) {
      return NOT_AVAILABLE;
    }
  }

  protected boolean isActivePart() {
    IWorkbenchPart part = getSite().getPage().getActivePart();
    return part != null && OUTLINE_VIEW_ID.equals(part.getSite().getId());
  }

  @Override
  public void dispose() {
    try {
      if (document != null)
        document.removeDocumentListener(documentChangedListener);
    } catch (Throwable t) {}
    try {
      final TreeViewer viewer = getTreeViewer();
      if (viewer != null)
        viewer.removeSelectionChangedListener(this);
      if (viewer != null)
        viewer.removeSelectionChangedListener(treeSelectionChangedListener);
    } catch (Throwable t) {}
    try {
      IPostSelectionProvider selectionProvider = (IPostSelectionProvider) editor
          .getSelectionProvider();
      if (selectionProvider != null)
        selectionProvider
            .removePostSelectionChangedListener(editorSelectionChangedListener);
    } catch (Throwable t) {}
    super.dispose();
  }

  private void selectInOutline(ISelection selection) {
    TreeViewer viewer = getTreeViewer();
    lastSelection = selection;
    if (viewer != null && selection instanceof TextSelection) {
      TextSelection textSelection = (TextSelection) selection;
      int line = textSelection.getStartLine();
      StructuredSelection newSelection = findClosest(line + 1);
      ISelection oldSelection = viewer.getSelection();
      if (!newSelection.equals(oldSelection)) {
        viewer.setSelection(newSelection);
      }
    }
  }
}
TOP

Related Classes of ccw.editors.outline.ClojureOutlinePage$TreeSelectionChangedListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.