Fork me on GitHub
#portal
<
2022-10-01
>
seancorfield21:10:27

Is there a way to manipulate a selected value in Portal via user-supplied code? In Reveal, you can select a value and in the prompt for an action type an arbitrary piece of code containing *v which is bound to the selected value. It would be really nice in Portal if you could press ctrl+j and instead of just typing characters to filter the functions available you could type ( and enter an expression with *v in it and have Portal evaluate that.

yes 1
djblue21:10:22

So the main reason I haven't done this yet is doing it well is very hard. I feel like being able to get the selected value back in your REPL was the compromise. You have all of your existing tooling (sexp editing, syntax highlight, auto-complete, docs, ...) and the data from Portal. Do you think the Portal atom is not solving this problem?

seancorfield21:10:39

The main usability issue is having to go back and forth between the editor and the Portal window to actually get something selected in Portal, then back to the editor to do something with it.

seancorfield21:10:42

I'm already doing stuff like this to remotely manipulate Portal in VS Code:

"snippet": "(portal.api/eval-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))))\")"

seancorfield21:10:27

The problem is that it is async, so I can't simply navigate Portal to a given state and then execute some arbitrary Clojure code in the JVM and be sure that Portal actually is in that state.

seancorfield21:10:56

By default, Portal does not auto-select the most recently tap>'d value. That would be a big help.

djblue21:10:00

Ohh :thinking_face:

seancorfield21:10:31

But that workflow would probably break other people's workflow that assumes a selection in Portal stays put...?

djblue21:10:57

Yeah, that could be an issue for others

djblue21:10:07

I do think the async thing is annoying too

djblue21:10:00

I feel like I have a good sense of the problem, I'll play around with some ideas and see what happens 👌

R.A. Porter21:10:12

Could add a config flag on startup to tell it whether to select most recent or to stick with current behavior

1
seancorfield21:10:38

Is there a "wait" call I could add so I could guarantee that when eval-str returns, it has completed the chain of execution? I'm guessing not.

djblue21:10:19

No, but I've been working on a better version of eval-str for nrepl eval support that could support that option

seancorfield21:10:51

My ideal would be full remote control of Portal from the JVM and the ability to intermix eval-str calls and JVM code without worrying about async stuff 🙂

💯 1
djblue21:10:52

I think having a custom submit which tracks the last tap could solve the issue of wanting the last tapped value

seancorfield21:10:26

I wanted tap> to "just work". I don't want to use custom code.

seancorfield21:10:18

Or are you saying that this would be startup code used instead of the regular submit?

👍 1
seancorfield21:10:47

Here's how I start Portal right now, via a custom snippet/hotkey in VS Code/Calva:

"snippet": "(do (ns dev) (def portal ((requiring-resolve 'portal.api/open) {:launcher :vs-code :portal.launcher/window-title (System/getProperty \"user.dir\")})) (add-tap (requiring-resolve 'portal.api/submit)))"

seancorfield21:10:59

So, yeah, I could add a tap> listener function in my dev.clj that saved the value to an atom and use that instead of dev/portal

seancorfield21:10:15

Right now, the above works without any specific server side code. I guess I could shoehorn it into that string when starting Portal 🙈

djblue21:10:40

(def *v nil)

(defn submit [value]
  (alter-var-root #'*v (constantly value))
  (p/submit value))

(add-tap submit)

djblue21:10:50

I feel like having a custom submit function is pretty awesome, and where users have a lot of leverage, like automatically datafy'ing values or waiting on promises. I just think it's hard to have one submit function that works for everyone.

djblue21:10:12

I do think it sucks to have to inline the code in json for calva 😢

djblue22:10:34

The *v solution does only work to give you the root value, which doesn't help if you want something inside of *v which you've already selected in the UI :thinking_face:

seancorfield22:10:08

Now, when I tap> some value, I can then do ctrl+alt+space q and in the popup type, say, (map first @dev/*v) and it tap>'s the result of running that.

seancorfield22:10:03

I could probably add a str/replace on the (read-line) value to make *v auto-expand to that 🙂

seancorfield22:10:07

Done. OK. That's pretty nice. Thanks for the feedback.

djblue22:10:17

Sweet! What does ctrl+alt+space q do, eval prompt?

djblue22:10:29

Tap Input Code 👌

djblue22:10:38

(let [*v "@dev/portal"
      v  (read-string (clojure.string/replace (read-line) \"*v\" *v))]
  (tap> (eval v)))
Does ☝️ also work?

seancorfield22:10:25

No, because no value is selected in Portal.

seancorfield22:10:53

That would work when you have a selected value in Portal. See above about Portal not auto-selecting the most recent tap>'d value.

👍 1
seancorfield22:10:34

If I'm working with a selected value, I can use @dev/portal directly in the code to be evaluated -- but I very rarely need to interact with Portal's window since I can expand/collapse values from Calva via other hotkeys in that settings file.

seancorfield22:10:43

I have ctrl+alt+space 0 to cycle through Portal's different viewers for whatever is the top value in the window. And I can expand/collapse the top-level or second-level directly with ctrl+alt+space 1 and 2 respectively.

seancorfield22:10:43

I usually only switch to Portal's window when I need to datafy/`nav` around something complexity or drill into just specific parts of a large, nested data structure.

👍 1
djblue22:10:28

So remote controlling the UI is how you mostly interact with portal :thinking_face:

djblue22:10:11

And composing those interactions is difficult because of the async stuff

seancorfield22:10:24

Yes. And yes. 🙂

seancorfield22:10:38

Basically, I don't want to lose focus from my editor while I'm working. I can switch focus between windows with ctrl+1 (main editor) ctrl+2 (portal) ctrl+3 (calva repl that I never type in) but it's still disruptive to do so if all I wanted to do was eval (tap) something, select the value in portal, eval something else based on that, since that involves switching from editor to portal, navigating around portal, switching back to the editor repeatedly.

djblue22:10:08

Thanks for sharing your workflow, I'll see if I can make Portal better support it 👍

seancorfield22:10:24

for the most part, just cycling through the portal viewers for the "top" value without leaving the editor gets me nearly everything I usually need from portal; with this new ability to invoke arbitrary code on the most recently-tapped value I can get almost everything done 🙂 and then I still have the option to switch to portal for more intensive data inspection / navigation.

seancorfield22:10:11

I think, when the notebook/portal stuff is integrated and solid, I may switch completely to that, depending on how it actually ends up working, since then "everything" would be in the main editor window.

djblue22:10:36

What viewers do you tend to cycle between?

djblue22:10:53

I think the main issue with the notebook stuff will be that it's all edn based, so no runtime connection

djblue22:10:59

I ask about the viewers thing because you can set metadata on the value before submitting to set the default viewer 👌

seancorfield22:10:07

Ah, and I think keyboard navigation may be a barrier to me using the notebook stuff in more depth since I can't figure out how to navigate into an inline result to interact with it 🙂 and a lack of runtime connection might well well a showstopper for me. Maybe.

seancorfield22:10:48

For the viewers Q, I literally just cycle through all of them as needed. Pressing ctrl+alt+space 0 repeatedly.

👍 1
djblue17:01:00

I forgot to post here, but I added an https://github.com/djblue/portal/blob/master/src/portal/api.cljc#L130 to eval-str to await the result of a promise before returning the result. This should make it a little easier to compose async actions from the host runtime 👍

2