Skip to content

Using Conjure programatically (API)

Noah edited this page Aug 30, 2023 · 7 revisions

Since Conjure is just a Lua program (compiled from Fennel) running within Neovim we can require the Conjure’s modules directly into other plugins or scripts. This allows you to build more tools atop of Conjure that get to take advantage of all of Conjure’s supported language clients.

The best documentation for this is the source itself, so please have a look around for public functions you’d like to use. Feel free to get in touch with me (Olical) if you have any questions around something specific.

Now let’s dive into some of the most common and useful functions in the API that you’ll probably want to use. I’ll show how to use them from Lua but it’ll be easy to take the leap into Vim Script or Fennel.

Using the right client

When a user interacts with Conjure using mappings they’ll be in the buffer they’re interested in already which will have a filetype of clojure or janet for example. When your plugin runs the user may have a completely different buffer in focus, imagine a callback being executed at some point in the future, the filetype of the current user buffer could be anything!

So when calling any (well, only some, but it’s best to be on the safe side) Conjure API functions, be sure to wrap them in a call to with-filetype as shown below. You don’t have to do this is you know the call was invoked while the user was in the buffer you’re interested in with no callbacks or delays in the way (such as from a mapping or command they invoked).

local client = require("conjure.client")

client["with-filetype"]("janet", my_awesome_function, 10, 20)

The arguments are the filetype you want, the function to be called and then any optional trailing arguments you would like to be passed to your function when it’s called.

Appending to the log

Every Conjure client has it’s own log buffer, here’s how we can append to it.

local client = require("conjure.client")
local log = require("conjure.log")

-- Append to the log of the client for the current buffer's filetype.
log.append({"# Hello, World!"})

-- With the filetype enforced, just in case you're calling this from a callback.
client["with-filetype"]("janet", log.append, {"# Hello, World!"})

append does take a second opts argument but that’s less important, here’s a rough overview of those options.

  • break? will insert a break line of -------…​ to separate log entries.

  • suppress-hud? will avoid showing the HUD.

  • join-first? will join the first line of your new lines onto the current last line of the log.

Evaluating code

You can evaluate arbitrary strings and get the results through a callback, it has to be a callback because almost all clients require asynchronous operations in order to evaluate. The same rules of wrapping in with-filetype from above apply here too.

local eval = require("conjure.eval")
local client = require("conjure.client")

client["with-filetype"]("fennel", eval["eval-str"], {
  origin = "my-awesome-plugin",
  code = "(+ 10 20)",
  ["passive?"] = true,
  ["on-result"] = function (r)
    print("RESULT:", r)
  end
})

The optional passive? argument is semi-experimental but it essentially prevents the log from being appended to for this evaluation. This allows your plugin to do the work silently in the background and then display whatever it needs to in it’s own way. You can remove this if you’d rather see your evaluations and results show up in the log automatically.

Cleaning up text

Janet evaluations will result in text wrapped in ANSI escape codes, we can use the conjure.text module to clean some of these things up if required. There’s a fair few useful functions in there so you should have a look at the source for yourself, but here’s how we could strip ANSI escape codes.

local eval = require("conjure.eval")
local client = require("conjure.client")
local text = require("conjure.text")

client["with-filetype"]("janet", eval["eval-str"], {
  origin = "my-awesome-plugin",
  code = "(+ 10 20)",
  ["passive?"] = true,
  ["on-result"] = function (r)
    local clean = text["strip-ansi-escape-sequences"](r)
    print("RESULT:", r)
    print("CLEANED:", clean)
  end
})

Using a custom test framework (Clojure)

Conjure comes with support for running tests using Clojure’s built-in test framework clojure.test, as well as a popular alternative test runner Koacha. However, there are many alternative test runners and test frameworks, so it can be desirable to define custom Conjure test-runners to allow for running the tests in projects that use those alternative frameworks.

local clj_actions = require("conjure.client.clojure.nrepl.action")

-- In this example, I'm defining a runner for Lazytest
clj_actions["test-runners"].lazytest = {
  -- A root namespace that will be `required` before calling the actual functions and used as the namespace for all functions below.
  ["namespace"] = "lazytest.runner.console",
  -- Function to run all loaded tests.
  ["all-fn"] = "run-all-tests",
  -- Function to run a specified namespace's tests.
  ["ns-fn"] = "run-tests",
  -- Function to run a single test.
  ["single-fn"] = "run-test-var",
  -- Some test runners allow for options to be passed to the `-fn` calls above. (For example, kaocha's default is `{:kaocha/color? false}`.)
  ["default-call-suffix"] = "",
  -- Some test runners use vars, others use lists of vars or resolved symbols. The prefix and suffix allow for wrapping the test var as needed.
  -- Lazytest relies on vars, so the below renders as `#'some-ns/some-fn-test`.
  ["name-prefix"] = "#'",
  ["name-suffix"] = "",
}

-- Instead of overwriting the `current_form_names` with `=`, insert so that you can merely switch the runner to switch between test runners.
table.insert(vim.g["conjure#client#clojure#nrepl#test#current_form_names"], "describe")

-- Actually set the test runner here. This is documented in Conjure's help docs.
vim.g["conjure#client#clojure#nrepl#test#runner"] = "lazytest"