portal

caleb.macdonaldblack 2023-03-30T18:02:46.273649Z

Is there a way to pin something in portal? When debugging I often use def within code and it’s easy to evaluate it for the last value that was defined. This is better than logging or printing for cases where that code may be executed repeatedly and spamming my logs. Is this something that could be achieved with portal? Maybe even with changes?

djblue 2023-03-30T18:57:00.982029Z

Is the idea to use the value at the repl later or that you want to visually compare it to another?

djblue 2023-03-30T19:06:17.060659Z

You can pull a selected value back into the repl via derefing the portal atom which is returned from portal.api/open

djblue 2023-03-30T19:07:28.577289Z

(def p (portal.api/open))
;; select value in ui
(def selected @p)

djblue 2023-03-30T19:16:07.776589Z

(ns user
  (:require [portal.api :as p]))

(def values (atom []))

(defn push [& args]
  (swap! values into args))

(p/register! #'push)
☝️ would allow you to collect as many values as you want over time. Just select the value or multiple values and call your custom command user/push via the command palette

seancorfield 2023-03-30T19:43:00.026749Z

I have tried to write some fairly fancy code to ensure that the most recently tap>'d value always ends up as the most recent value in Portal with any middleware tap>'d values below it, so that I can programmatically get at that value from my editor. I don't want to have to interact with the Portal UI in general to select anything. I want to drive it all from my editor. And that requires a degree of predictability of where values are in the UI in order to get at them programmatically. I pretty much never want to interact with some arbitrary result produced by evaluation middleware -- but I want to drive Portal's UI from my editor to cause interactions with the most recent interesting value. In many ways, it would be ideal for me to have middleware eval output go into a separate panel in Portal and have just my direct tap> values go into the regular panel so the most recent was always what I explicitly tap>'d. I want the Portal UI to be more "drivable" by code, overall :)

seancorfield 2023-03-30T19:44:37.955079Z

TL;DR: it's not so much about having a specific value in Portal accessible to the program, as having the program be able to drive the UI in Portal around a specific value.

seancorfield 2023-03-30T19:49:48.421309Z

For example, I commonly want to cycle through viewers programmatically for the "most recently tap>'d value" but I want to ignore any middleware-produced data for that. Seeing that output is useful, and having the option to switch to the Portal UI and interact with it is nice -- and sometimes necessary -- but not my primary workflow when debugging. I have logging wired up to tap> into Portal, for example, and I already have a hot key set up to toggle that wiring on and off so I can eval a specific piece of code and not have logging clutter up Portal's UI, even tho' in general logging is useful. And I may end up doing something similar for middleware output, but it's still a pain to have to do this manually around specific evaluations. Having two separate sections in the UI for this would be my ideal choice: and then I could have logging and middleware go to the "secondary" panel and my explicit tap> results go to the "primary" panel -- and then programmatically interact with just that primary panel. Being able to start multiple VS Code launcher/viewer instances from a single JVM might solve this too -- not sure if that's possible?

djblue 2023-03-30T19:59:12.327879Z

@seancorfield would something like the following fit your workflow better?

(def tap-list (atom (list)))
(def eval-list (atom (list)))

(defn submit [value]
  (if (:portal.nrepl/eval value)
    (swap! eval-list conj value)
    (swap! tap-list conj value)))

(add-tap submit)

(portal.api/open
 {:window-title "tap-list"
  :value tap-list})
(portal.api/open
 {:window-title "eval-list"
  :value eval-list})

seancorfield 2023-03-30T19:59:43.317419Z

Can I have two VS Code windows open?

djblue 2023-03-30T20:00:13.732989Z

Yeah, you should be able to have multiple.

seancorfield 2023-03-30T20:00:33.807209Z

That's cool... let me try that...

djblue 2023-03-30T20:00:36.354969Z

And portal can be opened with any value, including atoms you can drive

djblue 2023-03-30T20:01:30.364099Z

The tap-list that is loaded into portal by default is:

(defonce ^:private tap-list
  (atom (with-meta (list)
          {:portal.viewer/default :portal.viewer/inspector})))

seancorfield 2023-03-30T20:03:40.265559Z

My setup is somewhat complicated by having my Portal start code be part of my Calva custom REPL snippets with logging wired up elsewhere but let me spend an hour playing with this... I had no idea I could manage my own tap list like that...

seancorfield 2023-03-30T20:05:22.514159Z

What will the portal.api/* functions do when there are multiple instances?

djblue 2023-03-30T20:06:05.143979Z

I think they do a broadcast, they should have an arity which includes passing in a specific instance

seancorfield 2023-03-30T20:07:28.668439Z

And that "specific instance" is what comes back from open?

πŸ‘ 1
djblue 2023-03-30T20:07:36.783009Z

close and clear are really the only ones that care

seancorfield 2023-03-30T20:08:12.426459Z

Well, I have a lot of this sort of stuff:

{:name "Portal Viewer"
   :key "0"
   :snippet (portal.api/eval-str
             (str
              '(let [state portal.ui.state/state]
                 (-> (portal.ui.commands/select-none state)
                     (.then #(portal.ui.commands/select-child state))
                     (.then #(portal.ui.commands/select-child state))
                     (.then #(portal.ui.commands/select-next-viewer state))
                     (.then #(portal.ui.commands/select-none state))))))}

djblue 2023-03-30T20:08:43.738909Z

eval-str also takes an instance πŸ‘Œ

seancorfield 2023-03-30T20:08:46.293309Z

Ah, it accepts portal too... OK, this is looking very promising πŸ™‚

djblue 2023-03-30T20:10:04.974049Z

Ohh, also I added {:await true} to eval-str so you can block on promises if you still wanted to use something like that

seancorfield 2023-03-30T20:10:52.011029Z

Nice, thanks.

djblue 2023-03-30T20:10:55.112509Z

(portal.api/eval-str portal "(.resolve js/Promise :hi)" {:await true}) => :hi

seancorfield 2023-03-30T20:21:40.606969Z

OMG! THIS IS SO AWESOME!! This dramatically simplifies my Portal setup and provides so much more value!!!

1
1
πŸŽ‰ 2
seancorfield 2023-03-30T20:22:37.001839Z

Interesting tap>'d values on top; middleware output below:

djblue 2023-03-30T20:24:41.761449Z

That's looking very nice πŸ‘Œ I think we have portal setup similarly for logging / taps at work

djblue 2023-03-30T20:24:47.188349Z

Sorry I didn't mention it earlier

djblue 2023-03-30T20:32:14.340479Z

https://cljdoc.org/d/djblue/portal/0.37.1/doc/guides/custom-tap-list for some other ideas

seancorfield 2023-03-30T20:37:42.969899Z

The "danger" of an infinitely flexible toolchain is that you can almost never know what is possible πŸ™‚

πŸ’― 1
1
djblue 2023-03-30T20:41:04.265639Z

I think the upcoming notebook updates are gonna make things even more flexible / fun for vscode users

seancorfield 2023-03-30T21:11:54.394839Z

https://github.com/seancorfield/vscode-calva-setup/commit/d379e6b202dc44404a82d3eb44ee8ab8d9bbd025 -- so much simplification πŸ™‚

djblue 2023-03-30T22:09:44.722009Z

Ohh, if your eval-str commands return :portal.api/ignore the nrepl middleware will not tap them πŸ‘Œ

djblue 2023-03-30T22:09:58.292369Z

Also, the code does look much nicer awesome

seancorfield 2023-03-30T22:41:28.314759Z

Good to know about :portal.api/ignore (allow that seems a strange value for the nREPL middleware to care about -- I'm surprised it isn't :portal.nrepl/ignore πŸ™‚ )

djblue 2023-03-30T22:44:00.984349Z

That would be better, but I have some custom socket repl based tooling that I use that needs the same behavior so I use that keyword for both

djblue 2023-03-30T22:47:42.259739Z

I could use both if it makes more sense πŸ€”

seancorfield 2023-03-30T22:49:42.491639Z

I updated my config.edn to use that. It caught another case I wasn't handling, so that's helpful.

seancorfield 2023-03-30T23:34:00.979319Z

For folks using my https://github.com/seancorfield/vscode-calva-setup repo -- or interested in a "real-world" VS Code/Calva/Portal setup -- I've updated the custom REPL snippets (which drive Portal in my setup) to run two Portal windows in VS Code: one for middleware output (which looks mostly like logging) and one for dedicated tap> output, such as when you're debugging code or doing explicit ctrl+alt+t space tap> evals on code, so that you can view and interact with them separately: my custom REPL snippets (`ctrl+alt+space <key>`) all operate on the dedicated tap> output window, and you can "lift" the most recent middleware result into the tap> window (`ctrl+alt+space l`). In addition, if you're using my https://github.com/seancorfield/dot-clojure repo and the :dev/repl alias there, logging from clojure.tools.logging is also routed into the middleware output window now, rather than the main tap> window. I want to reiterate my gratitude gratitude gratitude for all the work @djblue puts in on Portal and for creating such an amazingly flexible and powerful tool!

1
❀️ 5
djblue 2023-03-31T05:03:40.805879Z

It's really exciting seeing the evolution of how you utilize Portal in your workflow so thanks for taking the time to share your experiences ❀️

seancorfield 2023-03-31T05:35:49.025399Z

I need to write another blog entry πŸ™‚