diff --git a/README.md b/README.md index 00fdb57..878b896 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,20 @@ By default, \*.cljc files are identified as Clojure files. Thus when you eval, i Clojure file. If you'd like to eval as a ClojureScript file, run the command `Editor: Set current editor syntax`, select `ClojureScript` and then eval. +## Clojure(script) error messages +Currently this plugin supports an experimental feature that allows to change the default exception behavior (showing the complete stacktrace) to an expandable version of the same. Some screenshots of the new behavior can be found [here](https://github.com/LightTable/Clojure/issues/77). +To use the new behavior go to ```User behaviors``` and add the following lines : + +```Clojure +; for Clojure +[:editor.clj :-lt.plugins.clojure/clj-exception] +[:editor.clj :lt.plugins.clojure.collapsible-exception/clj-expandable-exception] + +; for Clojurescript +[:editor.cljs :-lt.plugins.clojure/cljs-exception] +[:editor.cljs :lt.plugins.clojure.collapsible-exception/cljs-expandable-exception] +``` + ## First ClojureScript Repl Welcome first time ClojureScript users! Please see [David Nolen's tutorial](https://github.com/swannodette/lt-cljs-tutorial) to get familiar with ClojureScript and comfortable with LightTable's repl. Note while doing that tutorial you were in a namespace. A namespace is necessary for a LightTable repl. Once you have finished the tutorial, create your own ClojureScript project with `lein new mies my-project` and eval there. If you want to add dependencies to your project, read the [below section](#clojurescript-eval) as that requires a different type of LightTable connection. diff --git a/clojure.behaviors b/clojure.behaviors index d5c411c..4fd32e4 100644 --- a/clojure.behaviors +++ b/clojure.behaviors @@ -97,4 +97,18 @@ [:sidebar.docs.search :lt.plugins.clojure/clj-doc-search] [:sidebar.docs.search :lt.plugins.clojure/cljs-doc-search] + + ; collapsible stacktrace + [:editor.clj.common :lt.plugins.clojure.collapsible-exception/expandable-exceptions] + ; new :collapsible.exception TAG + [:collapsible.exception :lt.objs.menu/menu!] + [:collapsible.exception :lt.objs.eval/ex-menu+] + [:collapsible.exception :lt.objs.eval/ex-clear] + [:collapsible.exception :lt.objs.eval/expand-on-click] + [:collapsible.exception :lt.objs.eval/shrink-on-double-click] + [:collapsible.exception :lt.objs.eval/destroy-on-cleared] + ;[:inline.exception :lt.objs.eval/copy-exception] + ;NOTE: currently this copies the complete stacktrace but what about just + ; copying the summary? + [:collapsible.exception :lt.objs.eval/copy-result] ] diff --git a/src/lt/plugins/clojure/collapsible_exception.cljs b/src/lt/plugins/clojure/collapsible_exception.cljs new file mode 100644 index 0000000..9d4e714 --- /dev/null +++ b/src/lt/plugins/clojure/collapsible_exception.cljs @@ -0,0 +1,100 @@ +(ns lt.plugins.clojure.collapsible-exception + (:require [lt.util.dom :as dom] + [lt.object :as object] + [lt.objs.editor :as ed] + [lt.objs.notifos :as notifos] + [crate.binding :refer [bound]]) + (:require-macros [lt.macros :refer [defui behavior]])) + +(def ^:private ^:const NOT_FOUND -1) + +(defn truncate + "truncate a string at newline or at 100 characters long" + [text] + (when-not (empty? text) + (if (= NOT_FOUND (.indexOf text "\n")) + (subs text 0 100); take 100 characters + (first (clojure.string/split-lines text))))) + +(defn ->collapse-class [this summary] + (str "inline-exception result-mark" + (when (:open this) " open"))) + +(defui collapsible-exception-UI [this info] + (let [stacktrace (:result info) + summary (str (:summary info) " ...")] + [:span {:class (bound this #(->collapse-class % summary)) + :style "background: #73404c; color: #ffa6a6; + max-width:initial; max-height:initial"} + [:span.truncated summary] + [:span.full stacktrace]]) + :mousewheel (fn [e] (dom/stop-propagation e)) + :click (fn [e] (dom/prevent e) + (object/raise this :click)) + :contextmenu (fn [e] (dom/prevent e) + (object/raise this :menu! e)) + :dblclick (fn [e] (dom/prevent e) + (object/raise this :double-click))) + +(object/object* ::collapsible-exception + :triggers #{:click :double-click :clear!} + :tags #{:inline :collapsible.exception} + :init + (fn [this info] + (when-let [ed (ed/->cm-ed (:ed info))] + (let [content (collapsible-exception-UI this info)] + (object/merge! this (assoc info + :widget (ed/line-widget (ed/->cm-ed (:ed info)) (:line (:loc info)) + content, {:coverGutter false}))) + content)))) + +(behavior ::expandable-exceptions + :triggers #{:editor.exception.collapsible} + :reaction + (fn [this summary stack loc] + (let [ed (:ed @this) + line (ed/line-handle ed (:line loc)) + ex-obj (object/create ::collapsible-exception + {:ed this, :result stack, + :summary summary + :loc loc, :line line})] + (when-let [prev (get (@this :widgets) [line :inline])] + (when (:open @prev) (object/merge! ex-obj {:open true})) + (object/raise prev :clear!)) + (when (:start-line loc) + (doseq [widget (map #(get (@this :widgets) [(ed/line-handle ed %) :inline]) + (range (:start-line loc) (:line loc))) + :when widget] + (object/raise widget :clear!))) + (object/update! this [:widgets] assoc [line :inline] ex-obj)))) + + +(behavior ::clj-expandable-exception + :triggers #{:editor.eval.clj.exception} + :reaction (fn [obj res passed?] + (when-not passed? + (notifos/done-working "")) + (let [meta (:meta res) + loc {:line (dec (:end-line meta)) :ch (:end-column meta 0) + :start-line (dec (:line meta 1))}] + (notifos/set-msg! (:result res) {:class "error"}) + (object/raise obj :editor.exception.collapsible (:result res) (:stack res) loc)))) + +(behavior ::cljs-expandable-exception + :triggers #{:editor.eval.cljs.exception} + :reaction (fn [obj res passed?] + (when-not passed? + (notifos/done-working "")) + (let [meta (:meta res) + loc {:line (dec (:end-line meta)) :ch (:end-column meta) + :start-line (dec (:line meta))} + msg (or (:stack res) (truncate (:ex res))) + stack (cond + (:stack res) (:stack res) + (and (:ex res) (.-stack (:ex res))) (.-stack (:ex res)) + (and (:ex res) (:verbatim meta)) (:ex res) + (and (:ex res) (not (:verbatim meta))) (pr-str (:ex res)) + (not (nil? msg)) (or (:stack res) (:ex res)); untruncated stacktrace + :else "Unknown error")] + (notifos/set-msg! msg {:class "error"}) + (object/raise obj :editor.exception.collapsible msg stack loc))))