Package ccw.launching

Source Code of ccw.launching.ClojureLaunchShortcut$IWithREPLView

/*******************************************************************************
* Copyright (c) 2009 Casey Marshall 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:
*    Casey Marshall - initial API and implementation
*******************************************************************************/
package ccw.launching;

import static ccw.launching.LaunchUtils.findRunningLaunchesForProject;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.IDebugModelPresentation;
import org.eclipse.debug.ui.ILaunchShortcut;
import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin;
import org.eclipse.jdt.internal.debug.ui.launcher.LauncherMessages;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.ElementListSelectionDialog;

import ccw.CCWPlugin;
import ccw.ClojureCore;
import ccw.editors.clojure.ClojureEditor;
import ccw.editors.clojure.LoadFileAction;
import ccw.preferences.PreferenceConstants;
import ccw.repl.REPLView;
import ccw.util.ClojureInvoker;
import ccw.util.DisplayUtil;
import ccw.util.Pair;
import clojure.lang.IFn;
import clojure.lang.Keyword;

public class ClojureLaunchShortcut implements ILaunchShortcut, IJavaLaunchConfigurationConstants {
    private static final Map<String, Long> tempLaunchCounters = new HashMap<String, Long>();

    public interface IWithREPLView {
      void run(REPLView replView);
    }
   
    /**
     * Map of console/process names to Pair<promise,IWithREPLView> of their associated nREPL URL.
     * The runnable is code to run after the REPL Client is connected.
     * <p>
     * Insertion of promises is done at launch time. <br/>
     * Delivery of promises is done via Consoles PatternMatch listeners. <br/>
     * Removal of promises is done via disconnect of Consoles Pattern Match listeners
     * (approximation of process terminated signal).
     * </p> 
     */
    public static final ConcurrentMap<String, Pair<Object,IWithREPLView>> launchNameREPLURLPromiseAndWithREPLView = new ConcurrentHashMap<String, Pair<Object,IWithREPLView>>();
   
    private ClojureInvoker leiningenConfiguration = ClojureInvoker.newInvoker(CCWPlugin.getDefault(), "ccw.leiningen.launch");
    private ClojureInvoker launch = ClojureInvoker.newInvoker(CCWPlugin.getDefault(), "ccw.launch");
   
    private static int incTempLaunchCount (String projectName) {
        synchronized (tempLaunchCounters) {
            Long cnt = tempLaunchCounters.get(projectName);
            cnt = cnt == null ? 1 : cnt + 1;
            tempLaunchCounters.put(projectName, cnt);
            return cnt.intValue();
        }
    }
   
    @Override
    public void launch(final IEditorPart editor, final String mode) {
      launch(editor, mode, false);
    }
   
    public void launch(final IEditorPart editor, final String mode, final boolean forceLeinLaunchWhenPossible) {
      if (editor instanceof ClojureEditor) {
        // a new thread ensures we're not in the UI thread
        new Thread(new Runnable() {
        @Override public void run() {
          LoadFileAction.run((ClojureEditor) editor, mode, forceLeinLaunchWhenPossible);
        }}).start();
      }
    }

    @Override
    public void launch(ISelection selection, final String mode) {
      launch(selection, mode, false);
    }
   
    public void launch(ISelection selection, final String mode, final boolean forceLeinLaunchWhenPossible) {
        if (selection instanceof IStructuredSelection) {
            IStructuredSelection strSel = (IStructuredSelection) selection;
            final List<IFile> files = new ArrayList<IFile>();
            IProject proj = null;
            for (Object o : strSel.toList()) {
                IResource r = (IResource) Platform.getAdapterManager().getAdapter(o, IResource.class);
                if (r != null) {
                  if (r.getType() == IResource.FILE) {
                    files.add((IFile) r);
                  }
                    if (proj == null) {
                        proj = r.getProject();
                    }
                }
            }
            if (proj != null) {
              final IProject theProj = proj;
              // a new thread ensures we're not in the UI thread
              new Thread(new Runnable() {
                @Override public void run() {
                  launchProjectCheckRunning(theProj, files.toArray(new IFile[] {}), mode, forceLeinLaunchWhenPossible, null);
                }
              }).start();
            }
        }
    }
   
