Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Access from different Lua process / Fennel REPL #2377

Open
martinklepsch opened this issue May 29, 2020 · 17 comments
Open

Access from different Lua process / Fennel REPL #2377

martinklepsch opened this issue May 29, 2020 · 17 comments

Comments

@martinklepsch
Copy link

I'd like to access Hammerspoon from a separate Lua process. I'm thinking that maybe configuring package.path similar to how it is configured for Hammerspoon itself but couldn't figure out how to do that.

Not sure if this is possible at all but I figured I might ask :)

Really having fun with Hammerspoon! 🙌

@martinklepsch
Copy link
Author

I just discovered hs.ipc. It seems that with that I can call functions that are defined in Hammerspoon(?). What I'm looking for is more of a way to pipe Lua code into a Lua process.

End goal here is having a Fennel REPL into the Hammerspoon environment.

@latenitefilms
Copy link
Contributor

Yes, you could trigger Hammerspoon code via hs.ipc command line interface.

However, it could be an interesting experiment to try and embed Fennel into Hammerspoon. I feel like this is something @asmagill might have an opinion on?

@martinklepsch
Copy link
Author

Just to clarify, this is less about writing configuration files in Fennel and more about being able to directly interact with Hammerspoon via an interactive REPL session (potentially running in your editor, see conjure).

To use Fennel files to configure Hammerspoon you can already use the following:

-- cat init.lua
local fennel = require "fennel"

-- allow requiring of fennel modules
table.insert(package.loaders or package.searchers, fennel.searcher)

fennel.dofile("init.fnl", { allowedGlobals = false })
;; cat init.fnl
(global logger (hs.logger.new "fennel" "debug"))
(logger.i "fennel loaded")

@asmagill
Copy link
Member

The Hammerspoon modules themselves can only be loaded from within Hammerspoon because they rely on the common macOS Frameworks (Foundation, CoreGraphics, etc.) being linked against the application, as well as the Hammerspoon specific framework, LuaSkin. The only lua process which can successfully meet those requirements is the lua process embedded within Hammerspoon itself.

