LightTable / Clojure

Light Table Clojure language plugin
MIT License
99 stars 47 forks source link

inline plot support #75

Closed carocad closed 8 years ago

carocad commented 8 years ago

Hey guys, Currently I am using lighttable for a lot of plotting stuff. I know that Lighttable's Python plugin supports inline plotting and I wonder why doesn't the clojure plugin supports this aswell?

After I quick overview of the code in both plugins the only thing that I found was this definition here which is used in the ::python-image behavior.

Now, I am not a Lighttable hacker but by the looks of it, it seems to simply take a base64-encoded image and creates an html img tag to bind it to lighttables UI. If I'm right this should be straight forward right?

As I said, I am not a LT hacker so take my words with caution

cldwalker commented 8 years ago

Hi carocad. The goal of the Clojure plugin is to provide tools that are generally useful to Clojure developers. Unfortunately I don't think plotting is such a tool. Building your own plotting tools would be a first good plugin. Here are some resources for getting started on plugins. It might also be neat to check out the gorilla repl and see if there is a possible plugin to be had there. Feel free to reach out to the mailing list if you have questions

carocad commented 8 years ago

Hey cldwalker, I disagree with you. I think that inline ploting would be one of the best addition to the clojure plugin. As the old saying states: "a picture is worth a thousand words". Also, from my point of view, inline results are the very spirit of Lighttable, why should be plots different?

Of course I think this could be added to another plugin but for me the ploting capabilities should be at the core of Lighttable, and not something that you have to reach for after you know how lighttable works.

I should clear that I don't think that the Clojure plugin should support all kinds of drawings, but rather a very simple interface that other programs could use to show whatever they want; be it a chart, a drawing or even formatted text as the gorilla repl does.

I will give it a try with the separate plugin but I am still hoping that the content of it can eventually be migrated to the Clojure plugin

kenny-evitt commented 8 years ago

@carocad How does the Python plugin support plotting? If you don't know, would you dig a little and try to find out? I would guess it's not explicitly supporting any particular plotting library, but maybe I'm wrong.

At some level, we shouldn't need to do much to support plotting. Everything in LT is HTML, CSS, and JavaScript, so displaying an image should be relatively straightforward. But maybe Python itself has support for 'first class image objects' in a way that Clojure doesn't.

This isn't something we, the core team, are interested in implementing. But we'll help anyone that is so interested. And I'll offer additional tentative help as a sounding board and tester.

carocad commented 8 years ago

@kenny-evitt I already gave it a quick look. The Python plugin only supports png images passed to lighttable through a base64 serialization. Basically any Python code that returns a png image is serialized using base64 format. When Lighttable finds this content type, it creates an HTML tag with "src=data:image/png;base64,{{content}}" where content is the serialized image.

The relevant lines of codes that I have found so far are here and here. The former returns the content with the serialized image whereas the later inserts the img tag into Lighttable's UI.

Since I am quite new to hacking Lighttable I still don't know how does lighttable get the messages that are being procesed on the first link in order to get the image, nor where is this behavior being used on Lighttables editor.

If you have any idea about how this works I think that could help me a lot :D

kenny-evitt commented 8 years ago

@carocad Here are some more clues.

This line is the start of the Python function that handles messages from IPy (or maybe via IPy is better; IPy is used to communicate with IPython – or so I am guessing!).

These lines assign the correct Light Table command based on the type of the message being 'image':

  elif m['type'] == 'image':
    command = 'editor.eval.python.image'
    ret['image'] = m['data']

Note that the command 'editor.eval.python.image' matches the trigger of the following behavior in python.cljs:


(behavior ::python-image
                  :triggers #{:editor.eval.python.image}
                  :reaction (fn [editor img]
                              ;(console/log (pr-str img))
                              (object/raise editor :editor.result.underline (image (:image img)) {:line (-> img :meta :end)
                                                                                                  :start-line (-> img :meta :start)})
                              ))

So, based on this, what you're going to want to find is where in the Clojure client code it's evaluating the results of evaluating Clojure code and then deciding what commands to issue back to Light Table.

carocad commented 8 years ago

@kenny-evitt thanks for the tips I have some further questions for you which I hope you can answer so that I can continue with this idea. I found out that Lighttable calls the eval command on the Clojure plugin through the different eval commands.

(cmd/command {:command :eval-editor
              :desc "Eval: Eval editor contents"
              :exec (fn []
                      (when-let [ed (pool/last-active)]
                        (object/raise ed :eval)))})

(cmd/command {:command :eval-editor-form
              :desc "Eval: Eval a form in editor"
              :exec (fn []
                      (when-let [ed (pool/last-active)]
                        (object/raise ed :eval.one)))})

(cmd/command {:command :eval.custom
              :desc "Eval: Eval custom expression in editor"
              :hidden true
              :exec (fn [exp opts]
                      (when-let [ed (pool/last-active)]
                        (object/raise ed :eval.custom exp opts)))})

All of them later converge to the :eval! behavior through other similar behaviors (:on-eval.*). :eval! triggers (I'm guessing here) :editor.eval.clj.result which will then trigger one of the following

Almost all of then later raise a lighttable behavior called :editor.result (example); which is what i'm guessing display everything in the editor.

Now given this information, I have several doubts about what to do. First of all, what is the function of result.replace, result.return? They seem to do something different but I have no idea about it.

Furthermore result.inline and result.inline-at-cursor differ in like two LOC. Are they really different? One last thing which is probably a killer question :/ (sorry): the Clojure plugin triggers the :editor.result behaviour whereas the python plugin triggers :editor.result.underline to display the inline image. Do you know why? is it necessary to use :editor.result.underline to append an HTML tag?

So, extending even more my guessing here: I think that modiying :editor.eval.clj.result.inline* at the point where they raise :editor.result should do the trick. Simply put an additional if condition to check if the result information is base64 formatted and if so then display it as in python. That would be relatively easy to modify directly in the Clojure plugin but, again since I'm not a Ligthttable hacker I don't know how to overwrite others plugins behaviors (assuming that I choose to create one of my own), which I guess would be even more difficult.

Any recommendations here?

carocad commented 8 years ago

Just for the record in case somebody comes here looking for the plugin to allow inline plot. My guesses were right so I implemented a plugin for it. You can find it here: https://github.com/carocad/nerdy-painter