    /**
     * @param mode if null, then global preferences run mode will be selected
     */
    public void launchProject(final IProject project, final String runMode, final boolean forceLeinLaunchWhenPossible, final IWithREPLView runOnceREPLAvailable) {
      // a new thread ensures we're not in the UI thread
      new Thread(new Runnable() {
        @Override public void run() {
          launchProjectCheckRunning(project, new IFile[] {}, getRunMode(runMode), forceLeinLaunchWhenPossible, runOnceREPLAvailable);
        }
      }).start();
    }
   
    private static String getDefaultRunMode() {
    boolean defaultIsDebugMode = getPreferences().getBoolean(PreferenceConstants.CCW_GENERAL_LAUNCH_REPLS_IN_DEBUG_MODE);
    return defaultIsDebugMode ? ILaunchManager.DEBUG_MODE : ILaunchManager.RUN_MODE;
    }
   
    /**
     * Launches a project, first verifying if a running configuration for the
     * project is live. If so, first asks the user for confirmation that he
     * wants to start a new launch configuration for the project.
     * @param project
     * @param filesToLaunch
     * @param mode
     */
    protected void launchProjectCheckRunning(IProject project, IFile[] filesToLaunch, String mode, boolean forceLeinLaunchWhenPossible, IWithREPLView runOnceREPLAvailable) {
      assert mode != null;
     
      String projectName = project.getName();
      List<ILaunch> running = findRunningLaunchesForProject(projectName);
      System.out.println("found " + running.size() + " running launches");
     
      if (running.size() == 0
          ||
              userConfirmsNewLaunch(project, running.size())) {
        launchProject(project, filesToLaunch, mode, forceLeinLaunchWhenPossible, runOnceREPLAvailable);
      } else {
      IViewPart replView = CCWPlugin.getDefault().getProjectREPL(project);
      if (replView != null) {
        replView.getViewSite().getPage().activate(replView);
      } else {
        System.out.println("Should not be there: because in the normal course of things, a Launch does not survive its REPLView");
      }
      }
    }   
   
