Skip to content

Commit

Permalink
introduce dirac prompt status style and improve status messages
Browse files Browse the repository at this point in the history
  • Loading branch information
darwin committed Jan 24, 2016
1 parent 4580e74 commit 6a26818
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 68 deletions.
15 changes: 8 additions & 7 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,12 @@ This is a proof that Dirac REPL can execute arbitrary ClojureScript code in the
Dirac integration with your project requires some effort and can be configured in many ways. I will first document
standard configuration with Leiningen. In later sections we will discuss alternative and/or optional setups.

Ingredients you definitely need:
Here are the ingredients you are going to need:

1. the [Dirac Chrome Extension](https://chrome.google.com/webstore/detail/dirac-devtools/kbkdngfljkchidcjpnfcgcokkbhlkogi) installed in your Chrome Canary
1. the [cljs-devtools](https://github.com/binaryage/cljs-devtools), they must be installed in your page with `:dirac` feature installed
1. an nREPL server and it has to be configured to include the Dirac nREPL middleware
1. then you have to launch the Dirac Agent
1. the [cljs-devtools](https://github.com/binaryage/cljs-devtools), the library must be installed in your page with `:dirac` feature enabled
1. an nREPL server (+ Dirac nREPL middleware)
1. the Dirac Agent

I assume you went through the [demo section](#a-demo-time) above, so you roughly know what to expect.

Expand All @@ -178,7 +178,8 @@ As I wrote in the demo section, you probably want to run your Chrome Canary with
--no-first-run \
--user-data-dir=$A_PATH_TO_YOUR_USER_PROFILE_DIRECTORY

Please note that `--remote-debugging-port` should be 9222 by default. But you can reconfigure it in the Dirac Extension `options page` if needed.
Please note that `--remote-debugging-port` should be 9222 by default.
But you can change it in the Dirac Extension `options page` if needed.

Now install [Dirac Chrome Extension](https://chrome.google.com/webstore/detail/dirac-devtools/kbkdngfljkchidcjpnfcgcokkbhlkogi).

Expand All @@ -190,7 +191,7 @@ Please follow [cljs-devtools](https://github.com/binaryage/cljs-devtools) instal
Also make sure that you call `(devtools/enable-feature! :dirac)` before `(devtools/install!)` - Dirac feature is not
enabled by default!

##### Configure and start an nREPL server
##### Start nREPL server

There are many ways how to start an nREPL server. We will use Leiningen's nREPL server here.

Expand All @@ -213,7 +214,7 @@ project dependencies. The configuration snippet could look something like this:
I tend to put this extra config under `:dev` profile in my `project.clj` files
(see an [example here](https://github.com/binaryage/cljs-devtools-sample/blob/master/project.clj)).

##### Start the Dirac Agent
##### Start Dirac Agent

Dirac Agent is a piece of server software which connects to an existing nREPL server and acts as a proxy which
provides nREPL connections to the browser.
Expand Down
26 changes: 20 additions & 6 deletions resources/unpacked/devtools/front_end/console/ConsoleView.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ WebInspector.ConsoleView = function()
var diracHistoryData = this._diracHistorySetting.get();
diracPrompt.setHistoryData(diracHistoryData);

var statusElement = diracPromptElement.createChild("div", "source-code");
var statusElement = diracPromptElement.createChild("div");
statusElement.id = "console-status-dirac";

var statusBannerElement = statusElement.createChild("div", "status-banner");
Expand Down Expand Up @@ -476,16 +476,30 @@ WebInspector.ConsoleView.prototype = {
this._filterStatusMessageElement.style.display = this._hiddenByFilterCount ? "" : "none";
},

updateDiracPromptStatus: function(s) {
this._diracPromptDescriptor.statusContent.textContent = s;
setDiracPromptStatusContent: function(s) {
this._diracPromptDescriptor.statusContent.innerHTML = s;
},

updateDiracPromptBanner: function(s) {
this._diracPromptDescriptor.statusBanner.textContent = s;
setDiracPromptStatusBanner: function(s) {
this._diracPromptDescriptor.statusBanner.innerHTML = s;
},

setDiracPromptStatusStyle: function(style) {
var knownStyles = ["error", "info"];
if (knownStyles.indexOf(style)==-1) {
console.warn("unknown style passed to setDiracPromptStatusStyle:", style);
}
for (var i = 0; i < knownStyles.length; i++) {
var s = knownStyles[i];
this._diracPromptDescriptor.status.classList.toggle("dirac-prompt-status-"+s, style==s);
}
},

setDiracPromptMode: function(mode) {
var knownModes = ["edit", "status"];
if (knownModes.indexOf(mode)==-1) {
console.warn("unknown mode passed to setDiracPromptMode:", mode);
}
for (var i = 0; i < knownModes.length; i++) {
var m = knownModes[i];
this._diracPromptDescriptor.element.classList.toggle("dirac-prompt-mode-"+m, mode==m);
Expand All @@ -503,7 +517,7 @@ WebInspector.ConsoleView.prototype = {
promptDescriptor.codeMirror.setOption("placeholder", label);
},

setDiracReplNS: function(name)
setDiracPromptNS: function(name)
{
this._currentNs = name;
this._refreshNs();
Expand Down
14 changes: 12 additions & 2 deletions resources/unpacked/devtools/front_end/console/dirac-prompt.css
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,22 @@
}

#console-status-dirac .status-content {
font-weight: bold;
font-style: italic;
}

#console-status-dirac.dirac-prompt-status-info .status-content,
#console-status-dirac.dirac-prompt-status-info .status-content a {
color: #00f;
}

#console-status-dirac.dirac-prompt-status-error .status-content,
#console-status-dirac.dirac-prompt-status-error .status-content a {
color: #f00;
}


@media (-webkit-min-device-pixel-ratio: 1.5) {
#console-prompt-dirac::before {
background-image: url(Images/toolbarButtonGlyphs_2x.png);
}
} /* media */
} /* media */
32 changes: 22 additions & 10 deletions src/implant/dirac/implant/console.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@
(warn "Dirac: Unable to obtain console view from DevTools"))
(warn "Dirac: Unable to obtain console panel from DevTools")))

(defn set-repl-ns! [ns-name]
(if-let [console-view (get-console-view)]
(ocall console-view "setDiracReplNS" ns-name)))

(defn announce-job-start! [job-id info]
(group (str "nREPL JOB #" job-id) info)
(if-let [console-view (get-console-view)]
Expand All @@ -23,14 +19,30 @@
(if-let [console-view (get-console-view)]
(ocall console-view "onJobEnded" job-id)))

(defn set-prompt-mode! [mode]
(defn set-prompt-ns! [ns-name]
(if-let [console-view (get-console-view)]
(ocall console-view "setDiracPromptMode" mode)))
(ocall console-view "setDiracPromptNS" ns-name)))

(defn set-prompt-status! [status]
(defn set-prompt-mode! [mode]
(let [mode (name mode)]
(assert (#{"status" "edit"} mode))
(if-let [console-view (get-console-view)]
(ocall console-view "setDiracPromptMode" (name mode)))))

;
(defn set-prompt-status-content! [status]
{:pre [(string? status)]}
(if-let [console-view (get-console-view)]
(ocall console-view "updateDiracPromptStatus" status)))
(ocall console-view "setDiracPromptStatusContent" status)))

(defn set-prompt-banner! [banner]
; banner is an overlay text on the right side of prompt in "status" mode
(defn set-prompt-status-banner! [banner]
{:pre [(string? banner)]}
(if-let [console-view (get-console-view)]
(ocall console-view "updateDiracPromptBanner" banner)))
(ocall console-view "setDiracPromptStatusBanner" banner)))

(defn set-prompt-status-style! [style]
(let [style (name style)]
(assert (#{"error" "info"} style))
(if-let [console-view (get-console-view)]
(ocall console-view "setDiracPromptStatusStyle" style))))
92 changes: 56 additions & 36 deletions src/implant/dirac/implant/intercom.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -13,65 +13,87 @@

(def ^:dynamic *repl-connected* false)
(def ^:dynamic *repl-bootstrapped* false)
(def ^:dynamic *last-prompt-status* "")
(def ^:dynamic *last-prompt-banner* "")
(def ^:dynamic *last-prompt-status-content* "")
(def ^:dynamic *last-prompt-status-style* "")
(def ^:dynamic *last-prompt-status-banner* "")
(def ^:dynamic *last-connection-url* nil)
(def ^:dynamic *last-connect-fn-id* 0)

(defn ^:dynamic missing-cljs-devtools-message []
(str "Dirac DevTools requires runtime support from the page context.\n"
"Please install cljs-devtools into your app => https://github.com/binaryage/dirac#installation."))
"Please <a href=\"https://github.com/binaryage/dirac#installation\">install cljs-devtools</a> into your app."))

(defn ^:dynamic old-cljs-devtools-message [current-api required-api]
(str "Obsolete cljs-devtools version detected. Dirac DevTools requires Dirac API v" required-api
", but your cljs-devtools is v" current-api ".\n"
(str "Obsolete cljs-devtools version detected. Dirac DevTools requires Dirac API v" required-api ", "
"but your cljs-devtools is v" current-api ".\n"
"Please ugrade your cljs-devtools installation in page => https://github.com/binaryage/cljs-devtools."))

(defn ^:dynamic failed-to-retrieve-client-config-message [where]
(str "Failed to retrive client-side Dirac config (" where "). This is an unexpected error."))

(def ^:const EXPONENTIAL_BACKOFF_CEILING (* 60 1000))
(defn ^:dynamice unable-to-bootstrap-message []
(str "Unable to bootstrap CLJS REPL due to a timeout. This is usually a case when server side process "
"raised an exception or crashed. Check your nREPL console."))

(defn exponential-backoff-ceiling [attempt]
(let [time (* (js/Math.pow 2 attempt) 1000)]
(js/Math.min time EXPONENTIAL_BACKOFF_CEILING)))
(defn ^:dynamic bootstrap-error-message []
(str "Unable to bootstrap CLJS REPL due to an error. Check your nREPL console."))

(defn ^:dynamic unable-to-connect-exception-message [url e]
(str "Unable to connect to Dirac Agent at " url ":\n" e))

(defn ^:dynamic will-reconnect-banner-message [remaining-time]
(str "will try reconnect in " remaining-time " seconds"))

(defn ^:dynamic dirac-agent-disconnected-message [tunnel-url]
(str "<b>Dirac Agent is not listening</b> at " tunnel-url " "
"(<a href=\"https://github.com/binaryage/dirac#start-dirac-agent\">need help?</a>)."))

(defn ^:dynamic dirac-agent-connected-message []
(str "Dirac Agent connected. Bootstrapping cljs REPL..."))

; -- prompt status ----------------------------------------------------------------------------------------------------------

(defn repl-ready? []
(and *repl-connected*
*repl-bootstrapped*))

(defn update-repl-mode! []
(if (repl-ready?)
(console/set-prompt-mode! "edit")
(console/set-prompt-mode! "status"))
(console/set-prompt-status! *last-prompt-status*)
(console/set-prompt-banner! *last-prompt-banner*))
(console/set-prompt-mode! :edit)
(console/set-prompt-mode! :status))
(console/set-prompt-status-content! *last-prompt-status-content*)
(console/set-prompt-status-style! *last-prompt-status-style*)
(console/set-prompt-status-banner! *last-prompt-status-banner*))

(defn update-prompt-banner [banner]
(set! *last-prompt-status-banner* banner)
(console/set-prompt-status-banner! *last-prompt-status-banner*))

(defn display-prompt-status [status & [style]]
(let [effective-style (or style :error)]
(update-prompt-banner "")
(set! *last-prompt-status-content* status)
(set! *last-prompt-status-style* effective-style)
(console/set-prompt-status-content! *last-prompt-status-content*)
(console/set-prompt-status-style! effective-style)
(console/set-prompt-mode! :status)))

(defn on-client-change [_key _ref _old new]
(if (nil? new)
(do
(set! *last-prompt-status* (str "Dirac Agent is disconnected. Check your nREPL tunnel at " *last-connection-url* "."))
(set! *last-prompt-banner* "")
(display-prompt-status (dirac-agent-disconnected-message *last-connection-url*))
(set! *repl-bootstrapped* false)
(set! *repl-connected* false))
(do
(set! *last-prompt-status* "Dirac Agent connected. Bootstrapping CLJS REPL...")
(set! *last-prompt-banner* "")
(display-prompt-status (dirac-agent-connected-message) :info)
(set! *repl-connected* true)))
(update-repl-mode!))

; -- message processing -----------------------------------------------------------------------------------------------------

(defn init! []
(add-watch nrepl-tunnel-client/current-client ::client-observer on-client-change))

(defn display-prompt-status [status]
(set! *last-prompt-status* status)
(console/set-prompt-status! *last-prompt-status*)
(console/set-prompt-mode! "status"))

(defn update-prompt-banner [banner]
(set! *last-prompt-banner* banner)
(console/set-prompt-banner! *last-prompt-banner*))

(defn connect-to-weasel-server! [url]
(go
(if-let [client-config (<! (eval/get-dirac-client-config))]
Expand All @@ -83,19 +105,19 @@
(weasel-client/connect! url weasel-options)))
(display-prompt-status (failed-to-retrieve-client-config-message "in connect-to-weasel-server!")))))

(defn on-error-handler [url _client event]
(defn on-error-handler [url _client _event]
(display-prompt-status (str "Unable to connect to Dirac Agent at " url)))

(defn next-connect-fn [attempt]
(set! *last-connect-fn-id* (inc *last-connect-fn-id*))
(let [id *last-connect-fn-id*
step 1000
time (exponential-backoff-ceiling attempt)
prev-time (exponential-backoff-ceiling (dec attempt))
time (utils/exponential-backoff-ceiling attempt)
prev-time (utils/exponential-backoff-ceiling (dec attempt))
time-in-seconds (int (/ prev-time step))]
(go-loop [remaining-time time-in-seconds]
(when (pos? remaining-time)
(update-prompt-banner (str "will try reconnect in " remaining-time " seconds"))
(update-prompt-banner (will-reconnect-banner-message remaining-time))
(<! (timeout step))
(if (= id *last-connect-fn-id*)
(recur (dec remaining-time)))))
Expand All @@ -114,7 +136,7 @@
(info (str "Connecting to a nREPL tunnel at " url ". Tunnel options:") tunnel-options)
(nrepl-tunnel-client/connect! url tunnel-options))
(catch :default e
(display-prompt-status (str "Unable to connect to Dirac Agent at " url ":\n" e))
(display-prompt-status (unable-to-connect-exception-message url e))
(throw e)))))

