有一天,我用Homebrew装置了一些软件——因为曾经是一个月前的事件了,所以曾经记不清是装置了什么。装置后并没有立刻呈现什么问题,只是又过了两天我重新启动电脑后,发现同样是由Homebrew装置的Emacs不由分说地无奈启动了。这下可麻烦了,毕竟我是org-mode的重度使用者,还须要偶然用SLIME写点Common Lisp的代码,而它们都运行在Emacs中。

直觉通知我,兴许重新安装一下Emacs,所有就能够恢复正常。重装了Emacs后,又遇到了别的问题——用BetterTouchTools在Touch Bar中增加的按钮,无奈在Emacs曾经启动的状况下,切换到它的窗口上。

非要说,问题其实也不大,毕竟很多时候是将MacBook Pro合上盖子当主机用的,Touch Bar在工作时的应用频率并不高。此外,糊Node.js等语言的代码时也用不到Emacs——还是VSCode更适合。

但这就是令人不爽,因而我决定要解决它——用Hammerspoon

Hammerspoon是什么?

Hammerspoon的官网很好地阐明了这款工具的定位和原理

This is a tool for powerful automation of OS X. At its core, Hammerspoon is just a bridge between the operating system and a Lua scripting engine. What gives Hammerspoon its power is a set of extensions that expose specific pieces of system functionality, to the user.
  1. 它运行在OS X上——当初应该叫macOS;
  2. 它是用来自动化操作的——就像零碎内置的Automator或第三方的Alfred Workflow那样;
  3. 它的原理是将操作系统的性能封装成了能够用Lua代码调用的模块;

例如上面的代码

hs.hotkey.bind({"cmd", "alt", "ctrl"}, "E", function()  hs.alert.show("Hello World!")end)

就能够让使用者在按下组合键⌘⌥⌃e的时候,在屏幕正中间显示Hello World!这段文本

为什么用Hammerspoon?

Hammerspoon正好能够解决我的问题,它的hs.window模块既能够让使用者遍历所有关上的窗口(用hs.window.allWindows函数),也能够聚焦到指定的窗口上(用focus办法)。有了它们,将Emacs调到最后面(front-most)来也就是瓜熟蒂落的事件了:

  1. 调用函数hs.window.allWindows函数,取得所有窗口的列表;
  2. 一一查看列表中的窗口对象,如果属于Emacs的,就调用窗口的办法focus,并跳出循环。

剩下的两个问题便是:

  1. Emacsbundle ID是什么;
  2. 如何晓得一个窗口对象的bundle ID

Emacs的bundle ID

Bundle ID能够在macOS中举世无双地标识一个利用。要想晓得Emacsbundle ID是什么,只须要关上文件/Applications/Emacs.app/Contents/Info.plist,看看其中键为CFBundleIdentifier的值即可。

➜  Contents grep -A 1 'CFBundleIdentifier' Info.plist    <key>CFBundleIdentifier</key>    <string>org.gnu.Emacs</string>➜  Contents

能够看到,Emacsbundle IDorg.gnu.Emacs

来点Lua代码吧

有了Emacsbundle ID,接下来就能够在Hammerspoon中定义快捷键了。因为最初会通过Touch Bar上的按钮来触发这组快捷键,简单点也不要紧,因而我间接沿用了Hammerspoon的入门指引中作为例子的⌘⌥⌃w

hs.hotkey.bind({"cmd", "alt", "ctrl"}, "W", function()end)

为了在一个循环中一一遍历窗口对象,将hs.window.allWindows的返回值保留到一个局部变量中

hs.hotkey.bind({"cmd", "alt", "ctrl"}, "W", function()      local windows = hs.window.allWindows()end)

照着简书上的这篇文章,依葫芦画瓢地用forpairs来遍历变量windows

hs.hotkey.bind({"cmd", "alt", "ctrl"}, "W", function()      local windows = hs.window.allWindows()      -- 在Lua中遍历表的办法:https://www.jianshu.com/p/de5a4b132918      for _, win in pairs(windows) do      endend)

窗口本身没有bundle ID,为此须要先获取窗口所属的利用。查看文档能够晓得,有一个application办法正是用来获取利用对象的

hs.hotkey.bind({"cmd", "alt", "ctrl"}, "W", function()      local windows = hs.window.allWindows()      -- 在Lua中遍历表的办法:https://www.jianshu.com/p/de5a4b132918      for _, win in pairs(windows) do         local app = win:application()      endend)

调用allWindows时应用的是英文句号(.),调用application则是用冒号(:),这正是Lua中调用函数与办法时语法上的差别。

再用利用的bundleID办法取得它的bundle ID

hs.hotkey.bind({"cmd", "alt", "ctrl"}, "W", function()      local windows = hs.window.allWindows()      -- 在Lua中遍历表的办法:https://www.jianshu.com/p/de5a4b132918      for _, win in pairs(windows) do         local app = win:application()         local bundleID = app:bundleID()      endend)

当初,只有变量bundleID等于Emacsbundle ID就能够聚焦到以后遍历的窗口上了

hs.hotkey.bind({"cmd", "alt", "ctrl"}, "W", function()      local windows = hs.window.allWindows()      -- 在Lua中遍历表的办法:https://www.jianshu.com/p/de5a4b132918      for _, win in pairs(windows) do         local app = win:application()         local bundleID = app:bundleID()         if bundleID == "org.gnu.Emacs" then            win:focus()         end      endend)

让Touch Bar按钮触发这所有

只须要在BetterTouchTools中配置一下即可

这个办法比此前唤起/Applications/Emacs.app的形式更好,因为它只依赖于Emacs逻辑上亘古不变的货色——bundle ID,而不依赖于其物理上的装置地位。

浏览原文