/*******************************************************************************
* Copyright (c) 2005, 2012 eBay Inc.
* 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
*
*******************************************************************************/
/**
*
*/
package org.eclipse.vjet.eclipse.internal.ui.view.ast;
import java.io.File;
import java.io.FileReader;
import java.util.Collections;
import org.apache.tools.ant.util.FileUtils;
import org.eclipse.dltk.mod.core.IModelElement;
import org.eclipse.dltk.mod.internal.core.NativeVjoSourceModule;
import org.eclipse.dltk.mod.internal.core.VjoSourceModule;
import org.eclipse.dltk.mod.internal.ui.editor.ScriptSourceViewer;
import org.eclipse.dltk.mod.ui.DLTKUILanguageManager;
import org.eclipse.dltk.mod.ui.DLTKUIPlugin;
import org.eclipse.dltk.mod.ui.IDLTKUILanguageToolkit;
import org.eclipse.dltk.mod.ui.text.ScriptSourceViewerConfiguration;
import org.eclipse.dltk.mod.ui.text.ScriptTextTools;
import org.eclipse.dltk.mod.ui.util.PixelConverter;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ILabelProvider;
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.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.mod.wst.jsdt.core.ast.IASTNode;
import org.eclipse.mod.wst.jsdt.core.compiler.IProblem;
import org.eclipse.mod.wst.jsdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.editors.text.ILocationProvider;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.vjet.dsf.jstojava.parser.SyntaxTreeFactory2;
import org.eclipse.vjet.eclipse.core.VjoNature;
import org.eclipse.vjet.eclipse.internal.ui.editor.VjoEditor;
import org.eclipse.vjet.eclipse.internal.ui.nodeprinter.INodePrinter;
import org.eclipse.vjet.eclipse.internal.ui.nodeprinter.NodePrinterFactory;
import org.eclipse.vjet.eclipse.internal.ui.text.IJavaScriptPartitions;
import org.eclipse.vjet.eclipse.internal.ui.text.SimpleVjoSourceViewerConfiguration;
import org.eclipse.vjet.eclipse.ui.VjetUIPlugin;
/**
* ast view
*
*
*
*/
public class ASTView extends ViewPart implements ISelectionListener {
private TreeViewer leftTreeViewer;
private TreeViewer rightTreeViewer;
private VjoEditor vjoEditor; //the editor that provide the content
private IPartListener partListener = new PartListener();
private IDocumentListener documentListener = new DocumentListener();
private IDoubleClickListener doubleClickListener = new DoubleClickListener();
private CompilationUnitDeclaration orignalAst;
private CompilationUnitDeclaration recoveryAst;
private ScriptSourceViewer leftSourceViewer;
private ScriptSourceViewer rightSourceViewer;
private SkipDocumentChangeAction skipAction = new SkipDocumentChangeAction();
/* (non-Javadoc)
* @see org.eclipse.ui.part.ViewPart#init(org.eclipse.ui.IViewSite)
*/
public void init(IViewSite site) throws PartInitException {
super.init(site);
this.getSite().getPage().getWorkbenchWindow().getSelectionService().addPostSelectionListener(this);
this.getSite().getPage().getWorkbenchWindow().getPartService().addPartListener(partListener);
}
/* (non-Javadoc)
* @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
*/
@Override
public void createPartControl(Composite parent) {
parent.setLayout(new GridLayout());
//fill action bar
this.fillActionBar();
SashForm fullForm = new SashForm(parent, SWT.HORIZONTAL);
fullForm.setLayoutData(new GridData(GridData.FILL_BOTH));
//left panel
SashForm leftForm = new SashForm(fullForm, SWT.VERTICAL);
leftForm.setLayoutData(new GridData(GridData.FILL_BOTH));
this.leftTreeViewer = this.createTreeViewer(leftForm, false);
this.leftSourceViewer = this.createSourceViewer(leftForm);
this.leftTreeViewer.addSelectionChangedListener(new SelectionChangeListener(this.leftSourceViewer));
//right panel
SashForm rightForm = new SashForm(fullForm, SWT.VERTICAL);
rightForm.setLayoutData(new GridData(GridData.FILL_BOTH));
this.rightTreeViewer = this.createTreeViewer(rightForm, true);
this.rightSourceViewer = this.createSourceViewer(rightForm);
this.rightTreeViewer.addSelectionChangedListener(new SelectionChangeListener(this.rightSourceViewer));
//set selection provider
this.getSite().setSelectionProvider(this.leftTreeViewer);
}
private void fillActionBar() {
IActionBars actionBars = this.getViewSite().getActionBars();
actionBars.getToolBarManager().add(skipAction);
}
private TreeViewer createTreeViewer(Composite parent, boolean isRecovery) {
TreeViewer treeViewer = new TreeViewer(parent);
treeViewer.getTree().setLayoutData(new GridData(GridData.FILL_BOTH));
treeViewer.setContentProvider(new ASTTreeContentProvider());
treeViewer.setLabelProvider(new ASTTreeLabelProvider(isRecovery));
treeViewer.addDoubleClickListener(this.doubleClickListener);
this.initContextMenu(treeViewer);
return treeViewer;
}
private ScriptSourceViewer createSourceViewer(Composite parent) {
IDocument document= new Document();
IDLTKUILanguageToolkit toolkit = DLTKUILanguageManager.getLanguageToolkit(VjoNature.NATURE_ID);
ScriptTextTools textTools = VjetUIPlugin.getDefault().getTextTools();
textTools.setupDocumentPartitioner(document, IJavaScriptPartitions.JS_PARTITIONING);
IPreferenceStore store = toolkit.getPreferenceStore();
ScriptSourceViewer viewer = new ScriptSourceViewer(parent, null, null, false, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL, store);
// ScriptSourceViewerConfiguration configuration = toolkit.createSourceViewerConfiguration();
ScriptSourceViewerConfiguration configuration = new SimpleVjoSourceViewerConfiguration(toolkit.getTextTools()
.getColorManager(), toolkit.getPreferenceStore(), null,
IJavaScriptPartitions.JS_PARTITIONING, false);
viewer.configure(configuration);
viewer.setEditable(false);
viewer.setDocument(document);
Control control= viewer.getControl();
GridData data= new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.FILL_HORIZONTAL);
data.heightHint= new PixelConverter(parent).convertHeightInCharsToPixels(3);
control.setLayoutData(data);
return viewer;
}
private void initContextMenu(TreeViewer viewer) {
MenuManager popupMenuManager = new MenuManager("#PopupMenu");
popupMenuManager.add(new CopyStructureAction(viewer));
popupMenuManager.add(new CopyPropertiesAction(viewer));
Menu popupMenu = popupMenuManager.createContextMenu(viewer.getTree());
viewer.getTree().setMenu(popupMenu);
getSite().registerContextMenu(popupMenuManager, viewer);
}
private String copyStructure(Object node, int tier) {
ILabelProvider labelProvider = (ILabelProvider)this.leftTreeViewer.getLabelProvider();
StringBuilder structureBuilder = new StringBuilder(labelProvider.getText(node));
ITreeContentProvider contentProvider = (ITreeContentProvider)this.leftTreeViewer.getContentProvider();
Object[] children = contentProvider.getChildren(node);
++ tier;
for (int i = 0; i < children.length; i++) {
String childrenStructure = copyStructure(children[i], tier);
structureBuilder.append("\n");
for (int j = 0; j < tier; j++) {
structureBuilder.append("\t");
}
structureBuilder.append(childrenStructure);
}
return structureBuilder.toString();
}
private String copyProperties(Object node) {
INodePrinter nodePrinter = NodePrinterFactory.getNodePrinter(node);
if (nodePrinter == null)
return "";
String[] names = nodePrinter.getPropertyNames(node);
if (names.length == 0)
return "";
StringBuilder stringBuilder = new StringBuilder();
Object[] values = nodePrinter.getPropertyValuies(node);
for (int i = 0; i < names.length; i++) {
stringBuilder.append(names[i] + ":" + values[i]);
if (i != names.length - 1)
stringBuilder.append("\n");
}
return stringBuilder.toString();
}
/* (non-Javadoc)
* @see org.eclipse.ui.part.WorkbenchPart#setFocus()
*/
@Override
public void setFocus() {
// TODO Auto-generated method stub
}
/* (non-Javadoc)
* @see org.eclipse.ui.ISelectionListener#selectionChanged(org.eclipse.ui.IWorkbenchPart, org.eclipse.jface.viewers.ISelection)
*/
public void selectionChanged(IWorkbenchPart part, ISelection selection) {
if (!(part instanceof IEditorPart))
return;
if (!(part instanceof VjoEditor)) {
this.cleanUp();
return;
}
if (this.vjoEditor != part) {
IModelElement modelElement = ((VjoEditor)part).getInputModelElement();
if (!(modelElement instanceof VjoSourceModule)) {
this.cleanUp();
return;
}
VjoSourceModule sourceModule = ((VjoSourceModule)modelElement);
CompilationUnitDeclaration ast = getASTUnit(sourceModule, (VjoEditor)part);
//add to fix NPE
if (ast == null)
return;
CompilationUnitDeclaration recoveryAST = getRecoveryASTUnit(ast, sourceModule, (VjoEditor)part);
this.orignalAst = ast;
this.recoveryAst = recoveryAST;
this.leftTreeViewer.setInput(new Object[]{ast});
this.rightTreeViewer.setInput(new Object[]{recoveryAST});
this.vjoEditor = (VjoEditor)part;
this.vjoEditor.getScriptSourceViewer().getDocument().addDocumentListener(this.documentListener);
this.leftTreeViewer.expandAll();
this.leftTreeViewer.collapseAll();
this.rightTreeViewer.expandAll();
this.rightTreeViewer.collapseAll();
}
//select the corresponding ast node
if (selection instanceof ITextSelection) {
ITextSelection textSelection = (ITextSelection)selection;
IASTNode node = getASTNode(this.orignalAst, textSelection.getOffset());
if (node != null)
this.leftTreeViewer.setSelection(new StructuredSelection(node), true);
node = getASTNode(this.recoveryAst, textSelection.getOffset());
if (node != null)
this.rightTreeViewer.setSelection(new StructuredSelection(node), true);
}
}
private IASTNode getASTNode(CompilationUnitDeclaration ast, int offset) {
IASTNode parentNode = ast;
while (getSubNode(parentNode, offset) != null) {
parentNode = getSubNode(parentNode, offset);
}
return parentNode;
}
private IASTNode getSubNode(IASTNode parentNode, int offset) {
ITreeContentProvider contentProvider = (ITreeContentProvider)this.leftTreeViewer.getContentProvider();
Object[] children = contentProvider.getChildren(parentNode);
if (children == null || children.length == 0)
return null;
for (int i = 0; i < children.length; i++) {
if (!(children[i] instanceof IASTNode))
continue;
IASTNode child = (IASTNode)children[i];
if (offset >= child.sourceStart() && offset < child.sourceEnd())
return child;
}
return null;
}
/**
* create the recovery AST Unit based on the existing ast unit
*
* @param declaration
* @param sourceModule
* @param vjoEditor
* @return
*/
private CompilationUnitDeclaration getRecoveryASTUnit(CompilationUnitDeclaration declaration, VjoSourceModule sourceModule, VjoEditor vjoEditor) {
String fileName = getFileName(sourceModule, vjoEditor);
final String jsSource = getSource(sourceModule, vjoEditor);
return SyntaxTreeFactory2.fixedProblems(Collections.EMPTY_MAP, jsSource.toCharArray(), fileName, null, declaration);
}
/**
* create the AST unit
*
* @param sourceModule
* @param vjoEditor
* @return
*/
private CompilationUnitDeclaration getASTUnit(VjoSourceModule sourceModule, VjoEditor vjoEditor) {
try {
String fileName = getFileName(sourceModule, vjoEditor);
final String jsSource = getSource(sourceModule, vjoEditor);
CompilationUnitDeclaration ast = SyntaxTreeFactory2.
createASTCompilationResult(Collections.EMPTY_MAP, jsSource.toCharArray(), fileName, null)
.getCompilationUnitDeclaration();
return ast;
} catch (Exception e) {
return null;
}
}
private String getFileName(VjoSourceModule sourceModule, VjoEditor vjoEditor) {
if (sourceModule instanceof NativeVjoSourceModule) {
IEditorInput editorInput = vjoEditor.getEditorInput();
String filePath = ((ILocationProvider)editorInput).getPath(editorInput).toOSString();
return new File(filePath).getName();
}
else {
return new String(sourceModule.getFileName());
}
}
private String getSource(VjoSourceModule sourceModule, VjoEditor vjoEditor) {
try {
if (sourceModule instanceof NativeVjoSourceModule) {
IEditorInput editorInput = vjoEditor.getEditorInput();
String filePath = ((ILocationProvider)editorInput).getPath(editorInput).toOSString();
return FileUtils.readFully(new FileReader(filePath));
}
else {
return convertHTMLCommentsToJsComments(sourceModule.getSource());
}
} catch (Exception e) {
DLTKUIPlugin.log(e);
return null;
}
}
private String convertHTMLCommentsToJsComments(String source) {
if(source.contains("<!--")){
source = source.replace("<!--", "//--");
}
if(source.contains("-->")){
source = source.replace("-->", "///");
}
return source;
}
/* (non-Javadoc)
* @see org.eclipse.ui.part.WorkbenchPart#dispose()
*/
public void dispose() {
if (this.vjoEditor != null)
this.vjoEditor.getScriptSourceViewer().getDocument().removeDocumentListener(this.documentListener);
super.dispose();
this.getSite().getPage().getWorkbenchWindow().getSelectionService().removePostSelectionListener(this);
this.getSite().getPage().getWorkbenchWindow().getPartService().removePartListener(partListener);
}
private void cleanUp() {
this.leftTreeViewer.setInput(null);
this.rightTreeViewer.setInput(null);
if (this.vjoEditor != null)
this.vjoEditor.getScriptSourceViewer().getDocument().removeDocumentListener(this.documentListener);
this.vjoEditor = null;
}
/**
* vjo editor part listener
*
*
*
*/
private class PartListener implements IPartListener {
public void partActivated(IWorkbenchPart part) {
}
public void partBroughtToTop(IWorkbenchPart part) {
}
/*
* clean the content in script view
*
* @see org.eclipse.ui.IPartListener#partClosed(org.eclipse.ui.IWorkbenchPart)
*/
public void partClosed(IWorkbenchPart part) {
if (vjoEditor == part) {
cleanUp();
}
}
public void partDeactivated(IWorkbenchPart part) {
// TODO Auto-generated method stub
}
public void partOpened(IWorkbenchPart part) {
// TODO Auto-generated method stub
}
}
/**
* update ast unit view, when ast unit changed (document changed)
*
*
*
*/
private class DocumentListener implements IDocumentListener {
public void documentChanged(DocumentEvent event) {
//if skip document change, return
if (skipAction.isChecked())
return;
IModelElement modelElement = vjoEditor.getInputModelElement();
if (!(modelElement instanceof VjoSourceModule)) {
return;
}
VjoSourceModule sourceModule = (VjoSourceModule)modelElement;
CompilationUnitDeclaration astUnit = getASTUnit(sourceModule, vjoEditor);
//add to fix NPE
if (astUnit == null)
return;
CompilationUnitDeclaration recoveryUnit = getRecoveryASTUnit(astUnit, sourceModule, vjoEditor);
orignalAst = astUnit;
recoveryAst = recoveryUnit;
leftTreeViewer.setInput(new Object[] {astUnit});
leftTreeViewer.expandAll();
leftTreeViewer.collapseAll();
rightTreeViewer.setInput(new Object[]{recoveryUnit});
rightTreeViewer.expandAll();
rightTreeViewer.collapseAll();
}
public void documentAboutToBeChanged(DocumentEvent event) {
// nothing to do
}
}
/**
* tree viewer selection change listener, fetch ast code and update the source viewer
*
*
*
*/
private class SelectionChangeListener implements ISelectionChangedListener {
private ScriptSourceViewer sourceViewer;
public SelectionChangeListener(ScriptSourceViewer sourceViewer) {
this.sourceViewer = sourceViewer;
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent)
*/
public void selectionChanged(SelectionChangedEvent event) {
Object object = ((TreeSelection)event.getSelection()).getFirstElement();
if (object instanceof IProblem) {
String message = ((IProblem)object).getMessage();
//update source viewer
IDocument document = this.sourceViewer.getDocument();
document.set(message);
this.sourceViewer.setDocument(document, 0, document.getLength());
}
if (!(object instanceof IASTNode))
return;
String astCode = ((IASTNode)object).toString();
if (astCode == null)
return;
//update source viewer
IDocument document = this.sourceViewer.getDocument();
document.set(astCode);
this.sourceViewer.setDocument(document, 0, document.getLength());
}
}
/**
*
*
*/
private class DoubleClickListener implements IDoubleClickListener {
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.IDoubleClickListener#doubleClick(org.eclipse.jface.viewers.DoubleClickEvent)
*/
public void doubleClick(DoubleClickEvent event) {
Object element = ((IStructuredSelection)event.getSelection()).getFirstElement();
if (!(element instanceof IASTNode) && !(element instanceof IProblem))
return;
if (element instanceof IProblem) {
IProblem problem = (IProblem)element;
int length = problem.getSourceEnd() - problem.getSourceStart() + 1;
vjoEditor.getScriptSourceViewer().revealRange(problem.getSourceStart(), length);
vjoEditor.getScriptSourceViewer().setSelectedRange(problem.getSourceStart(), length);
}
if (element instanceof IASTNode) {
IASTNode node = (IASTNode)element;
int length = node.sourceEnd() - node.sourceStart() + 1;
vjoEditor.getScriptSourceViewer().revealRange(node.sourceStart(), length);
vjoEditor.getScriptSourceViewer().setSelectedRange(node.sourceStart(), length);
}
}
};
private class CopyStructureAction extends Action {
private TreeViewer viewer;
public CopyStructureAction(TreeViewer viewer) {
super("Copy Structure");
this.viewer = viewer;
}
/* (non-Javadoc)
* @see org.eclipse.jface.action.Action#run()
*/
public void run() {
Object selectedNode = ((IStructuredSelection)this.viewer.getSelection()).getFirstElement();
String structure = copyStructure(selectedNode, 0);
Clipboard clipboard = new Clipboard(Display.getCurrent());
clipboard.setContents(new Object[] {structure}, new Transfer[] {TextTransfer.getInstance()});
clipboard.dispose();
}
}
private class CopyPropertiesAction extends Action {
private TreeViewer viewer;
public CopyPropertiesAction(TreeViewer viewer) {
super("Copy Properties");
this.viewer = viewer;
}
/* (non-Javadoc)
* @see org.eclipse.jface.action.Action#run()
*/
public void run() {
Object selectedNode = ((IStructuredSelection)this.viewer.getSelection()).getFirstElement();
String properties = copyProperties(selectedNode);
Clipboard clipboard = new Clipboard(Display.getCurrent());
clipboard.setContents(new Object[] {properties}, new Transfer[] {TextTransfer.getInstance()});
clipboard.dispose();
}
}
}