(defn send-eval-request! [job-id code]
Expand Down Expand Up @@ -163,18 +185,16 @@
(update-repl-mode!)
{:op :bootstrap-done})
"timeout" (do
(display-prompt-status (str "Unable to bootstrap CLJS REPL due to a timeout. "
"This is usually a case when server side process "
"raised an exception or crashed. Check your nREPL console."))
(display-prompt-status (unable-to-bootstrap-message))
{:op :bootstrap-timeout})
(do
(display-prompt-status (str "Unable to bootstrap CLJS REPL due to an error. Check your nREPL console."))
(display-prompt-status (bootstrap-error-message))
{:op :bootstrap-error})))))

(defmethod nrepl-tunnel-client/process-message :bootstrap-info [_client message]
(let [{:keys [server-url ns]} message]
(assert server-url (str "expected :server-url in :bootstrap-info message" message))
(assert ns (str "expected :ns in :bootstrap-info message" message))
(console/set-repl-ns! ns)
(console/set-prompt-ns! ns)
(connect-to-weasel-server! server-url))
nil)
2 changes: 1 addition & 1 deletion src/implant/dirac/implant/nrepl_tunnel_client.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
; we have our own output recoding based on recording driver, sent via :print-output message
out nil ; (eval/present-output id "stdout" out)
err nil ; (eval/present-output id "stderr" err)
ns (console/set-repl-ns! ns)
ns (console/set-prompt-ns! ns)
status (when id
(deliver-response message) ; *** (see pending-messages above)
(console/announce-job-end! id))
Expand Down
15 changes: 9 additions & 6 deletions src/shared/dirac/utils.cljs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
(ns dirac.utils
(:require [goog.string :as gstring]
[goog.string.format]
[chromex.support :refer-macros [oget ocall oapply]]
[chromex.logging :refer-macros [log info warn error group group-end]]
[clojure.string :as string]))
(:require [chromex.support :refer-macros [oget ocall oapply]]
[chromex.logging :refer-macros [log info warn error group group-end]]))

(defn escape-double-quotes [s]
(.replace s #"\"" "\\\""))

(defn remove-nil-values [m]
(into {} (remove (comp nil? second) m)))
(into {} (remove (comp nil? second) m)))

(def ^:const EXPONENTIAL_BACKOFF_CEILING (* 60 1000))

(defn exponential-backoff-ceiling [attempt]
(let [time (* (js/Math.pow 2 attempt) 1000)]
(js/Math.min time EXPONENTIAL_BACKOFF_CEILING)))

0 comments on commit 6a26818

Please sign in to comment.