portal 2024-11-27

I'm using Vscode/Calva with the portal extension, and connecting remotely to a project where I cannot add portal as a dependency (also on clojure 1.11 so no add-lib). Is there any way to use portal? Something like a calva middleware maybe?

If the project is using lein, you can add the dependency to your lein profile

I think the problem here is that the remote app is already running, exposing an nrepl server?

Yep, it's deployed in a server exposing nrepl

I would try to convince whoever runs it to include portal middleware. It’s safer than many alternative ways to inspect the running process, as I see it.

If it works at all, I imagine it would have to be by sending serialized strings (losing metadata etc), but better than nothing

I can probably add portal to the project, it just might take a few weeks for the deployed version to be updated

I think you can use portal client side. And it’s probably the same as what you are talking about with serialized data.

I find this standalone portal which may be the option for this: https://cljdoc.org/d/djblue/portal/0.58.3/doc/guides/portal-standalone

The question is more how to send the data to portal (in a not too annoying way, I could copy paste the output of the eval). Maybe a joyride script that captures the output of calva eval commands, and sends it to portal?

Yeah. Calva has commands for copying the latest result to the clipboard. And a simple #joyride script should be able to post to the standalone portal with the data encoded correctly.

πŸ‘ 1

Thanks, I wanted to see if there was already something done, but writing that joyride script shouldn't be difficult

There may be already something done. Ask in #joyride about it. But also, probably fun to solve. πŸ˜ƒ

I would use the runCommand command to string the copying and sending to portal as a sequence. VS Code has API for accessing the clipboard data. https://code.visualstudio.com/api/references/vscode-api#Clipboard

πŸ‘€ 1

I wrote a little something about runCommands here: https://blog.agical.se/en/posts/vs-code-runcommands-for-multi-commands-keyboard-shortcuts/ It includes some tips about finding command ids and such.

TL;DR: πŸ˜ƒ The Calva load current file reimplemented (well, actshhuallyyyy) using runCommands:

{
    "command": "runCommands",
    "key": "ctrl+alt+c ctrl+alt+enter",
    "args": {
      "commands": [
        "editor.action.selectAll",
        "calva.evaluateSelection",
        "cursorUndo"
      ]
    }
  },

Here’s a bit of a crazy way to fire of a joyride script without hosting a script:

{
    "command": "joyride.runCode",
    "comment": "Replaces current selection with a markdown link",
    "args": "(require '[\"vscode\" :as vscode]\n '[promesa.core :as p]\n '[joyride.core :as joy])\n\n(defn current-selection []\n (let [editor vscode/window.activeTextEditor\n selection (.-selection editor)]\n selection))\n\n(defn current-document []\n (let [editor vscode/window.activeTextEditor\n document (.-document editor)]\n document))\n\n(defn current-selection-text []\n (.getText (current-document) (current-selection)))\n\n(defn insert-text!+ \n ([text]\n (insert-text!+ text vscode/window.activeTextEditor (.-active (current-selection))))\n ([text editor position]\n (-> (.edit editor\n (fn [builder]\n (.insert builder position text))\n #js {:undoStopBefore true :undoStopAfter false})\n (p/catch (fn [e]\n (js/console.error e))))))\n\n(defn replace-range!+\n ([text]\n (replace-range!+ text vscode/window.activeTextEditor (current-selection)))\n ([text editor range]\n (-> (.edit editor\n (fn [builder]\n (.replace builder range text))\n #js {:undoStopBefore true :undoStopAfter false})\n (p/catch (fn [e]\n (js/console.error e))))))\n\n(defn delete-range!\n [editor range]\n (-> (p/do (.edit editor\n (fn [builder]\n (.delete builder range))\n #js {:undoStopBefore true :undoStopAfter false}))\n (p/catch (fn [e]\n (js/console.error e)))))\n\n(defn markdown-link! []\n (p/do (replace-range!+ (str \"[\" (current-selection-text) \"]()\"))\n (let [pos (.translate (.-end (current-selection)) 0, -1)]\n (aset vscode/window.activeTextEditor \"selection\" (vscode/Selection. pos pos)))))\n\n(markdown-link!)",
    "key": "ctrl+alt+j ctrl+cmd+m"
  },

(Without recommending to doing it that way πŸ˜ƒ )

So because Portal can be hosted by joyride, you can actually do something like πŸ‘‡ which gives you the identical experience of https://github.com/djblue/portal/blob/master/src/portal/nrepl.clj#L60-L96 but entirely remotely:

(ns portal.eval
  (:require [portal.api :as p]
            ["vscode" :as vscode]
            ["$v0" :as calva]))

(defn eval-str []
  (when-not (seq (p/sessions))
    (p/open {:launcher :vs-code})
    (add-tap #'p/submit))
  (let [editor  (.-activeTextEditor vscode/window)
        ns      (second (read-string (.. editor -document getText)))
        code    (second (.currentEnclosingForm calva/ranges))
        file    (.. editor -document -fileName)
        line    (inc (.. editor -selection -active -line))
        column  (inc (.. editor -selection -active -character))
        time    (js/Date.)
        start   (.now js/Date)]
    (-> (.evaluateCode calva/repl "clj" code {} #js {:ns (name ns)})
        (.then (fn [result]
                 (update
                  (merge
                   {:level :info}
                   (js->clj result :keywordize-keys true))
                  :result
                  (fn [result]
                    (try
                      (read-string result)
                      (catch :default _
                        result))))))
        (.catch (fn [ex]
                  (merge
                   {:level :error
                    :result ex})))
        (.then (fn [result]
                 (tap> (with-meta
                         (merge result
                                {:ns (symbol ns)
                                 :code code
                                 :file file
                                 :line line
                                 :column column
                                 :time time
                                 :ms (- (.now js/Date) start)
                                 :runtime :clj})
                         {:portal.viewer/for
                          {:code :portal.viewer/code
                           :time :portal.viewer/relative-time
                           :ms   :portal.viewer/duration-ms}
                          :portal.viewer/code {:language :clojure}})))))))

1

It has the additional advantage of opening up portal on eval if portal isn't already open πŸ‘Œ

1