/******************************************************************************* * 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.ui.actions; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import org.eclipse.core.filebuffers.FileBuffers; import org.eclipse.core.filebuffers.ITextFileBuffer; import org.eclipse.core.filebuffers.ITextFileBufferManager; import org.eclipse.core.filebuffers.LocationKind; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.jface.action.IAction; import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.text.DocumentRewriteSession; import org.eclipse.jface.text.DocumentRewriteSessionType; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentExtension; import org.eclipse.jface.text.IDocumentExtension4; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.formatter.FormattingContextProperties; import org.eclipse.jface.text.formatter.IFormattingContext; import org.eclipse.jface.text.formatter.MultiPassContentFormatter; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.window.Window; import org.eclipse.ui.IObjectActionDelegate; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchSite; import org.eclipse.ui.PlatformUI; import org.eclipse.wst.jsdt.core.IJavaScriptUnit; import org.eclipse.wst.jsdt.core.IJavaScriptElement; import org.eclipse.wst.jsdt.core.IJavaScriptProject; import org.eclipse.wst.jsdt.core.IPackageFragment; import org.eclipse.wst.jsdt.core.IPackageFragmentRoot; import org.eclipse.wst.jsdt.core.JavaScriptModelException; import org.eclipse.wst.jsdt.internal.corext.util.JavaModelUtil; import org.eclipse.wst.jsdt.internal.corext.util.Messages; import org.eclipse.wst.jsdt.internal.corext.util.Resources; import org.eclipse.wst.jsdt.internal.ui.IJavaHelpContextIds; import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin; import org.eclipse.wst.jsdt.internal.ui.actions.ActionMessages; import org.eclipse.wst.jsdt.internal.ui.actions.WorkbenchRunnableAdapter; import org.eclipse.wst.jsdt.internal.ui.browsing.LogicalPackage; import org.eclipse.wst.jsdt.internal.ui.dialogs.OptionalMessageDialog; import org.eclipse.wst.jsdt.internal.ui.text.comment.CommentFormattingContext; import org.eclipse.wst.jsdt.internal.ui.text.comment.CommentFormattingStrategy; import org.eclipse.wst.jsdt.internal.ui.text.java.JavaFormattingStrategy; import org.eclipse.wst.jsdt.internal.ui.util.ExceptionHandler; import org.eclipse.wst.jsdt.ui.JavaScriptUI; import org.eclipse.wst.jsdt.ui.text.IJavaScriptPartitions; /** * Formats the code of the compilation units contained in the selection. * <p> * The action is applicable to selections containing elements of * type <code>IJavaScriptUnit</code>, <code>IPackage * </code>, <code>IPackageFragmentRoot/code> and * <code>IJavaScriptProject</code>. * </p> * <p> * This class may be instantiated; it is not intended to be subclassed. * </p> * * * Provisional API: This class/interface is part of an interim API that is still under development and expected to * change significantly before reaching stability. It is being made available at this early stage to solicit feedback * from pioneering adopters on the understanding that any code that uses this API will almost certainly be broken * (repeatedly) as the API evolves. */ public class FormatAllAction extends SelectionDispatchAction { private DocumentRewriteSession fRewriteSession; /* (non-Javadoc) * Class implements IObjectActionDelegate */ public static class ObjectDelegate implements IObjectActionDelegate { private FormatAllAction fAction; public void setActivePart(IAction action, IWorkbenchPart targetPart) { fAction= new FormatAllAction(targetPart.getSite()); } public void run(IAction action) { fAction.run(); } public void selectionChanged(IAction action, ISelection selection) { if (fAction == null) action.setEnabled(false); } } /** * Creates a new <code>FormatAllAction</code>. The action requires * that the selection provided by the site's selection provider is of type <code> * org.eclipse.jface.viewers.IStructuredSelection</code>. * * @param site the site providing context information for this action */ public FormatAllAction(IWorkbenchSite site) { super(site); setText(ActionMessages.FormatAllAction_label); setToolTipText(ActionMessages.FormatAllAction_tooltip); setDescription(ActionMessages.FormatAllAction_description); PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IJavaHelpContextIds.FORMAT_ALL); } /* (non-Javadoc) * Method declared on SelectionDispatchAction. */ public void selectionChanged(ITextSelection selection) { // do nothing } /* (non-Javadoc) * Method declared on SelectionDispatchAction. */ public void selectionChanged(IStructuredSelection selection) { setEnabled(isEnabled(selection)); } private IJavaScriptUnit[] getCompilationUnits(IStructuredSelection selection) { HashSet result= new HashSet(); Object[] selected= selection.toArray(); for (int i= 0; i < selected.length; i++) { try { if (selected[i] instanceof IJavaScriptElement) { IJavaScriptElement elem= (IJavaScriptElement) selected[i]; if (elem.exists()) { switch (elem.getElementType()) { case IJavaScriptElement.TYPE: if (elem.getParent().getElementType() == IJavaScriptElement.JAVASCRIPT_UNIT) { result.add(elem.getParent()); } break; case IJavaScriptElement.JAVASCRIPT_UNIT: result.add(elem); break; case IJavaScriptElement.PACKAGE_FRAGMENT: collectCompilationUnits((IPackageFragment) elem, result); break; case IJavaScriptElement.PACKAGE_FRAGMENT_ROOT: collectCompilationUnits((IPackageFragmentRoot) elem, result); break; case IJavaScriptElement.JAVASCRIPT_PROJECT: IPackageFragmentRoot[] roots= ((IJavaScriptProject) elem).getPackageFragmentRoots(); for (int k= 0; k < roots.length; k++) { collectCompilationUnits(roots[k], result); } break; } } } else if (selected[i] instanceof LogicalPackage) { IPackageFragment[] packageFragments= ((LogicalPackage)selected[i]).getFragments(); for (int k= 0; k < packageFragments.length; k++) { IPackageFragment pack= packageFragments[k]; if (pack.exists()) { collectCompilationUnits(pack, result); } } } } catch (JavaScriptModelException e) { JavaScriptPlugin.log(e); } } return (IJavaScriptUnit[]) result.toArray(new IJavaScriptUnit[result.size()]); } private void collectCompilationUnits(IPackageFragment pack, Collection result) throws JavaScriptModelException { result.addAll(Arrays.asList(pack.getJavaScriptUnits())); } private void collectCompilationUnits(IPackageFragmentRoot root, Collection result) throws JavaScriptModelException { if (root.getKind() == IPackageFragmentRoot.K_SOURCE) { IJavaScriptElement[] children= root.getChildren(); for (int i= 0; i < children.length; i++) { collectCompilationUnits((IPackageFragment) children[i], result); } } } private boolean isEnabled(IStructuredSelection selection) { Object[] selected= selection.toArray(); for (int i= 0; i < selected.length; i++) { try { if (selected[i] instanceof IJavaScriptElement) { IJavaScriptElement elem= (IJavaScriptElement) selected[i]; if (elem.exists()) { switch (elem.getElementType()) { case IJavaScriptElement.TYPE: return elem.getParent().getElementType() == IJavaScriptElement.JAVASCRIPT_UNIT; // for browsing perspective case IJavaScriptElement.JAVASCRIPT_UNIT: return true; case IJavaScriptElement.PACKAGE_FRAGMENT: case IJavaScriptElement.PACKAGE_FRAGMENT_ROOT: IPackageFragmentRoot root= (IPackageFragmentRoot) elem.getAncestor(IJavaScriptElement.PACKAGE_FRAGMENT_ROOT); return (root.getKind() == IPackageFragmentRoot.K_SOURCE); case IJavaScriptElement.JAVASCRIPT_PROJECT: // https://bugs.eclipse.org/bugs/show_bug.cgi?id=65638 return true; } } } else if (selected[i] instanceof LogicalPackage) { return true; } } catch (JavaScriptModelException e) { if (JavaModelUtil.isExceptionToBeLogged(e)) JavaScriptPlugin.log(e); } } return false; } /* (non-Javadoc) * Method declared on SelectionDispatchAction. */ public void run(ITextSelection selection) { } /* (non-Javadoc) * Method declared on SelectionDispatchAction. */ public void run(IStructuredSelection selection) { IJavaScriptUnit[] cus= getCompilationUnits(selection); if (cus.length == 0) { MessageDialog.openInformation(getShell(), ActionMessages.FormatAllAction_EmptySelection_title, ActionMessages.FormatAllAction_EmptySelection_description); return; } try { if (cus.length == 1) { JavaScriptUI.openInEditor(cus[0]); } else { int returnCode= OptionalMessageDialog.open("FormatAll", //$NON-NLS-1$ getShell(), ActionMessages.FormatAllAction_noundo_title, null, ActionMessages.FormatAllAction_noundo_message, MessageDialog.WARNING, new String[] {IDialogConstants.OK_LABEL, IDialogConstants.CANCEL_LABEL}, 0); if (returnCode != OptionalMessageDialog.NOT_SHOWN && returnCode != Window.OK ) return; } } catch (CoreException e) { ExceptionHandler.handle(e, getShell(), ActionMessages.FormatAllAction_error_title, ActionMessages.FormatAllAction_error_message); } runOnMultiple(cus); } private IResource[] getResources(IJavaScriptUnit[] cus) { IResource[] res= new IResource[cus.length]; for (int i= 0; i < res.length; i++) { res[i]= cus[i].getResource(); } return res; } /** * Perform format all on the given compilation units. * @param cus The compilation units to format. */ public void runOnMultiple(final IJavaScriptUnit[] cus) { try { final MultiStatus status= new MultiStatus(JavaScriptUI.ID_PLUGIN, IStatus.OK, ActionMessages.FormatAllAction_status_description, null); IStatus valEditStatus= Resources.makeCommittable(getResources(cus), getShell()); if (valEditStatus.matches(IStatus.CANCEL)) { return; } status.merge(valEditStatus); if (!status.matches(IStatus.ERROR)) { PlatformUI.getWorkbench().getProgressService().run(true, true, new WorkbenchRunnableAdapter(new IWorkspaceRunnable() { public void run(IProgressMonitor monitor) { doRunOnMultiple(cus, status, monitor); } })); // workspace lock } if (!status.isOK()) { String title= ActionMessages.FormatAllAction_multi_status_title; ErrorDialog.openError(getShell(), title, null, status); } } catch (InvocationTargetException e) { ExceptionHandler.handle(e, getShell(), ActionMessages.FormatAllAction_error_title, ActionMessages.FormatAllAction_error_message); } catch (InterruptedException e) { // Canceled by user } } private static Map getFomatterSettings(IJavaScriptProject project) { return new HashMap(project.getOptions(true)); } private void doFormat(IDocument document, Map options) { final IFormattingContext context = new CommentFormattingContext(); try { context.setProperty(FormattingContextProperties.CONTEXT_PREFERENCES, options); context.setProperty(FormattingContextProperties.CONTEXT_DOCUMENT, Boolean.valueOf(true)); final MultiPassContentFormatter formatter= new MultiPassContentFormatter(IJavaScriptPartitions.JAVA_PARTITIONING, IDocument.DEFAULT_CONTENT_TYPE); formatter.setMasterStrategy(new JavaFormattingStrategy()); formatter.setSlaveStrategy(new CommentFormattingStrategy(), IJavaScriptPartitions.JAVA_DOC); formatter.setSlaveStrategy(new CommentFormattingStrategy(), IJavaScriptPartitions.JAVA_SINGLE_LINE_COMMENT); formatter.setSlaveStrategy(new CommentFormattingStrategy(), IJavaScriptPartitions.JAVA_MULTI_LINE_COMMENT); try { startSequentialRewriteMode(document); formatter.format(document, context); } finally { stopSequentialRewriteMode(document); } } finally { context.dispose(); } } private void startSequentialRewriteMode(IDocument document) { if (document instanceof IDocumentExtension4) { IDocumentExtension4 extension= (IDocumentExtension4) document; fRewriteSession= extension.startRewriteSession(DocumentRewriteSessionType.SEQUENTIAL); } else if (document instanceof IDocumentExtension) { IDocumentExtension extension= (IDocumentExtension) document; extension.startSequentialRewrite(false); } } private void stopSequentialRewriteMode(IDocument document) { if (document instanceof IDocumentExtension4) { IDocumentExtension4 extension= (IDocumentExtension4) document; extension.stopRewriteSession(fRewriteSession); } else if (document instanceof IDocumentExtension) { IDocumentExtension extension= (IDocumentExtension)document; extension.stopSequentialRewrite(); } } private void doRunOnMultiple(IJavaScriptUnit[] cus, MultiStatus status, IProgressMonitor monitor) throws OperationCanceledException { if (monitor == null) { monitor= new NullProgressMonitor(); } monitor.setTaskName(ActionMessages.FormatAllAction_operation_description); monitor.beginTask("", cus.length * 4); //$NON-NLS-1$ try { Map lastOptions= null; IJavaScriptProject lastProject= null; for (int i= 0; i < cus.length; i++) { IJavaScriptUnit cu= cus[i]; IPath path= cu.getPath(); if (lastProject == null || !lastProject.equals(cu.getJavaScriptProject())) { lastProject= cu.getJavaScriptProject(); lastOptions= getFomatterSettings(lastProject); } if (monitor.isCanceled()) { throw new OperationCanceledException(); } if (cu.getResource().getResourceAttributes().isReadOnly()) { String message= Messages.format(ActionMessages.FormatAllAction_read_only_skipped, path.toString()); status.add(new Status(IStatus.WARNING, JavaScriptUI.ID_PLUGIN, IStatus.WARNING, message, null)); continue; } ITextFileBufferManager manager= FileBuffers.getTextFileBufferManager(); try { try { manager.connect(path, LocationKind.IFILE, new SubProgressMonitor(monitor, 1)); monitor.subTask(path.makeRelative().toString()); ITextFileBuffer fileBuffer= manager.getTextFileBuffer(path, LocationKind.IFILE); formatCompilationUnit(fileBuffer, lastOptions); if (fileBuffer.isDirty() && !fileBuffer.isShared()) { fileBuffer.commit(new SubProgressMonitor(monitor, 2), false); } else { monitor.worked(2); } } finally { manager.disconnect(path, LocationKind.IFILE, new SubProgressMonitor(monitor, 1)); } } catch (CoreException e) { String message= Messages.format(ActionMessages.FormatAllAction_problem_accessing, new String[] { path.toString(), e.getLocalizedMessage() }); status.add(new Status(IStatus.WARNING, JavaScriptUI.ID_PLUGIN, IStatus.WARNING, message, e)); } } } finally { monitor.done(); } } private void formatCompilationUnit(final ITextFileBuffer fileBuffer, final Map options) { if (fileBuffer.isShared()) { getShell().getDisplay().syncExec(new Runnable() { public void run() { doFormat(fileBuffer.getDocument(), options); } }); } else { doFormat(fileBuffer.getDocument(), options); // run in context thread } } }