Skip to content

Commit

Permalink
feat(launch): Moare launcher improvements:
Browse files Browse the repository at this point in the history
- 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
laurentpetit committed Feb 27, 2014
1 parent 005127f commit 80e2e4f
Show file tree
Hide file tree
Showing 12 changed files with 198 additions and 53 deletions.
2 changes: 2 additions & 0 deletions ccw.core/.settings/org.eclipse.core.resources.prefs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding//src/java/ccw/launching/ClojureLaunchShortcut.java=UTF-8
16 changes: 16 additions & 0 deletions ccw.core/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>


Expand Down
12 changes: 10 additions & 2 deletions ccw.core/src/clj/ccw/eclipse.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
59 changes: 59 additions & 0 deletions ccw.core/src/clj/ccw/editors/clojure/debug_hyperlink.clj
Original file line number Diff line number Diff line change
@@ -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))
4 changes: 2 additions & 2 deletions ccw.core/src/clj/ccw/editors/clojure/nrepl_hyperlink.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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 []
Expand Down
13 changes: 12 additions & 1 deletion ccw.core/src/clj/ccw/launch.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand All @@ -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))
Expand Down
8 changes: 2 additions & 6 deletions ccw.core/src/clj/ccw/leiningen/launch.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
21 changes: 21 additions & 0 deletions ccw.core/src/java/ccw/ClojureCore.java
Original file line number Diff line number Diff line change
Expand Up @@ -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>
*/
Expand Down
2 changes: 1 addition & 1 deletion ccw.core/src/java/ccw/editors/clojure/ClojureEditor.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
24 changes: 17 additions & 7 deletions ccw.core/src/java/ccw/editors/clojure/LoadFileAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -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$

Expand All @@ -47,29 +47,38 @@ 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) {
editor.setStatusLineErrorMessage("Unable to create a Clojure Application for this editor's content");
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",
"), so launching a new one");
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);
Expand All @@ -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();
Expand All @@ -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());
Expand All @@ -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(),
Expand Down
37 changes: 31 additions & 6 deletions ccw.core/src/java/ccw/launching/ClojureLaunchDelegate.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -238,12 +239,21 @@ public String[] getEnvironment(ILaunchConfiguration configuration)

@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);
Expand Down Expand Up @@ -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) {
Expand Down
Loading

0 comments on commit 80e2e4f

Please sign in to comment.