As you identified above, it looks like you should be able to evaluate/execute fennel files exactly as you describe above (I haven't tried, just read through some of the files on the fennel repo, but sounds like you have). The fennel.eval function looks interesting for passing strings created in Hammerspoon to be evaluated by the fennel library, but again, this is just from a quick skimming and not actually trying anything out yet.

And for piping in code from the outside, the only real mechanism to do so is hs.ipc. If you use hs.ipc.cliInstall([path]), it will install hs and a man page for it into path/bin and path/share/man/man1/ (by default path is /usr/local if you don't specify it when invoking hs.ipc.cliInstall) which can be used from shell scripts (and environments that can execute shell commands) to execute commands within Hammerspoon... hs -c "..." will execute the command(s) "..." and return any output as if you had typed it into the Hammerspoon console.

(hs is a thin wrapper around IPC calls that interacts with hs.ipc)

There is an undocumented variable hs._consoleInputPreparser which can be set to a function that should accept a string argument and return a string argument -- this accepts whatever you have typed in the Hamemrspoon console (or from the hs shell command) as its input string and allows you to modify it before it's actually sent to the parser within Hammerspoon that eventually evaluates it with lua_pcall. I use it in https://github.com/asmagill/hammerspoon-config-take2/blob/master/utils/_actions/preParsers.lua to perform some munging to allow more flexibility with the internal help system and a few history commands I've also included in my config. I mention this (and mentioned fennel.eval above) because it might be possible to do something that allowed entering fennel code into the Hammerspoon console, but this is pure conjecture at the moment.

Sorry I don't have any better ideas at the moment... this looks interesting, and I kind of hope I get a chance to play with it myself at some point, but I won't have any time to mess with it myself or see if we can add an easier way to interface with fennel for the next week or so.

@martinklepsch
Copy link
Author

martinklepsch commented May 30, 2020

It seems like one way to do this would be starting a networked REPL inside init.lua using something like jeejah. I briefly tried following the instructions but it seems that LuaSocket isn't available:

local jeejah = require("jeejah")
local coro = jeejah.start(12345, {debug=true, sandbox={x=12}})
*** ERROR: ...app/Contents/Resources/extensions/hs/_coresetup/init.lua:597: module 'socket' not found:
	no field package.preload['socket']

EDIT resolved, see #2377 (comment).

@martinklepsch martinklepsch changed the title Access from different Lua process Access from different Lua process / Fennel REPL May 30, 2020
@latenitefilms
Copy link
Contributor

Try installing LuaSocket via luarocks:

luarocks install --local luasocket

@martinklepsch
Copy link
Author

martinklepsch commented May 30, 2020

@latenitefilms thanks for the swift response, I just tried that but it still can't find socket. I'm thinking that something might need extra setup for accessing luarocks stuff, working through some of the comments in #363 to figure out what.

EDIT 2.20pm It looks like I was missing require("luarocks.loader"), full working setup:

brew install lua # will install lua5.3
brew install luarocks 
luarocks install xxxxx # will install things into /usr/local/share/lua/5.3/luarocks
ln -s /usr/local/share/lua/5.3/luarocks ~/.hammerspoon/
require("luarocks.loader")
http = require("socket.http")
print(http.request("http://www.cs.princeton.edu/~diego/professional/luasocket"))

Maybe it's useful to edit #363 (comment) to include a note on requiring luarocks.loader.

EDIT 2.00pm This seemed promising but didn't end up working (same error): #363 (comment)

2020-05-30 14:08:02: *** ERROR: ...app/Contents/Resources/extensions/hs/_coresetup/init.lua:597: module 'socket' not found:
	no field package.preload['socket']
	no file '/Users/martinklepsch/.hammerspoon/socket.lua'
	no file '/Users/martinklepsch/.hammerspoon/socket/init.lua'
	no file '/Users/martinklepsch/.hammerspoon/Spoons/socket.spoon/init.lua'
	no file '/usr/local/share/lua/5.3/socket.lua'
	no file '/usr/local/share/lua/5.3/socket/init.lua'
	no file '/usr/local/lib/lua/5.3/socket.lua'
	no file '/usr/local/lib/lua/5.3/socket/init.lua'
	no file './socket.lua'
	no file './socket/init.lua'
	no file '/Applications/Hammerspoon.app/Contents/Resources/extensions/socket.lua'
	no file '/Applications/Hammerspoon.app/Contents/Resources/extensions/socket/init.lua'
	no file '/Users/martinklepsch/.hammerspoon/socket.so'
	no file '/usr/local/lib/lua/5.3/socket.so'
	no file '/usr/local/lib/lua/5.3/loadall.so'
	no file './socket.so'
	no file '/Applications/Hammerspoon.app/Contents/Resources/extensions/socket.so'
stack traceback:
	[C]: in function 'rawrequire'
	...app/Contents/Resources/extensions/hs/_coresetup/init.lua:597: in function 'require'
	/Users/martinklepsch/.hammerspoon/jeejah.lua:1: in main chunk
	[C]: in function 'rawrequire'
	...app/Contents/Resources/extensions/hs/_coresetup/init.lua:597: in function 'require'
	/Users/martinklepsch/.hammerspoon/init.lua:4: in main chunk
	[C]: in function 'xpcall'
	...app/Contents/Resources/extensions/hs/_coresetup/init.lua:648: in function 'hs._coresetup.setup'
	(...tail calls...)

@martinklepsch
Copy link
Author

Also found this issue where some folks try to set up a Fennel REPL into Hammerspoon agzam/spacehammer#33

@asmagill
Copy link
Member

asmagill commented May 30, 2020

Using luarocks with --local installs into ~/.luarocks, so you need to update your path and cpath in Hammerspoon:

package.path = package.path .. ";" .. os.getenv("HOME") .. "/.luarocks/share/lua/5.3/?.lua;" .. os.getenv("HOME") .. "/.luarocks/share/lua/5.3/?/init.lua"
package.cpath = package.cpath .. ";" .. os.getenv("HOME") .. "/.luarocks/lib/lua/5.3/?.so"

After doing this, I was able to do require("socket") with no error (and I don't have the "luarocks.loader" -- never heard of it, actually).

edit - forgot to prepend existing path the first time!

@asmagill
Copy link
Member

Ah, I see, it's part of the luarocks installation itself... well, never needed it before, but I guess good to know it exists if I do start using rocks for much and have versioning issues to worry about.

@asmagill
Copy link
Member

oops... should be:

package.path = package.path .. ";" .. os.getenv("HOME") .. "/.luarocks/share/lua/5.3/?.lua;" .. os.getenv("HOME") .. "/.luarocks/share/lua/5.3/?/init.lua"
package.cpath = package.cpath .. ";" .. os.getenv("HOME") .. "/.luarocks/lib/lua/5.3/?.so"

@martinklepsch
Copy link
Author

So I made a bit more progress here. I cloned the jeejah repo into my .hammerspoon dir and am trying to require it this way:

require("luarocks.loader")
local search_parent = string.format("%s?.lua;%s", "/Users/martinklepsch/etc/hammerspoon/jeejah/jeejah/", package.path)
local jeejah = dofile(package.searchpath("jeejah", search_parent))

The problem is that I'm still getting an error:

module 'jeejah.fenneleval' not found:No LuaRocks module found for jeejah.fenneleval

The jeejah.fenneleval file is in a subdirectory of the jeejah repo but I'm not sure how to tell Hammerspoon where to find that file. I experimented a bit with search_parent as you can see in the snippet above but haven't been able to figure it out.

I'm pretty unfamiliar with Lua's package loading but it seems to be that usually files loaded via require are searched in package path as {name}.lua or {name}/init.lua. For the jeejah.fenneleval piece above that doesn't seem like a workable approach though?

Next I'll try inlining some of this code (i.e. cheat around actually loading it from separate files).

@dsedivec
Copy link

dsedivec commented Sep 6, 2020

I have successfully connected to Hammerspoon via Jeejah, with @technomancy's help, though there is a caveat at the end. I haven't tried bootstrapping this from the ground up, but I think the following are the essential steps.

First, you need luasocket installed. I did this with the help of MacPorts and LuaRocks:

  1. port install lua luarocks
  2. luarocks install --local luasocket
  3. Tell Hammerspoon how to find LuaRocks and its loader by putting this in init.lua:
    package.path = package.path:gsub('/usr/local/share/lua/5.3',
                                     '/opt/local/share/lua/5.3')
    require 'luarocks.loader'

The rewriting of package.path is (I think) due to the fact that the default package.path refers to /usr/local, but MacPorts installs things in /opt/local. If you installed LuaRocks via Homebrew, you might not need to change package.path at all.

Once that's all done, you should be able to require socket from the Hammerspoon REPL without error.

Next I cloned Jeejah into ~/.hammerspoon/jeejah and appended the following to init.lua:

package.path = package.path .. ';jeejah/?.lua'
-- Fennel expects the "arg" table to exist, which (I think) is where
-- argv is stored in Lua.
arg = {}
fennel = require 'fennel'
table.insert(package.loaders or package.searchers, fennel.searcher)
require 'init_fennel'

I will note that I believe technomancy recently fixed a bug or two in Jeejah that was causing it to break, so if you have a local copy or a clone or something, update to the latest from the Git repo.

Next I put the following into ~/.hammerspoon/init_fennel.fnl:

(local jeejah (require "jeejah"))
(global jeejah-coro (jeejah.start))
(global jeejah-coro-freq 0.01)
(fn jeejah-spin []
  (coroutine.resume jeejah-coro)
  (when (not= (coroutine.status jeejah-coro)
              "dead")
    (hs.timer.doAfter jeejah-coro-freq jeejah-spin)))
(global jeejah-timer (hs.timer.doAfter jeejah-coro-freq jeejah-spin))

Once all that is done, reload your Hammerspoon config and you should see something like this in the Hammerspoon console:

2020-09-06 01:06:38: Server started on port 7888...

🎉

I was able to successfully connect from Emacs with Monroe on port 7888.

Here's the Caveat: with the current release of Hammerspoon, I'm pretty sure this is still broken because coroutines are broken. If I do (global foo 42) from Jeejah, I can't see it in the Hammerspoon console, but if I then do foo = 999 from the Hammerspoon console, I can see that from Jeejah.

And if I do (hs.alert.show "hi") from Jeejah, Hammerspoon just crashes instantly.

I am hoping that #2306, which was closed this past April, means this will all be working in the next release of Hammerspoon. Building from source didn't look particularly easy this evening, so I'll probably try and retest after the next release.

@latenitefilms
Copy link
Contributor

FYI: You can also use the latest CommandPost beta, which has the coroutine support merged in:

https://github.com/CommandPost/CommandPost/releases

@asmagill
Copy link
Member

asmagill commented Sep 7, 2020

Just a heads up -- the next major release of Hamemrspoon will be using Lua 5.4 internally... not sure if that will be an issue or not. If the socket library contains any compiled C code, it probably will, though. Last I checked (which was at least a couple of weeks ago, I think) Homebrew didn't have a lua5.4 entry yet, so I installed it by hand myself, but I don't know about Macports.

@dsedivec
Copy link

dsedivec commented Sep 20, 2020

Just a heads up -- the next major release of Hamemrspoon will be using Lua 5.4 internally... not sure if that will be an issue or not. If the socket library contains any compiled C code, it probably will, though. Last I checked (which was at least a couple of weeks ago, I think) Homebrew didn't have a lua5.4 entry yet, so I installed it by hand myself, but I don't know about Macports.

Thanks, this was important. MacPorts has an open PR to upgrade to Lua 5.4, so in the interim I compiled my own.

I now have a Fennel REPL working under the latest (just released) Hammerspoon. New instructions are probably something like:

  1. Use this script I made to install Lua 5.4 and luasocket into ~/.hammerspoon/lua. Certainly this requires you already have development tools installed, plus whatever else Lua might require to build, which I didn't look into—it worked for me with what I already had installed. Note that I'm not using LuaRocks this time around.

  2. cd ~/.hammerspoon/lua/src && git clone https://gitlab.com/technomancy/jeejah/

  3. In ~/.hammerspoon/init.lua:

     ```lua
     HS_LUA_ROOT = os.getenv("HOME") .. "/.hammerspoon/lua"
     package.path = package.path:gsub('/usr/local', HS_LUA_ROOT)
     package.cpath = package.cpath:gsub('/usr/local', HS_LUA_ROOT)
     -- Fennel expects the "arg" table to exist, which (I think) is where
     -- argv is stored in Lua.
     arg = {}
     -- Jeejah includes fennel.lua for us.
     package.path = package.path .. ";" .. HS_LUA_ROOT .. "/src/jeejah/?.lua"
     fennel = require 'fennel'
     table.insert(package.loaders or package.searchers, fennel.searcher)
     require 'init_fennel'
     ```
    
  4. In ~/.hammerspoon/init_fennel.fnl:

     ```
     (local jeejah (require "jeejah"))
     (global jeejah-coro (jeejah.start))
     (global jeejah-coro-freq 0.01)
     (fn jeejah-spin []
       (coroutine.resume jeejah-coro)
       (when (not= (coroutine.status jeejah-coro)
                   "dead")
         (hs.timer.doAfter jeejah-coro-freq jeejah-spin)))
     (global jeejah-timer (hs.timer.doAfter jeejah-coro-freq jeejah-spin))
     ```
    

After that, reload your Hammerspoon config, and you should see the "Server started on port 7888..." message. Now you can connect with e.g. Monroe and indeed I can now do things like (hs.alert "hi") and it works exactly as expected!

This is really great, big thank you to all the people who volunteer your time to make Hammerspoon work!

EDIT: Sorry, left out some bits in the original comment, now hopefully correct.

@jaidetree
Copy link
Contributor

Drafted a feature for consideration in spacehammer agzam/spacehammer#108 (comment) but realized it could be combined with other tools to work like a repl system

2021-08-29 14 36 58

The good news is that it's editor agnostic in this setup. I have hs config that's reading selected text, and sending it to a tmux session via task commands. The bad news is it depends on tmux, nodemon, and shevek.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants