Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

feat(launch): Moare launcher improvements:

- Merge Alt+L L and Ctrl+Alt S
- contextual editor run/debug as works as [Ctrl+Alt S when no active REPL found]
- Make debugger work
- print stacktrace if loading a file fails, and do not stop the process of starting the REPL
  • Loading branch information...
commit 80e2e4f07a2bc792d8fa31cab8107fc35aa81053 1 parent 005127f
@laurentpetit authored
View
2  ccw.core/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+encoding//src/java/ccw/launching/ClojureLaunchShortcut.java=UTF-8
View
16 ccw.core/plugin.xml
@@ -35,6 +35,22 @@
</or>
</enablement>
</consolePatternMatchListener>
+ <consolePatternMatchListener
+ id="ccw.editors.clojure.debugProcessHyperlink"
+ regex="Listening for transport dt_socket at address: \d+">
+ <class class="ccw.util.GenericExecutableExtension">
+ <parameter
+ name="factory"
+ value="ccw.editors.clojure.debug-hyperlink/factory">
+ </parameter>
+ </class>
+ <enablement>
+ <or>
+ <test property="org.eclipse.ui.console.consoleTypeTest" value="javaStackTraceConsole"/>
+ <test property="org.eclipse.debug.ui.processTypeTest" value="java"/>
+ </or>
+ </enablement>
+ </consolePatternMatchListener>
</extension>
View
12 ccw.core/src/clj/ccw/eclipse.clj
@@ -16,6 +16,7 @@
FileLocator
IAdaptable]
[org.eclipse.jdt.core IJavaProject]
+ [org.eclipse.debug.core ILaunchConfiguration ILaunch]
[org.eclipse.ui.handlers HandlerUtil]
[org.eclipse.ui IEditorPart
PlatformUI
@@ -26,7 +27,8 @@
[org.eclipse.ui.actions WorkspaceModifyDelegatingOperation]
[java.io File IOException]
[ccw CCWPlugin]
- [ccw.util PlatformUtil DisplayUtil]))
+ [ccw.util PlatformUtil DisplayUtil]
+ [ccw.launching LaunchUtils]))
(defn adapter
"Invokes Eclipse Platform machinery to try by all means to adapt object to
@@ -115,7 +117,13 @@
(project [this] (project (resource this)))
ExecutionEvent
- (project [this] (project (resource this))))
+ (project [this] (project (resource this)))
+
+ ILaunchConfiguration
+ (project [^ILaunchConfiguration l] (LaunchUtils/getProject l))
+
+ ILaunch
+ (project [^ILaunch l] (LaunchUtils/getProject l)))
(extend-protocol IResourceCoercion
nil
View
59 ccw.core/src/clj/ccw/editors/clojure/debug_hyperlink.clj
@@ -0,0 +1,59 @@
+(ns ccw.editors.clojure.debug-hyperlink
+ "Detect and create hyperlinks in Consoles when a pattern describing a JVM
+ listening to some debug port is found.
+ The first time the pattern is encountered for the currently attached console,
+ will automatically start a remote process for connecting to this port"
+ (:require [ccw.string :as s]
+ [ccw.launch :as launch]
+ [ccw.eclipse :as e])
+ (:use [clojure.test])
+ (:import [org.eclipse.ui.console PatternMatchEvent TextConsole]))
+
+(def ^:private debug-port-pattern #"\d+")
+
+(defn remote-connect
+ "Launch a remote java debugging connection on port for project."
+ [project port]
+ (launch/launch :debug
+ {:type-id :remote-java
+ :private true
+ :name (str (and project (e/project-name project)) " VM")
+ :launch-in-background false
+ :java/project-name (and project (e/project-name project))
+ :java/vm-connector (launch/vm-connector :socket-attach-vm-connector)
+ :java/connect-map {"port" port
+ "hostname" "localhost"}}))
+
+(defn match-found
+ "state contains the console instance, and a set of seen (if many) debug port patterns.
+ This allows to e.g. only process opening remote connections once, even if
+ match-found is called several times on the same text."
+ [^PatternMatchEvent event ^TextConsole {:keys [console debug-ports] :as state}]
+ (let [offset (.getOffset event)
+ length (.getLength event)
+ document (.getDocument console)
+ s (.get document offset length)
+ [debug-port] (re-seq debug-port-pattern s)
+ remote-connect #(remote-connect
+ (some-> console .getProcess .getLaunch e/project)
+ debug-port)
+ hyperlink (reify org.eclipse.ui.console.IHyperlink
+ (linkActivated [this] (remote-connect))
+ (linkExited [this])
+ (linkEntered [this]))]
+ (.addHyperlink console hyperlink offset length)
+ (when-not (debug-ports debug-port)
+ (e/ui (remote-connect)))
+ (update-in state [:debug-ports] conj debug-port)))
+
+(defn make []
+ (let [state (atom nil)]
+ (reify org.eclipse.ui.console.IPatternMatchListenerDelegate
+ (connect [this console] (reset! state {:console console
+ :debug-ports #{}}))
+ (disconnect [this] (reset! state nil))
+ (matchFound [this event]
+ (swap! state (partial match-found event))
+ nil))))
+
+(defn factory "plugin.xml hook" [ _ ] (make))
View
4 ccw.core/src/clj/ccw/editors/clojure/nrepl_hyperlink.clj
@@ -23,8 +23,8 @@
(linkExited [this])
(linkEntered [this]))]
(.addHyperlink console hyperlink offset length)
- (when-not (nrepl-urls url)
- (e/ui (open-repl-view)))
+ #_(when-not (nrepl-urls url)
+ (e/ui (open-repl-view)))
(update-in state [:nrepl-urls] conj url)))
(defn make []
View
13 ccw.core/src/clj/ccw/launch.clj
@@ -64,7 +64,14 @@
{:ccw
"ccw.launching.clojure"
:java
- IJavaLaunchConfigurationConstants/ID_JAVA_APPLICATION})
+ IJavaLaunchConfigurationConstants/ID_JAVA_APPLICATION
+ :remote-java
+ IJavaLaunchConfigurationConstants/ID_REMOTE_JAVA_APPLICATION})
+
+(def vm-connector
+ "Identifiers for socket attaching and socket listening connectors"
+ {:socket-attach-vm-connector IJavaLaunchConfigurationConstants/ID_SOCKET_ATTACH_VM_CONNECTOR
+ :socket-listen-vm-connector IJavaLaunchConfigurationConstants/ID_SOCKET_LISTEN_VM_CONNECTOR})
(def attrs-map
"Launch configuration pre-existing attributes."
@@ -83,6 +90,10 @@
:java/classpath IJavaLaunchConfigurationConstants/ATTR_CLASSPATH
:java/default-classpath IJavaLaunchConfigurationConstants/ATTR_DEFAULT_CLASSPATH
:java/project-name IJavaLaunchConfigurationConstants/ATTR_PROJECT_NAME
+
+ ;; specific for remote java
+ :java/vm-connector IJavaLaunchConfigurationConstants/ATTR_VM_CONNECTOR
+ :java/connect-map IJavaLaunchConfigurationConstants/ATTR_CONNECT_MAP
})
#_(defn default-jre "Default JRE installed in workbench" [] (JavaRuntime/getDefaultVMInstall))
View
8 ccw.core/src/clj/ccw/leiningen/launch.clj
@@ -23,13 +23,9 @@
:name launch/default-jre-container-name}]
:java/default-classpath false
:java/vm-arguments (str " -Dfile.encoding=UTF-8"
- " -Dmaven.wagon.http.ssl.easy=false"
- ;; TODO see if we can safely remove this argument
- ;; " -Dleiningen.original.pwd=\"/Users/laurentpetit/tmp\""
- )
+ " -Dmaven.wagon.http.ssl.easy=false")
:java/main-type-name "clojure.main"
- :java/program-arguments (str "-m ccw.leiningen.main " command)
- }))
+ :java/program-arguments (str "-m ccw.leiningen.main " command)}))
(defn lein
"project can be nil"
View
21 ccw.core/src/java/ccw/ClojureCore.java
@@ -507,6 +507,27 @@ public static String findMaybeLibNamespace(IFile file) {
}
return null;
}
+
+ /**
+ * @return starting with a leading slash "/", the root classpath relative
+ * path of this file
+ */
+ public static String getAsRootClasspathRelativePath(IFile file) {
+ try {
+ IJavaProject jProject = JavaCore.create(file.getProject());
+ IPackageFragmentRoot[] froots = jProject.getAllPackageFragmentRoots();
+ for (IPackageFragmentRoot froot: froots) {
+ if (froot.getPath().isPrefixOf(file.getFullPath())) {
+ String ret = "/" + file.getFullPath().makeRelativeTo(froot.getPath()).toString();
+ return ret;
+ }
+ }
+ } catch (JavaModelException e) {
+ CCWPlugin.logError("unable to determine the fragment root of the file " + file, e);
+ }
+ return null;
+ }
+
/**
* @see <code>findMaybeLibNamespace(IFile file)</code>
*/
View
2  ccw.core/src/java/ccw/editors/clojure/ClojureEditor.java
@@ -283,7 +283,7 @@ protected void createActions() {
action = new Action() {
@Override
public void run() {
- new ClojureLaunchShortcut().launch(ClojureEditor.this, ILaunchManager.RUN_MODE);
+ new ClojureLaunchShortcut().launch(ClojureEditor.this, ILaunchManager.DEBUG_MODE);
};
};
action.setActionDefinitionId(IClojureEditorActionDefinitionIds.LAUNCH_REPL);
View
24 ccw.core/src/java/ccw/editors/clojure/LoadFileAction.java
@@ -33,7 +33,7 @@
public class LoadFileAction extends Action {
- private ClojureInvoker nreplHelpers = ClojureInvoker.newInvoker(CCWPlugin.getDefault(), "clojure.tools.nrepl.helpers");
+ private static ClojureInvoker nreplHelpers = ClojureInvoker.newInvoker(CCWPlugin.getDefault(), "clojure.tools.nrepl.helpers");
public final static String ID = "LoadFileAction"; //$NON-NLS-1$
@@ -47,9 +47,17 @@ public LoadFileAction(ClojureEditor editor) {
}
public void run() {
+ run(editor, ILaunchManager.DEBUG_MODE);
+ }
+
+ /**
+ * @param editor the clojure editor
+ * @param mode the "run" or "debug" launch mode
+ */
+ public static void run(final ClojureEditor editor, final String mode) {
final IFile editorFile = (IFile) editor.getEditorInput().getAdapter(IFile.class);
- final String filePath = computeFilePath(editorFile);
+ final String filePath = computeFilePath(editor, editorFile);
final String fileName = FilenameUtils.getName(filePath);
if (filePath == null) {
@@ -57,11 +65,12 @@ public void run() {
return;
}
- final String sourcePath = computeSourcePath(editorFile, filePath);
+ final String sourcePath = computeSourcePath(editor, editorFile, filePath);
final REPLView repl = REPLView.activeREPL.get();
if (repl != null && !repl.isDisposed()) {
evaluateFileText(repl, editor.getDocument().get(), filePath, sourcePath, fileName);
+ // FIXME: normal that we switch in namespace if start (if no repl), and not if not start ... ?
} else if (editorFile != null) {
CCWPlugin.getTracer().trace(TraceOptions.LAUNCHER, "No active REPL found (",
(repl == null) ? "active repl is null" : "active repl is disposed",
@@ -69,7 +78,7 @@ public void run() {
new Thread(new Runnable() {
public void run() {
final IProject project = editorFile.getProject();
- new ClojureLaunchShortcut().launchProject(project, ILaunchManager.RUN_MODE);
+ new ClojureLaunchShortcut().launchProject(project, mode);
DisplayUtil.asyncExec(new Runnable() {
public void run() {
REPLView repl = CCWPlugin.getDefault().getProjectREPL(project);
@@ -88,7 +97,7 @@ public void run() {
}
}
- private String computeFilePath(final IFile editorFile) {
+ private static String computeFilePath(final ClojureEditor editor, final IFile editorFile) {
String filePath;
if (editorFile != null) {
filePath = editorFile.getLocation().toOSString();
@@ -104,7 +113,8 @@ private String computeFilePath(final IFile editorFile) {
return filePath;
}
- private String computeSourcePath(final IFile editorFile, String filePath) {
+ // FIXME similar code in ClojureCore for finding classpath root related path
+ private static String computeSourcePath(final ClojureEditor editor, final IFile editorFile, String filePath) {
String sourcePath;
if (editorFile != null) {
ClojureProject proj = ClojureCore.getClojureProject(editor.getProject());
@@ -125,7 +135,7 @@ private String computeSourcePath(final IFile editorFile, String filePath) {
return sourcePath;
}
- private void evaluateFileText(REPLView repl, String text, String filePath, String sourcePath, String fileName) {
+ private static void evaluateFileText(REPLView repl, String text, String filePath, String sourcePath, String fileName) {
try {
if (repl.getAvailableOperations().contains("load-file")) {
repl.getConnection().sendSession(repl.getSessionId(),
View
37 ccw.core/src/java/ccw/launching/ClojureLaunchDelegate.java
@@ -19,6 +19,7 @@
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
+import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
@@ -107,7 +108,7 @@ protected IStatus run(final IProgressMonitor monitor) {
monitor.beginTask("Waiting for new REPL process to be ready...", IProgressMonitor.UNKNOWN);
- final Number port = (Number)Connection.find("clojure.tools.nrepl.ack", "wait-for-ack").invoke(60000);
+ final Number port = (Number)Connection.find("clojure.tools.nrepl.ack", "wait-for-ack").invoke(300000);
cancelOrAck.countDown();
if (monitor.isCanceled()) {
@@ -238,12 +239,21 @@ public String getVMArguments(ILaunchConfiguration configuration) throws CoreExce
@Override
public String getProgramArguments(ILaunchConfiguration configuration) throws CoreException {
+ String superProgramArguments = super.getProgramArguments(configuration);
if (isLeiningenConfiguration(configuration)) {
- // Leiningen configuration does not need (yet) program arguments tweaks
- return super.getProgramArguments(configuration);
+ List<IFile> filesToLaunch = LaunchUtils.getFilesToLaunchList(configuration);
+ if (filesToLaunch.size() > 0) {
+ int headlessReplOffset = superProgramArguments.indexOf("repl :headless");
+ String arguments = superProgramArguments.substring(0, headlessReplOffset) +
+ " " + createFileLoadInjections(filesToLaunch) +
+ " -- " + superProgramArguments.substring(headlessReplOffset);
+ return arguments;
+ } else {
+ return superProgramArguments;
+ }
}
- String userProgramArguments = super.getProgramArguments(configuration);
+ String userProgramArguments = superProgramArguments;
if (isLaunchREPL(configuration)) {
String filesToLaunchArguments = LaunchUtils.getFilesToLaunchAsCommandLineList(configuration, false);
@@ -273,13 +283,28 @@ public String getProgramArguments(ILaunchConfiguration configuration) throws Cor
}
}
+ private String createFileLoadInjections(List<IFile> filesToLaunch) {
+
+ assert filesToLaunch.size() > 0;
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(" update-in :injections conj \"");
+ for (IFile file: filesToLaunch) {
+ // We use load so that the right info are compiled for use with breakpoints in a debugger
+ String path = ClojureCore.getAsRootClasspathRelativePath(file);
+ int offset = path.lastIndexOf(".clj");
+ sb.append("(try (load \\\"" + path.substring(0, offset) + "\\\") (catch Exception e (.printStackTrace e)))");
+ }
+ sb.append("\" ");
+ return sb.toString();
+ }
+
private static boolean isLaunchREPL(ILaunchConfiguration configuration) throws CoreException {
return configuration.getAttribute(LaunchUtils.ATTR_CLOJURE_START_REPL, true);
}
public static boolean isLeiningenConfiguration(ILaunchConfiguration configuration) throws CoreException {
- return false;
- //return configuration.getAttribute(LaunchUtils.ATTR_LEININGEN_CONFIGURATION, false);
+ return configuration.getAttribute(LaunchUtils.ATTR_LEININGEN_CONFIGURATION, false);
}
public static boolean isAutoReloadEnabled (ILaunch launch) {
View
53 ccw.core/src/java/ccw/launching/ClojureLaunchShortcut.java
@@ -42,15 +42,15 @@
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Shell;
-import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.ElementListSelectionDialog;
-import org.eclipse.ui.part.FileEditorInput;
import ccw.CCWPlugin;
import ccw.ClojureCore;
+import ccw.editors.clojure.ClojureEditor;
+import ccw.editors.clojure.LoadFileAction;
import ccw.util.ClojureInvoker;
import ccw.util.DisplayUtil;
import clojure.lang.Keyword;
@@ -72,12 +72,9 @@ private static int incTempLaunchCount (String projectName) {
@Override
public void launch(IEditorPart editor, String mode) {
- IEditorInput input = editor.getEditorInput();
- if (input instanceof FileEditorInput) {
- FileEditorInput fei = (FileEditorInput) input;
- launchProjectCheckRunning(fei.getFile().getProject(), new IFile[] { fei
- .getFile() }, mode);
- }
+ if (editor instanceof ClojureEditor) {
+ LoadFileAction.run((ClojureEditor) editor, mode);
+ }
}
@Override
@@ -152,17 +149,17 @@ public void run() {
}
private boolean useLeiningenLaunchConfiguration(IProject project) throws CoreException {
- return false;
- //return project.hasNature(CCWPlugin.LEININGEN_NATURE_ID);
+ return project.hasNature(CCWPlugin.LEININGEN_NATURE_ID);
}
protected void launchProject(IProject project, IFile[] filesToLaunch, String mode) {
try {
- ILaunchConfiguration config = findLaunchConfiguration(project);
- if (config == null) {
- if (useLeiningenLaunchConfiguration(project)) {
- config = createLeiningenLaunchConfiguration(project);
- } else {
+ ILaunchConfiguration config;
+ if (useLeiningenLaunchConfiguration(project)) {
+ config = createLeiningenLaunchConfiguration(project, (mode!= null && 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);
}
@@ -172,11 +169,7 @@ protected void launchProject(IProject project, IFile[] filesToLaunch, String mod
ILaunchConfigurationWorkingCopy runnableConfiguration =
config.copy(config.getName() + " #" + incTempLaunchCount(project.getName()));
try {
- if (useLeiningenLaunchConfiguration(project)) {
- // Nothing special
- } else {
- LaunchUtils.setFilesToLaunchString(runnableConfiguration, Arrays.asList(filesToLaunch));
- }
+ LaunchUtils.setFilesToLaunchString(runnableConfiguration, Arrays.asList(filesToLaunch));
if (filesToLaunch.length > 0) {
runnableConfiguration.setAttribute(LaunchUtils.ATTR_NS_TO_START_IN, ClojureCore.findMaybeLibNamespace(filesToLaunch[0]));
}
@@ -191,22 +184,26 @@ protected void launchProject(IProject project, IFile[] filesToLaunch, String mod
}
}
- private ILaunchConfiguration createLeiningenLaunchConfiguration(IProject project) {
- clojure.lang.IPersistentMap configMap =
+ private ILaunchConfiguration createLeiningenLaunchConfiguration(IProject project, boolean createInDebugMode) {
+ String command = " update-in :dependencies conj \"[ccw/ccw.server \\\"0.1.0\\\"]\" -- update-in :injections conj \"(require 'ccw.debug.serverrepl)\" -- 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,
- "update-in :dependencies conj \"[ccw/ccw.server \\\"0.1.0\\\"]\" -- update-in :injections conj \"(require 'ccw.debug.serverrepl)\" -- repl :headless");
+ command);
configMap = configMap.assoc(Keyword.intern("type-id"), Keyword.intern("ccw"));
- configMap = configMap.assoc(Keyword.intern("name"), project.getName() + " Leiningen");
+ 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"), false);
- configMap = configMap.assoc(Keyword.intern("launch-in-background"), false);
+ configMap = configMap.assoc(Keyword.intern("private"), true);
return (ILaunchConfiguration)
- launch._("launch-configuration",
- configMap);
+ launch._("launch-configuration", configMap);
}
private ILaunchConfiguration findLaunchConfiguration(IProject project) throws CoreException {
Please sign in to comment.
Something went wrong with that request. Please try again.