com.google.dart.tools.ui.internal.formatter.DartFormatter.java Source code

Java tutorial

Introduction

Here is the source code for com.google.dart.tools.ui.internal.formatter.DartFormatter.java

Source

/*
 * Copyright (c) 2013, the Dart project authors.
 * 
 * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.dart.tools.ui.internal.formatter;

import com.google.dart.tools.core.DartCore;
import com.google.dart.tools.core.dart2js.ProcessRunner;
import com.google.dart.tools.core.model.DartSdkManager;
import com.google.dart.tools.core.utilities.general.StringUtilities;
import com.google.dart.tools.core.utilities.io.FileUtilities;
import com.google.dart.tools.ui.DartToolsPlugin;
import com.google.dart.tools.ui.actions.DartEditorActionDefinitionIds;
import com.google.dart.tools.ui.internal.text.editor.DartEditor;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.window.IShellProvider;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.WorkspaceAction;
import org.eclipse.ui.ide.ResourceUtil;
import org.eclipse.ui.internal.editors.text.EditorsPlugin;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Launches the <code>dartfmt</code> process collecting stdout, stderr, and exit code information.
 */
@SuppressWarnings("restriction")
public class DartFormatter {
    public static class DartFmtRunner {

        public static FormattedSource format(final String source, final Point selection, IProgressMonitor monitor)
                throws IOException, CoreException {

            File dartfmt = DartSdkManager.getManager().getSdk().getDartFmtExecutable();
            if (!dartfmt.canExecute()) {
                return null;
            }

            if (source.length() == 0) {
                FormattedSource result = new FormattedSource();
                result.source = source;
                return result;
            }

            ProcessBuilder builder = new ProcessBuilder();

            List<String> args = new ArrayList<String>();
            args.add(dartfmt.getPath());

            if (selection != null) {
                args.add(ARGS_PRESERVE_FLAG);
                args.add(selection.x + ":" + selection.y);
            }

            if (getMaxLineLengthEnabled() && getMaxLineLength().length() > 0) {
                args.add(ARGS_MAX_LINE_LEN_FLAG);
                args.add(getMaxLineLength());
            }

            args.add(ARGS_MACHINE_FORMAT_FLAG);

            builder.command(args);
            builder.redirectErrorStream(true);

            ProcessRunner runner = new ProcessRunner(builder) {
                @Override
                protected void processStarted(Process process) throws IOException {
                    BufferedWriter writer = new BufferedWriter(
                            new OutputStreamWriter(process.getOutputStream(), "UTF-8"), source.length());
                    writer.append(source);
                    writer.close();
                }
            };

            runner.runSync(monitor);

            StringBuilder sb = new StringBuilder();

            if (!runner.getStdOut().isEmpty()) {
                sb.append(runner.getStdOut());
            }

            //TODO (pquitslund): better error handling
            if (runner.getExitCode() != 0) {
                sb.append(runner.getStdErr());
                throw new IOException(sb.toString());
            }

            String formattedSource = sb.toString();
            if (!formattedSource.startsWith("{")) {
                throw new IOException(formattedSource);
            }

            try {
                JSONObject json = new JSONObject(formattedSource);
                String sourceString = (String) json.get(JSON_SOURCE_KEY);
                JSONObject selectionJson = (JSONObject) json.get(JSON_SELECTION_KEY);
                //TODO (pquitslund): figure out why we (occasionally) need to remove an extra trailing NEWLINE
                if (sourceString.endsWith("\n\n")) {
                    sourceString = sourceString.substring(0, sourceString.length() - 1);
                }
                // prepare FormattedSource
                FormattedSource result = new FormattedSource();
                result.source = sourceString;
                result.selectionOffset = selectionJson.getInt(JSON_OFFSET_KEY);
                result.selectionLength = selectionJson.getInt(JSON_LENGTH_KEY);
                // compute change
                if (!sourceString.equals(source)) {
                    int prefixLength = StringUtilities.findCommonPrefix(source, sourceString);
                    int suffixLength = StringUtilities.findCommonSuffix(source, sourceString);
                    String prefix = source.substring(0, prefixLength);
                    String suffix = source.substring(source.length() - suffixLength, source.length());
                    int commonLength = StringUtilities.findCommonOverlap(prefix, suffix);
                    suffixLength -= commonLength;
                    result.changeOffset = prefixLength;
                    result.changeLength = source.length() - prefixLength - suffixLength;
                    int replacementEnd = sourceString.length() - suffixLength;
                    result.changeReplacement = sourceString.substring(prefixLength, replacementEnd);
                }
                // done
                return result;
            } catch (JSONException e) {
                DartToolsPlugin.log(e);
                throw new IOException(e);
            }

        }
    }

    public static class FormatFileAction extends WorkspaceAction {

        private List<IFile> files = Arrays.asList(new IFile[0]);

        public FormatFileAction(IShellProvider provider) {
            super(provider, "Format");
            setId(DartEditorActionDefinitionIds.QUICK_FORMAT);
            setActionDefinitionId(DartEditorActionDefinitionIds.QUICK_FORMAT);
        }

        @Override
        public void run() {
            for (IFile file : files) {
                try {
                    format(file, new NullProgressMonitor());
                } catch (Exception e) {
                    DartCore.logError(e);
                }
            }
        }

        @Override
        protected String getOperationMessage() {
            return "Formatting;";
        }

        @Override
        protected List<IFile> getSelectedResources() {
            @SuppressWarnings("unchecked")
            List<Object> res = super.getSelectedResources();
            ArrayList<IFile> resources = new ArrayList<IFile>();
            for (Object r : res) {
                if (r instanceof IFile && DartCore.isDartLikeFileName(((IResource) r).getName())) {
                    resources.add((IFile) r);
                }
            }
            return resources;
        }

