nrepl.fnl

Branches > main > e6dd500 > files / nvim / pack / plugins / opt / conjure / fnl / conjure / remote / nrepl.fnl

(local {: autoload : define} (require :conjure.nfnl.module))
(local core (autoload :conjure.nfnl.core))
(local bencode (autoload :conjure.remote.transport.bencode))
(local client (autoload :conjure.client))
(local log (autoload :conjure.log))
(local net (autoload :conjure.net))
(local uuid (autoload :conjure.uuid))

(local M (define :conjure.remote.nrepl))

(fn M.with-all-msgs-fn [cb]
  (let [acc []]
    (fn [msg]
      (table.insert acc msg)
      (when msg.status.done
        (cb acc)))))

(fn M.enrich-status [msg]
  (let [ks (core.get msg :status)
        status {}]
    (core.run!
      (fn [k]
        (core.assoc status k true))
      ks)
    (core.assoc msg :status status)
    msg))

(fn M.connect [opts]
  "Connects to a remote nREPL server.
  * opts.host: The host string.
  * opts.port: Port as a string.
  * opts.on-failure: Function to call after a failed connection with the error.
  * opts.on-success: Function to call on a successful connection.
  * opts.on-error: Function to call when we receive an error (passed as argument) or a nil response.
  * opts.default-callback: Function to call when the user didn't provide a callback to their send.
  * opts.side-effect-callback: Intended for side-effects fired off the back of some messages. It's called with every message before the message callback handler.
  * opts.on-message: Function to call when we receive a message after the callback for the message is invoked.
  Returns a connection table containing a `destroy` function."
  (let [state {:message-queue []
               :awaiting-process? false
               :bc (bencode.new)
               :msgs {}}]

    (var conn
      {:session nil
       :state state})

    (fn send [msg cb]
      (let [msg-id (uuid.v4)]
        (core.assoc msg :id msg-id)

        (if
          (= :no-session msg.session)
          (core.assoc msg :session nil)

          (and (not msg.session) conn.session)
          (core.assoc msg :session conn.session))

        (log.dbg "send" msg)
        (core.assoc-in state [:msgs msg-id]
                    {:msg msg
                     :cb (or cb (fn []))
                     :sent-at (os.time)})
        (conn.sock:write (bencode.encode msg))
        nil))

    (fn process-message [err chunk]
      (if
        err (opts.on-error err)
        (not chunk) (opts.on-error)
        (->> (let [(ok? res) (pcall bencode.decode-all state.bc chunk)]
               (if ok?
                 res
                 (error (.. "conjure.remote.nrepl: Failed to decode message, maybe a different server is running on this port?\n" res))))
             (core.run!
               (fn [msg]
                 (log.dbg "receive" msg)
                 (M.enrich-status msg)

                 (let [(ok? err) (pcall opts.side-effect-callback msg)]
                   (when (not ok?)
                     (opts.on-error err)))

                 (let [cb (core.get-in state [:msgs msg.id :cb] opts.default-callback)
                       (ok? err) (pcall cb msg)]
                   (when (not ok?)
                     (opts.on-error err)))

                 (when msg.status.done
                   (core.assoc-in state [:msgs msg.id] nil))

                 (opts.on-message msg))))))

    (fn process-message-queue []
      (set state.awaiting-process? false)
      (when (not (core.empty? state.message-queue))
        (let [msgs state.message-queue]
          (set state.message-queue [])
          (core.run!
            (fn [args]
              (process-message (unpack args)))
            msgs))))

    (fn enqueue-message [...]
      (table.insert state.message-queue [...])
      (when (not state.awaiting-process?)
        (set state.awaiting-process? true)
        (client.schedule process-message-queue)))

    (fn handle-connect-fn []
      (client.schedule-wrap
        (fn [err]
          (if err
            (opts.on-failure err)

            (do
              (conn.sock:read_start (client.wrap enqueue-message))
              (opts.on-success))))))

    (set conn
         (core.merge!
           conn
           {:send send}
           (net.connect
             {:host opts.host
              :port opts.port
              :cb (handle-connect-fn)})))

    conn))

M