    private boolean userConfirmsNewLaunch(final IProject project, final int nb) {
      final boolean[] ret = new boolean[1];
      final String title = "Clojure Application Launcher";
      final String msg = (nb==1?"A":nb) + " REPL" + (nb==1?" is":"s are")
          + " already running for this project. Changes you made can "
          + "be evaluated in an existing REPL (see Clojure menu). "
          + "\n\nAre you sure you want to start up another REPL for this project?\n"
          + "(Cancel will open existing REPL)";
        DisplayUtil.syncExec(new Runnable() {
            public void run() {
              Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
                ret[0= MessageDialog.openConfirm(shell, title, msg);
            }
        });
      return ret[0];
    }
   
    private boolean useLeiningenLaunchConfiguration(IProject project,
        boolean forceLeinLaunchWhenPossible) throws CoreException {
      return project.hasNature(CCWPlugin.LEININGEN_NATURE_ID) &&
          (forceLeinLaunchWhenPossible
           ||
           getPreferences().getBoolean(PreferenceConstants.CCW_GENERAL_USE_LEININGEN_LAUNCHER));
    }
   
    protected void launchProject(IProject project, IFile[] filesToLaunch, String mode, boolean forceLeinLaunchWhenPossible, IWithREPLView runOnceREPLAvailable) {
      mode = getRunMode(mode);
        try {
          ILaunchConfiguration config;
      if (useLeiningenLaunchConfiguration(project, forceLeinLaunchWhenPossible)) {
            config = createLeiningenLaunchConfiguration(project, mode.equals(ILaunchManager.DEBUG_MODE));
      } else {
        config = findLaunchConfiguration(project);
        if (config == null) {
            System.out.println("creating basic configuration (no lein configuration)");
                config = createConfiguration(project, null);
          }
          }
           
            if (config != null) {
              final String name = config.getName() + " #" + incTempLaunchCount(project.getName());
              launchNameREPLURLPromiseAndWithREPLView.put(name, new Pair<Object,IWithREPLView>(promise(), runOnceREPLAvailable));
        ILaunchConfigurationWorkingCopy runnableConfiguration =
                  config.copy(name);
              try {
              LaunchUtils.setFilesToLaunchString(runnableConfiguration, Arrays.asList(filesToLaunch));
                if (filesToLaunch.length > 0) {
                  runnableConfiguration.setAttribute(LaunchUtils.ATTR_NS_TO_START_IN, ClojureCore.findMaybeLibNamespace(filesToLaunch[0]));
                }
                runnableConfiguration.launch(mode, null);
                return;
              } finally {
                runnableConfiguration.delete();
              }
            }
        } catch (CoreException e) {
            throw new RuntimeException(e);
        }
    }
   
    private Object promise() {
      IFn promise = clojure.java.api.Clojure.var("clojure.core", "promise");
      return promise.invoke();
    }

    private ILaunchConfiguration createLeiningenLaunchConfiguration(IProject project, boolean createInDebugMode) {
      String command =
          // Adding ccw/ccw.server for enabling ccw custom code completion, etc.
          " update-in :dependencies conj \"[ccw/ccw.server \\\"0.1.1\\\"]\" "
          + "-- update-in :injections conj \"(require 'ccw.debug.serverrepl)\" "

          // Starting repl :headless ; removing :main attribute because
          // it is causing problems: "leiningen repl :headless" defaults
          // to automatically requiring the namespace symbol found in the
          // [:main] project.clj key if there's no namespace symbol defined in the
          // the [:repl-options :init-ns] project.clj key.
          + "-- update-in : dissoc :main " // here ':' refers to project.clj's root
          + "-- repl :headless ";
     
        if (createInDebugMode) {
          command = " update-in :jvm-opts concat \"[\\\"-Xdebug\\\" \\\"-Xrunjdwp:transport=dt_socket,server=y,suspend=n\\\"]\" -- "
                + command;
        }
       
      clojure.lang.IPersistentMap configMap =
        (clojure.lang.IPersistentMap)
        leiningenConfiguration._("lein-launch-configuration",
          project, 
          command);
    configMap = configMap.assoc(Keyword.intern("type-id"), Keyword.intern("ccw"));
    configMap = configMap.assoc(Keyword.intern("name"), project.getName() + " Leiningen VM");
    configMap = configMap.assoc(LaunchUtils.ATTR_CLOJURE_START_REPL, true);
    configMap = configMap.assoc(LaunchUtils.ATTR_LEININGEN_CONFIGURATION, true);
    configMap = configMap.assoc(Keyword.intern("private"), true);
   
    // Add LEIN_REPL_ACK_PORT as an additional environment variable
    final Map<String, String> additionalEnvVariables = new HashMap<String, String>();
    additionalEnvVariables.put("LEIN_REPL_ACK_PORT", Integer.toString(CCWPlugin.getDefault().getREPLServerPort(), 10));
    configMap = configMap.assoc(Keyword.intern("environment-variables"), additionalEnvVariables);
    configMap = configMap.assoc(Keyword.intern("append-environment-variables"), true);
   
    return (ILaunchConfiguration)
        launch._("launch-configuration", configMap);
    }

  private ILaunchConfiguration findLaunchConfiguration(IProject project) throws CoreException {
        ILaunchManager lm = DebugPlugin.getDefault().getLaunchManager();
        ILaunchConfigurationType type =
            lm.getLaunchConfigurationType(LaunchUtils.LAUNCH_CONFIG_ID);
       
        List<ILaunchConfiguration> candidateConfigs = Collections.EMPTY_LIST;
       
        //boolean isLeinProject = project.hasNature(CCWPlugin.LEININGEN_NATURE_ID);
       
        try {
            ILaunchConfiguration[] configs = DebugPlugin.getDefault().getLaunchManager().getLaunchConfigurations(type);
            candidateConfigs = new ArrayList<ILaunchConfiguration>(configs.length);
            for (ILaunchConfiguration config : configs) {
                if (config.getAttribute(ATTR_MAIN_TYPE_NAME, "").startsWith("clojure.")
                    && config.getAttribute(ATTR_PROJECT_NAME, "").equals(project.getName())
                    && !config.getAttribute(ILaunchManager.ATTR_PRIVATE, false)) {
                  if (   true
                      //(isLeinProject && ClojureLaunchDelegate.isLeiningenConfiguration(config))
                      //||
                      //(!isLeinProject && !ClojureLaunchDelegate.isLeiningenConfiguration(config))
                      ) {
                    candidateConfigs.add(config);
                  }
                }
            }
        }
        catch (CoreException e) {
            throw new RuntimeException(e);
        }
        int candidateCount = candidateConfigs.size();
        if (candidateCount == 1) {
            return (ILaunchConfiguration) candidateConfigs.get(0);
        } else if (candidateCount > 1) {
            return chooseConfiguration(candidateConfigs);
        }
        return null;
    }

    protected ILaunchConfiguration createConfiguration(IProject project, IFile[] files) {
        if (files == null) files = new IFile[] {};
        try {
            ILaunchManager lm = DebugPlugin.getDefault().getLaunchManager();
            ILaunchConfigurationType type =
                lm.getLaunchConfigurationType(LaunchUtils.LAUNCH_CONFIG_ID);
           
            String basename = project.getName() + " REPL";
            if (files.length == 1) {
                basename += " [" + files[0].getName() + "]";
            }
           
            ILaunchConfigurationWorkingCopy wc = type.newInstance(
                    null, DebugPlugin.getDefault().getLaunchManager().
                    generateLaunchConfigurationName(basename));
           
            LaunchUtils.setFilesToLaunchString(wc, Arrays.asList(files));
           
            wc.setAttribute(ATTR_PROGRAM_ARGUMENTS, "");
           
            wc.setAttribute(ATTR_MAIN_TYPE_NAME, LaunchUtils.CLOJURE_MAIN);
           
            wc.setAttribute(ATTR_PROJECT_NAME, project.getName());
           
            wc.setMappedResources(new IResource[] {project});
           
            return wc.doSave();
        }
        catch (CoreException ce) {
            throw new RuntimeException(ce);
        }
    }
   
    protected ILaunchConfiguration chooseConfiguration(final List<ILaunchConfiguration> configList) {
      final AtomicReference<ILaunchConfiguration> ret = new AtomicReference<ILaunchConfiguration>();
      DisplayUtil.syncExec(new Runnable() {
      @Override
      public void run() {
            IDebugModelPresentation labelProvider = null;
          try {
            labelProvider = DebugUITools.newDebugModelPresentation();
              ElementListSelectionDialog dialog= new ElementListSelectionDialog(JDIDebugUIPlugin.getActiveWorkbenchShell(), labelProvider);
              dialog.setElements(configList.toArray());
              dialog.setTitle("Choose a Clojure launch configuration")
              dialog.setMessage(LauncherMessages.JavaLaunchShortcut_2);
              dialog.setMultipleSelection(false);
              dialog.setAllowDuplicates(true);
              int result = dialog.open();
              if (result == Window.OK) {
                ret.set((ILaunchConfiguration) dialog.getFirstResult());
              }
          } finally {
            if (labelProvider != null) {
              labelProvider.dispose();
            }
          }
      }
    });
      return ret.get();
    }

    /**
     * @return the run mode, getting the default run mode if <code>mode</code>
     *         is null.
     */
  private String getRunMode(String mode) {
    return (mode!=null) ? mode : getDefaultRunMode();
  }

    private static IPreferenceStore getPreferences() {
      return  CCWPlugin.getDefault().getCombinedPreferenceStore();
    }
}
TOP

Related Classes of ccw.launching.ClojureLaunchShortcut$IWithREPLView

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.