Skip to content

Commit

Permalink
Add a stdio based janet client
Browse files Browse the repository at this point in the history
  • Loading branch information
sogaiu committed Oct 27, 2023
1 parent cb86d46 commit 5036ec7
Show file tree
Hide file tree
Showing 5 changed files with 371 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The core features of Conjure are language agnostic (although it's targeted at Li
* https://fennel-lang.org/[Fennel] inside Neovim via Lua and https://github.com/Olical/aniseed[Aniseed] (https://github.com/Olical/conjure/wiki/Quick-start:-Fennel-(Aniseed)[quickstart])
* https://fennel-lang.org[Fennel] outside of Neovim within any Lua process through an stdio REPL (https://github.com/Olical/conjure/wiki/Quick-start:-Fennel-(stdio)[quickstart])
* https://janet-lang.org/[Janet] over https://github.com/janet-lang/spork/#networked-repl[spork/netrepl] (https://github.com/Olical/conjure/wiki/Quick-start:-Janet-(netrepl)[quickstart])
* https://janet-lang.org/[Janet] over stdio (https://github.com/Olical/conjure/wiki/Quick-start:-Janet-(stdio)[quickstart])
* https://racket-lang.org/[Racket] over stdio (https://github.com/Olical/conjure/wiki/Quick-start:-Racket-(stdio)[quickstart])
* https://docs.hylang.org[Hy] over stdio (https://github.com/Olical/conjure/wiki/Quick-start:-Hy-(stdio)[quickstart])
* https://www.gnu.org/software/mit-scheme/[Scheme] (MIT by default) over stdio (https://github.com/Olical/conjure/wiki/Quick-start:-Scheme-(stdio)[quickstart])
Expand Down
77 changes: 77 additions & 0 deletions doc/conjure-client-janet-stdio.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
*conjure-client-janet-stdio*

==============================================================================
CONTENTS *conjure-client-janet-stdio-contents*

1. Introduction ........ |conjure-client-janet-stdio-introduction|
2. Mappings ................ |conjure-client-janet-stdio-mappings|
3. Configuration ...... |conjure-client-janet-stdio-configuration|

==============================================================================
INTRODUCTION *conjure-client-janet-stdio-introduction*
>
Janet is a functional and imperative programming language. It runs on
Windows, Linux, macOS, BSDs, and should run on other systems with some
porting. The entire language (core library, interpreter, compiler,
assembler, PEG) is less than 1MB. You can also add Janet scripting to an
application by embedding a single C file and two headers.
Conjure starts a Janet REPL within Neovim when you first open a Janet file.

The default Janet filetype client is `conjure.client.janet.netrepl`, to use
this client instead you must override the configuration.
>
let g:conjure#filetype#janet = "conjure.client.janet.stdio"
You should be able to evaluate files and forms as you would with other Conjure
supported languages right away.

Check out `:ConjureSchool` if you're unsure about what evaluation operations
you can perform.

* https://janet-lang.org/

==============================================================================
MAPPINGS *conjure-client-janet-stdio-mappings*

These mappings are the defaults, you can change them as described in
|conjure-mappings| and |conjure-configuration|.

See |conjure-client-janet-stdio-configuration| for specific configuration
options relevant to these mappings.

<localleader>cs Start the Janet REPL if it's not running already.

<localleader>cS Stop any existing Janet REPL.

==============================================================================
CONFIGURATION *conjure-client-janet-stdio-configuration*

All configuration can be set as described in |conjure-configuration|.


*g:conjure#client#janet#stdio#mapping#start*
`g:conjure#client#janet#stdio#mapping#start`
Start the Janet REPL if it's not running already.
Default: `"cs"`

*g:conjure#client#janet#stdio#mapping#stop*
`g:conjure#client#janet#stdio#mapping#stop`
Stop any existing Janet REPL.
Default: `"cS"`

*g:conjure#client#janet#stdio#command*
`g:conjure#client#janet#stdio#command`
Command used to start the Janet REPL, you can modify this to add
arguments or change the command entirely.
Default: `"janet -n -s"`

*g:conjure#client#janet#stdio#prompt_pattern*
`g:conjure#client#janet#stdio#prompt_pattern`
Lua pattern to identify a new REPL prompt. This match signals to
Conjure that the previous evaluation is complete and we're ready
to submit more code as well as collect all output prior to the
marker as the result.
Default: `"repl:[0-9]+:[^>]*> "`

vim:tw=78:sw=2:ts=2:ft=help:norl:et:listchars=
8 changes: 6 additions & 2 deletions doc/conjure.txt
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,11 @@ configure the ones you care about one at a time you can set:

*g:conjure#filetype#janet*
`g:conjure#filetype#janet`
Client to use for `janet` buffers.
Help: |conjure-client-janet-netrepl|
Client to use for `janet` buffers. Conjure also ships with an
alternative `"conjure.client.janet.stdio"` client which allows
you to work with Janet without installing and running a netrepl
server.
Help: |conjure-client-janet-netrepl|, |conjure-client-janet-stdio|
Default: `"conjure.client.janet.netrepl"`

*g:conjure#filetype#hy*
Expand Down Expand Up @@ -949,6 +952,7 @@ your dotfiles.
- |conjure-client-fennel-stdio|
- |conjure-client-clojure-nrepl|
- |conjure-client-janet-netrepl|
- |conjure-client-janet-stdio|
- |conjure-client-hy-stdio|
- |conjure-client-racket-stdio|
- |conjure-client-scheme-stdio|
Expand Down
140 changes: 140 additions & 0 deletions fnl/conjure/client/janet/stdio.fnl
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
(module conjure.client.scheme.stdio
{autoload {a conjure.aniseed.core
str conjure.aniseed.string
nvim conjure.aniseed.nvim
stdio conjure.remote.stdio
config conjure.config
mapping conjure.mapping
client conjure.client
log conjure.log
ts conjure.tree-sitter}
require-macros [conjure.macros]})

(config.merge
{:client
{:janet
{:stdio
{:mapping {:start "cs"
:stop "cS"}
;; -n -> disables ansi color
;; -s -> raw stdin (no getline functionality)
:command "janet -n -s"
;; Example prompts:
;;
;; "repl:23:>"
;; "repl:8:(>"
;;
:prompt_pattern "repl:[0-9]+:[^>]*> "
;; XXX: Possibly at a future date (janet -d + (debug)):
;;
;; "debug[7]:2>"
;; "debug[7]:2:{>"
;;
;;:prompt_pattern "(repl|debug\\[[0-9]+\\]):[0-9]+:[^>]*> "
}}}})

(def- cfg (config.get-in-fn [:client :janet :stdio]))

(defonce- state (client.new-state #(do {:repl nil})))

(def buf-suffix ".janet")
(def comment-prefix "# ")
(def form-node? ts.node-surrounded-by-form-pair-chars?)

(defn- with-repl-or-warn [f opts]
(let [repl (state :repl)]
(if repl
(f repl)
(log.append [(.. comment-prefix "No REPL running")]))))

(defn unbatch [msgs]
{:out (->> msgs
(a.map #(or (a.get $1 :out) (a.get $1 :err)))
(str.join ""))})

(defn- format-message [msg]
(->> (str.split msg.out "\n")
(a.filter #(~= "" $1))))

(defn- prep-code [s]
(.. s "\n"))

(defn eval-str [opts]
(with-repl-or-warn
(fn [repl]
(repl.send
(prep-code opts.code)
(fn [msgs]
(let [lines (-> msgs unbatch format-message)]
(when opts.on-result
(opts.on-result (a.last lines)))
(log.append lines)))
{:batch? true}))))

(defn eval-file [opts]
(eval-str (a.assoc opts :code (a.slurp opts.file-path))))

(defn- display-repl-status [status]
(let [repl (state :repl)]
(when repl
(log.append
[(.. comment-prefix (a.pr-str (a.get-in repl [:opts :cmd])) " (" status ")")]
{:break? true}))))

(defn stop []
(let [repl (state :repl)]
(when repl
(repl.destroy)
(display-repl-status :stopped)
(a.assoc (state) :repl nil))))

(defn start []
(if (state :repl)
(log.append [(.. comment-prefix "Can't start, REPL is already running.")
(.. comment-prefix "Stop the REPL with "
(config.get-in [:mapping :prefix])
(cfg [:mapping :stop]))]
{:break? true})
(a.assoc
(state) :repl
(stdio.start
{:prompt-pattern (cfg [:prompt_pattern])
:cmd (cfg [:command])

:on-success
(fn []
(display-repl-status :started))

:on-error
(fn [err]
(display-repl-status err))

:on-exit
(fn [code signal]
(when (and (= :number (type code)) (> code 0))
(log.append [(.. comment-prefix "process exited with code " code)]))
(when (and (= :number (type signal)) (> signal 0))
(log.append [(.. comment-prefix "process exited with signal " signal)]))
(stop))

:on-stray-output
(fn [msg]
(log.append (format-message msg)))}))))

(defn on-load []
(start))

(defn on-filetype []
(mapping.buf
:JanetStart (cfg [:mapping :start])
start
{:desc "Start the REPL"})

(mapping.buf
:JanetStop (cfg [:mapping :stop])
stop
{:desc "Stop the REPL"}))

(defn on-exit []
(stop))

147 changes: 147 additions & 0 deletions lua/conjure/client/janet/stdio.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
local _2afile_2a = "fnl/conjure/client/janet/stdio.fnl"
local _2amodule_name_2a = "conjure.client.scheme.stdio"
local _2amodule_2a
do
package.loaded[_2amodule_name_2a] = {}
_2amodule_2a = package.loaded[_2amodule_name_2a]
end
local _2amodule_locals_2a
do
_2amodule_2a["aniseed/locals"] = {}
_2amodule_locals_2a = (_2amodule_2a)["aniseed/locals"]
end
local autoload = (require("conjure.aniseed.autoload")).autoload
local a, client, config, log, mapping, nvim, stdio, str, ts, _ = autoload("conjure.aniseed.core"), autoload("conjure.client"), autoload("conjure.config"), autoload("conjure.log"), autoload("conjure.mapping"), autoload("conjure.aniseed.nvim"), autoload("conjure.remote.stdio"), autoload("conjure.aniseed.string"), autoload("conjure.tree-sitter"), nil
_2amodule_locals_2a["a"] = a
_2amodule_locals_2a["client"] = client
_2amodule_locals_2a["config"] = config
_2amodule_locals_2a["log"] = log
_2amodule_locals_2a["mapping"] = mapping
_2amodule_locals_2a["nvim"] = nvim
_2amodule_locals_2a["stdio"] = stdio
_2amodule_locals_2a["str"] = str
_2amodule_locals_2a["ts"] = ts
_2amodule_locals_2a["_"] = _
config.merge({client = {janet = {stdio = {mapping = {start = "cs", stop = "cS"}, command = "janet -n -s", prompt_pattern = "repl:[0-9]+:[^>]*> "}}}})
local cfg = config["get-in-fn"]({"client", "janet", "stdio"})
do end (_2amodule_locals_2a)["cfg"] = cfg
local state
local function _1_()
return {repl = nil}
end
state = ((_2amodule_2a).state or client["new-state"](_1_))
do end (_2amodule_locals_2a)["state"] = state
local buf_suffix = ".janet"
_2amodule_2a["buf-suffix"] = buf_suffix
local comment_prefix = "# "
_2amodule_2a["comment-prefix"] = comment_prefix
local form_node_3f = ts["node-surrounded-by-form-pair-chars?"]
_2amodule_2a["form-node?"] = form_node_3f
local function with_repl_or_warn(f, opts)
local repl = state("repl")
if repl then
return f(repl)
else
return log.append({(comment_prefix .. "No REPL running")})
end
end
_2amodule_locals_2a["with-repl-or-warn"] = with_repl_or_warn
local function unbatch(msgs)
local function _3_(_241)
return (a.get(_241, "out") or a.get(_241, "err"))
end
return {out = str.join("", a.map(_3_, msgs))}
end
_2amodule_2a["unbatch"] = unbatch
local function format_message(msg)
local function _4_(_241)
return ("" ~= _241)
end
return a.filter(_4_, str.split(msg.out, "\n"))
end
_2amodule_locals_2a["format-message"] = format_message
local function prep_code(s)
return (s .. "\n")
end
_2amodule_locals_2a["prep-code"] = prep_code
local function eval_str(opts)
local function _5_(repl)
local function _6_(msgs)
local lines = format_message(unbatch(msgs))
if opts["on-result"] then
opts["on-result"](a.last(lines))
else
end
return log.append(lines)
end
return repl.send(prep_code(opts.code), _6_, {["batch?"] = true})
end
return with_repl_or_warn(_5_)
end
_2amodule_2a["eval-str"] = eval_str
local function eval_file(opts)
return eval_str(a.assoc(opts, "code", a.slurp(opts["file-path"])))
end
_2amodule_2a["eval-file"] = eval_file
local function display_repl_status(status)
local repl = state("repl")
if repl then
return log.append({(comment_prefix .. a["pr-str"](a["get-in"](repl, {"opts", "cmd"})) .. " (" .. status .. ")")}, {["break?"] = true})
else
return nil
end
end
_2amodule_locals_2a["display-repl-status"] = display_repl_status
local function stop()
local repl = state("repl")
if repl then
repl.destroy()
display_repl_status("stopped")
return a.assoc(state(), "repl", nil)
else
return nil
end
end
_2amodule_2a["stop"] = stop
local function start()
if state("repl") then
return log.append({(comment_prefix .. "Can't start, REPL is already running."), (comment_prefix .. "Stop the REPL with " .. config["get-in"]({"mapping", "prefix"}) .. cfg({"mapping", "stop"}))}, {["break?"] = true})
else
local function _10_()
return display_repl_status("started")
end
local function _11_(err)
return display_repl_status(err)
end
local function _12_(code, signal)
if (("number" == type(code)) and (code > 0)) then
log.append({(comment_prefix .. "process exited with code " .. code)})
else
end
if (("number" == type(signal)) and (signal > 0)) then
log.append({(comment_prefix .. "process exited with signal " .. signal)})
else
end
return stop()
end
local function _15_(msg)
return log.append(format_message(msg))
end
return a.assoc(state(), "repl", stdio.start({["prompt-pattern"] = cfg({"prompt_pattern"}), cmd = cfg({"command"}), ["on-success"] = _10_, ["on-error"] = _11_, ["on-exit"] = _12_, ["on-stray-output"] = _15_}))
end
end
_2amodule_2a["start"] = start
local function on_load()
return start()
end
_2amodule_2a["on-load"] = on_load
local function on_filetype()
mapping.buf("JanetStart", cfg({"mapping", "start"}), start, {desc = "Start the REPL"})
return mapping.buf("JanetStop", cfg({"mapping", "stop"}), stop, {desc = "Stop the REPL"})
end
_2amodule_2a["on-filetype"] = on_filetype
local function on_exit()
return stop()
end
_2amodule_2a["on-exit"] = on_exit
return _2amodule_2a

0 comments on commit 5036ec7

Please sign in to comment.