        @Override
        protected boolean updateSelection(IStructuredSelection selection) {
            files = getSelectedResources();
            return !files.isEmpty();
        }

    }

    /**
     * Holder for formatted source and selection info.
     */
    public static class FormattedSource {
        public int selectionOffset;
        public int selectionLength;
        public String source;
        public int changeOffset;
        public int changeLength;
        public String changeReplacement;
    }

    /**
     * Preference key for showing migrating print margin preferences.
     */
    private final static String PRINT_MARGIN_MIGRATED = "dart-printMargin-migrated";

    /**
     * Preference key for showing print margin ruler.
     */
    public final static String PRINT_MARGIN = "dart-printMargin";

    /**
     * Preference key for print margin ruler color.
     */
    public final static String PRINT_MARGIN_COLOR = AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLOR;

    /**
     * Preference key for print margin ruler column.
     */
    public final static String PRINT_MARGIN_COLUMN = "dart-printMarginColumn";

    private static final String ARGS_MACHINE_FORMAT_FLAG = "-m";
    private static final String ARGS_MAX_LINE_LEN_FLAG = "-l";
    private static final String ARGS_PRESERVE_FLAG = "--preserve";
    private static final String JSON_LENGTH_KEY = "length";
    private static final String JSON_OFFSET_KEY = "offset";
    private static final String JSON_SELECTION_KEY = "selection";
    private static final String JSON_SOURCE_KEY = "source";

    public static void ensurePrintMarginPreferencesMigrated() {
        IPreferenceStore store = EditorsPlugin.getDefault().getPreferenceStore();
        if (!store.getBoolean(PRINT_MARGIN_MIGRATED)) {
            store.setValue(PRINT_MARGIN_COLUMN,
                    store.getString(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLUMN));
            store.setValue(PRINT_MARGIN,
                    store.getString(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN));
            store.setValue(PRINT_MARGIN_MIGRATED, true);
        }
    }

    /**
     * Run the formatter on the given input file.
     * 
     * @param file the source to pass to the formatter
     * @param selection the selection info to pass into the formatter
     * @param monitor the monitor for displaying progress
     * @throws IOException if an exception was thrown during execution
     * @throws CoreException if an exception occurs in file refresh
     */
    public static void format(IFile file, IProgressMonitor monitor) throws IOException, CoreException {

        if (file == null || DartCore.isPackagesResource(file)) {
            return;
        }

        DartEditor editor = getDirtyEditor(file);
        if (editor != null) {
            // Delegate to the editor if possible
            editor.doFormat();
        } else {
            formatFile(file, monitor);
        }
    }

    /**
     * Run the formatter on the given input source.
     * 
     * @param source the source to pass to the formatter
     * @param selection the selection info to pass into the formatter
     * @param monitor the monitor for displaying progress
     * @throws IOException if an exception was thrown during execution
     * @throws CoreException if an exception occurs in file refresh
     * @return the formatted source (or null in case formatting could not be executed)
     */
    public static FormattedSource format(final String source, final Point selection, IProgressMonitor monitor)
            throws IOException, CoreException {
        return DartFmtRunner.format(source, selection, monitor);
    }

    public static String getMaxLineLength() {
        return EditorsPlugin.getDefault().getPreferenceStore().getString(PRINT_MARGIN_COLUMN);
    }

    public static boolean getMaxLineLengthEnabled() {
        return EditorsPlugin.getDefault().getPreferenceStore().getBoolean(PRINT_MARGIN);
    }

    public static boolean isAvailable() {
        return DartSdkManager.getManager().getSdk().getDartFmtExecutable().canExecute();
    }

    public static void setMaxLineLength(String maxLineLength) {
        EditorsPlugin.getDefault().getPreferenceStore().setValue(PRINT_MARGIN_COLUMN, maxLineLength);
    }

    public static void setMaxLineLengthEnabled(boolean enabled) {
        EditorsPlugin.getDefault().getPreferenceStore().setValue(PRINT_MARGIN, enabled);
    }

    private static IEditorPart findEditor(final IWorkbenchPage activePage, final IFile file) {

        final IEditorPart[] editor = new IEditorPart[1];

        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                editor[0] = ResourceUtil.findEditor(activePage, file);
            }
        });

        return editor[0];
    }

    private static void formatFile(IFile file, IProgressMonitor monitor)
            throws UnsupportedEncodingException, CoreException, IOException {

        Reader reader = new InputStreamReader(file.getContents(), file.getCharset());
        String contents = FileUtilities.getContents(reader);

        if (contents != null) {
            FormattedSource result = format(contents, null, monitor);
            if (!contents.equals(result.source)) {
                InputStream stream = new ByteArrayInputStream(result.source.getBytes("UTF-8"));
                file.setContents(stream, IResource.KEEP_HISTORY, monitor);
            }
        }
    }

    private static IWorkbenchPage getActivePage() {

        final IWorkbenchPage[] page = new IWorkbenchPage[1];

        Display.getDefault().syncExec(new Runnable() {

            @Override
            public void run() {
                IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
                if (window != null) {
                    page[0] = window.getActivePage();
                }
            }
        });

        return page[0];
    }

    private static DartEditor getDirtyEditor(IFile file) {

        IWorkbenchPage activePage = getActivePage();
        if (activePage != null) {
            IEditorPart editor = findEditor(activePage, file);
            if (editor instanceof DartEditor) {
                if (editor.isDirty()) {
                    return (DartEditor) editor;
                }
            }
        }
        return null;
    }

}