Lua 快速入门并完成一个基于Hammerspoon的备忘录项目
环境说明
系统版本:macOS 10.15.7
Hammerspoon版本:0.9.81
Lua版本:5.4.0
需求整理
需求由来
用了一段时间的macOS后发现。有很多终端的指令需要记。常用的指令一般都不会忘。但是,一些不常用的指令,例如scp指令。所以就想要一个功能帮我记录这些常用指令的。后来引申到了想要记录一些常用的注释代码片段。
当然,我是知道这些备忘完全可以随便记录在macOS提供的备忘录中或者是写在博客里,但是,操作了一段时间后就发现了一个很大的弊端:记录、查找、修改备忘都要先打开某个应用后才能操作,往往要经过很多步骤。同时我在用的另外一个macOS神级应用Alfred里也有一个片段功能。似乎也能满足我的需求。但是,我不满意的地方是新增修改删除备忘的时候,不是很方便。需要进入设置界面才能操作。第二个问题是多台mac数据同步的问题,官方推荐Dropbox,网络问题就不说了,真心不想装多一个没用的软件。理论上是能通过git来同步配置文件的,不过不想试了,就想自己手动写一个:-)。控制权在自己手上,想怎么改就怎么改。
需求整理
- 快速的操作备忘,最好使用快捷键,不要太多的鼠标操作
- 能够通过快捷键添加备忘
- 能够使用快捷键调出备忘列表
- 能够快速的应用选中的备忘内容
- 能够编辑备忘内容
- 能够删除备忘内容
- 能够本地保存备忘数据
项目地址
https://github.com/sugood/hammerspoon
目录结构
│ history.json
│ init.lua
│ README.md
│ README_zh-CN.md
└─modules
│ hotkey.lua
——│ launcher.lua
——│ reload.lua
——│ snippet.lua
——│ system.lua
——│ windows.lua
备忘录实现步骤
1、导入脚本文件
在init.lua文件中插入以下代码
require "modules/snippet"
2、绑定添加备忘和显示备忘列表的快捷键
-- 添加片段(按下快捷键时做一个复制操作,并记录复制的内容到片段列表中)
hs.hotkey.bind({"ctrl", "cmd"}, "A", function ()
-- TODO
end)
-- 选取片段内容(按下快捷键时显示片段列表,点击选中的快捷键将自动粘贴)
hs.hotkey.bind({ "ctrl", "cmd" }, "V", function ()
--TODO
end)
3、添加备忘
- 发送复制粘贴的快捷键将选中的内容复制到剪贴板中(官方文档没有找到直接获取电脑上选中内容的api)
hs.eventtap.keyStroke({ "cmd" }, "C")
- 将剪贴板上的最后一条文本插入备忘录列表中。并保存到本地文件history.json中,实际代码中还需要做重复判断还有排序
-- 将备忘插入备忘录列表中
table.insert(history, 1, item)
-- 将备忘列表保存到json文件中
hs.json.write(history,historyPath, true, true)
4、显示备忘列表
- 创建一个选择器,并绑定选中完成的处理方法。这个处理方法主要作用是,将我们选中的备忘录内容插入剪贴板中。然后发送粘贴的消息,将该内容粘贴出去。
-- 创建一个选择器
hs.chooser.new(completionFn)
:choices(history)
:rightClickCallback(menuFn)
:searchSubText(true)
:show()
-- 粘贴选中的片段
local completionFn = function(result)
if result then
hs.pasteboard.setContents(result.text)
hs.eventtap.keyStroke({ "cmd" }, "V")
print("keywords:"..result.text)
end
end
- 监听鼠标右键事件。判断到有右键的操作时,将列表的index传递到绑定的方法中。然后就能处理。删除还有修改的操作了。
-- 右键弹出菜单
local menuFn = function(index)
menubar = hs.menubar.new(false)
menubar:setTitle("Hidden Menu")
menubar:setMenu( {
{ title = "菜单", fn = function() print("you clicked my menu item!") end },
{ title = "-" },
{ title = "修改片段内容", fn = function()
result,text = hs.dialog.textPrompt("修改片段内容", "请输入新的内容", history[index].text, "确定", "取消")
if result == "确定" then
modifyHistory(text,history[index].subText,true,index)
end
end },
{ title = "修改片段说明", fn = function()
result,subText = hs.dialog.textPrompt("修改片段说明", "请输入新的说明", history[index].subText, "确定", "取消")
if result == "确定" then
modifyHistory(history[index].text,subText,false,index)
end
end },
{ title = "-" },
{ title = "删除当前片段", fn = function()
if hs.dialog.blockAlert("确定删除以下片段?",history[index].text,"确定","取消","informational") == "确定" then
table.remove(history,index)
hs.json.write(history,historyPath, true, true)
hs.alert.show("成功删除片段")
end
end },
})
menubar:popupMenu(hs.mouse.getAbsolutePosition(), true)
end
5、最后记得 Reload Config
6、如何同步不同电脑上的配置
使用github或者是gitee。具体的操作就不详细说了。
快速入门Hammerspoon脚本开发
我把快速入门放最后,主要的原因就是,编程学习主要还是要多写多练。基础语法这些只要知道个大概就可以了。特别是脚本语言,本身设计的时候都已经简化了很多语法,只要有一定的编程基础,基本拿来就能用了。不懂的地方再查资料就好。当然,如果你要做个大项目,请一定要打好基础。咱们快速入门就直接开干就好。
因为Hammerspoon 使用Lua脚本。所以,我们一部分是介绍Lua脚本的一些语法,一部分是介绍Hammerspoon API的。
参考文档
Hammerspoon API文档:https://www.hammerspoon.org/docs/index.html
Lua 参考文档:http://www.lua.org/manual/5.4/
如果使用的Hammerspoon和我的版本不同,可能Lua的版本也不同。那你可能要找对应的Lua版本的文档来看。在脚本中通过以下指令可以打印出Lua版本
print(_VERSION)
Lua语法说明
基础语法大家可以看文档,这里只说一些文档没有写的,或者是我觉得比较实用的。
Lua是一种动态类型语言,因此语言中没有类型的定义,不需要声明变量类型,每个变量自己保存了类型。有8种基本类型:nil、布尔值(boolean)、数字体(number)、字符串型(string)、用户自定义类型(userdata)、函数(function)、线程(thread)和表(table)。
print(type(nil)) -- 输出 nil
print(type(99.7+12*9)) -- 输出 number
print(type(true)) -- 输出 boolean
print(type("Hello Wikipedia")) -- 输出 string
print(type(print)) -- 输出 function
print(type{1, 2, test = "test"}) -- 输出 table
我们用的比较多的类型是 nil 、 string 、 table
- nil 和java中的null差不多的概念。在使用一些变量前需要是要做非空判断,要不会报错
if str == nil then
print("字符串为nil")
else
print("字符串不为nil")
end
- string 字符串类型。
-- 字符串拼接,使用..
str = "Hello".." World"
print(str) -- 输出的结果就是 Hello World
-- 查看字符串长度使用 #
str = "Hello World"
print(#str) --输出11
--字符串判空,所谓的空字符串一般是指字符串是nil或者字符串没有内容,所以我们可以封装成一个方法
local function isEmpty(s)
return s == nil or s == ''
end
if isEmpty(s) then
print("字符串为空")
end
- table 就是表,其实是一个"关联数组"(associative arrays)
我们说下多维表的操作,包含增删改查和排序。表的格式如下
local history = {
{
["text"] = "First Choice",
["subText"] = "This is the subtext of the first choice"
},
{ ["text"] = "Second Choice",
["subText"] = "This is the subtext of the second choice"
},
{ ["text"] = "three Choice",
["subText"] = "This is the subtext of the three choice"
},
}
--插入一条数据
local item = {}
item.text = "Fourth Choice"
item.subText = "This is the subtext of the fourth choice"
table.insert(history, 1, item)
--删除第一条数据,table索引是从1开始的
table.remove(history,1)
--修改第一条数据
history[index].text = "First Choice change"
history[index].subText = "This is the subtext of the first choice change"
--查看第一条的数据
print("text:"..history[1].text)
print("subText:"..history[1].subText)
-- 排序,手动选择一个子项来排序,这里选择subText
table.sort(history,function(a,b) return a.subText<b.subText end )
--查看table的长度,也使用#这个符号
print(#history)
- function 函数的使用
跟其他编程语言的也差不多,可以传入参数,能返回值。就是调用函数前必须先定义好函数,函数体要写在调用代码的上面。
Hammerspoon Api说明
- 快捷键绑定并打开应用
hs.hotkey.bind({ "ctrl", "shift" }, "T", function ()
hs.application.launchOrFocus('Terminal')
end)
- 触发键盘事件
-- 触发复制事件
hs.eventtap.keyStroke({ "cmd" }, "C")
-- 触发粘贴事件
hs.eventtap.keyStroke({ "cmd" }, "V")
- 本地json文件操作
local historyPath= "~/.hammerspoon/history.json"
-- 读取本地文件
history = hs.json.read(historyPath)
-- 写入本地文件
hs.json.write(history,historyPath, true, true)
- 弹出菜单栏
-- 最好配合rightClickCallback 监听右键,鼠标右键时才弹出菜单
menubar = hs.menubar.new(false)
menubar:setTitle("Hidden Menu")
menubar:setMenu( {
{ title = "菜单", fn = function() print("you clicked my menu item!") end },
{ title = "-" },
menubar:popupMenu(hs.mouse.getAbsolutePosition(), true)
总结
写这篇文章就是自己做个总结。同时推荐下Hammerspoon,真的很好用:-)