PopClip 插件

关于 PopClip,其实很早就安装了,但一直没有找到很好的运用场景。最近终于正式用上了,顺手写个 Extension 玩玩。

环境信息
PopClip: 2019.10(3032)
PopMaker: 0.3 (10)


顺便扯点别的。很久没更新博客了,实在是太忙。每天很多繁杂的事情需要处理,而且很多事情,转头就忘,所以我一直在和 TODO List 作斗争,尝试过相当多的 TODO 类 App,也自己写过。最近终于找到一个比较 smooth 流程。当然,我会单独拿一篇博客来写(年底了,冲一波量)。

如果有人点进这篇博客,但还不知道 PopClip 是什么,那么可以把它理解为是 macOS 文字选中增强的 App。功能看下面这张图应该就明白,对于选中的文字,可以加一些操作啥的(是的,比如加到 TODO List)。

官方有一些很不错的 Extension,可以下载尝试一下,文末也会介绍一些。

目标

先来明确一下最终要做个什么东西吧。痛点和平时的开发流程有关,比如有一个 Crash,需要通过这个 Crash 的链接,自动创建一个 Jira 单。所以列了一下步骤以及优先级(P0-P2,P0 最高):

  1. [P0] - 在选中链接的时候,弹出 PopClip,点击能请求到 Crash 的 Title、版本号等基本信息
  2. [P0] - 创建完后,返回 Jira URL
  3. [P0] - 支持用户填写自己的 Jira Token
  4. [P1] - 支持修改/新增自动获取到的信息。比如需要填写对应的 QA,或者 Bug 引入原因等
  5. [P1] - 支持不使用链接触发(因为不是每个 Bug,都有 Crash 链接。可能是一个普通的体验 Bug,或者 UI Bug,顺便能复用,支持快速提单)

配置介绍

PopMaker

这篇文章不是用来介绍插件使用的,而是插件开发。首先需要下载 PopMaker,见名知意,就不说了(看完文章,就明白这个 App 不是必须要下载的,它只是将输入生成了一个模板,既然是模板,复制粘贴也是一样的,所以熟悉以后就不用了)。

是的,界面很丑,但就像官网说的:It’s ugly right now, but it works,管它呢,好使就行。主要介绍一下各个参数吧:

名称 描述
Author 作者
Extension name 插件名称。这个名称是点击 PopClip 窗口,展现 Extension 列表的时候展示的(即配置页面),不会在选中 text 的时候展示(我们把这个操作叫做 pop 吧)
Description 描述
Menu title 这个就是 pop 展示的 Title 了,如果选择了 icon,那么 pop 的时候,会直接展示 icon。icon 最好是大于 256*256 的 PNG 图片,并且图像是黑色,背景色透明。最后再 pop 上看到的图片,会处理为白色;在配置页面,展示的是原图
Extension Type-Surround 环绕式。这种就是对选的 text 做处理。选择这个类型,会多出两个输入框:[Text before] [selection] [Text after]。讲真最开始我真的没看懂,上手试了一下,原来是指:
- [seletion]: 选中的文本
- [Text before]: 要在选中文本之前拼接的文字
- [Text after]: 和 before 对应,就是在之后拼接。
官网提到可以用来格式化 Markdown,比如要给某个 text 加粗。[before][after] 都填 ** 就可以了,这样 text 前后就加上了 `**
Extension Type-Search 链接式。直译是搜索,但其实他的功能其实是支持自定义链接,选中的参数会以 ${query} 传入。官网提到,这里的 URL 可以是 URL Scheme,所以如果知道某个 App 的 scheme,能直接用来操作 App。比如给印象笔记新增一个 Note(我用的 Bear,所以直接贴 Bear 的 scheme 吧)

最后就是保存和安装,保存每次会有一个「询问是否安装」的弹窗,可以关掉:

defaults write com.pilotmoon.popclip LoadUnsignedExtensions -bool YES

在 Debug 的时候,如果想要输出到控制台,可以执行:

defaults write com.pilotmoon.popclip EnableExtensionDebug -bool YES

点击安装以后,可以在 ~/Library/Application Support/PopClip/Extensions 中找到 xxx.popclipext 文件。这个文件是打包以后的,右键 Show Package Contents 能看到一些文件(比如 Config.plist),后面再提。

支持的事件

具体可以看 PopClip-Extensions,不得不说,这个地址藏得是真的深,至少在官网上一眼看不到,README 的内容比较干,可以学习一下。其中提到几种事件,这里事件意思是能通过 PopClip 触发的事件。

Service

很多 App 都支持一些 Service,比如 Finder 支持 Reveal in Finder,或者官方文档提到的 Make Sticky,可以将选中的文字添加到系统便利贴。Service 的配置体现在 Config.plistActions/Service 字段里。

如果想自己新增一个 Service 的 Action,比如选中路径,用 Finder 打开。那么需要先知道 Finder 都有哪些 Service 提供:

  • Service 存储在:~/Library/Caches/com.apple.nsservicescache.plist 中
  • 在一级目录里面的 ServicesCache 可以找到对应的 App,比如 Finder 叫 /System/Library/CoreServices/Finder.app
  • service_dictsNSMenuItem 的值就是 Finder 所支持的 Service。可以看到 Reveal in Finder 的 Service name 叫 Finder/Reveal。改改 Config.plist 就可以了

在 Service 列表中,还能看到一些 Workflow,或者自己没有装的 App。这些有可能是安装某类 App 的时候自动安装的。比如 iTerm2 会自动装 New iTerm2 Tab Here 的 Service。所以 Service 是可以自定义的,详见 Automator 文档,如果以后有机会,我另写一篇。

AppleScript/Shell Script

文档中是两个 Action,但我觉得这是一类:自定义脚本。他们区别在于…他们没啥区别,只是 Config.plist 中指定脚本的方式不同,但最后都是执行自定义的脚本(🌰 BBEditSay

// AppleScript:AppleScript File 后面放脚本名
<key>Actions</key>
<array>
<dict>
<key>AppleScript File</key>
<string>xxx.applescript</string>
</dict>
</array>

// Shell Script:除了脚本名以外,需要指定解释器路径
<key>Actions</key>
<array>
<dict>
<key>Script Interpreter</key>
<string>/usr/bin/ruby</string>
<key>Shell Script File</key>
<string>xxx.rb</string>
</dict>
</array>

URL

类似于之前提到 Search,触发一个 URL(🌰 GoogleTranslate)。

Keypress

触发物理按键。比如触发 Delete,物理按钮的映射可以看这个。目前想到关于这个 Action 的操作比较鸡肋,除了触发 Delete、CMD+C 这类的按键,应该还能触发某个 App 支持的快捷键(没有实操过),但都不是很好,希望各位可以分享一下。

Config.plist

Config.plist 是插件中的非常重要的文件,包含了所有的配置。配置项最好直接看文档,这里直说一些比较有意思的。

key value
Required Apps 仅在某些 App 中展示这个 pop
Regular Expression 仅在满足正则的条件下,展示这个 pop。比如给特定的链接加 xxx 功能
Stay Visible 不自动消失,而是在点击以后消失
Long Running 会展示一个 Loading,用于执行时间较长的脚本。比如网络请求这类的
Actions/Before Actions 中可以设置 Before/After 的 key,类似于钩子,会在执行 Actions 前后自动执行指定的操作
Options 提供给用户配置的窗口,后面会介绍到

文档的 Script Fields 部分约定了环境变量,比如选中的文本,会赋值给 POPCLIP_TEXT

开始写吧

再重新看一次需求,还不确定所有功能 PopClip 都支持,尽量实验。优先级由核心功能 → 自定义配置,逐级递减:

  1. [P0] - 在选中链接的时候,弹出 PopClip,点击能请求到 Crash 的 Title、版本号等基本信息
  2. [P0] - 创建完后,返回 Jira URL
  3. [P0] - 支持用户填写自己的 Jira Token
  4. [P1] - 支持修改/新增自动获取到的信息。比如需要填写对应的 QA,或者 Bug 引入原因等
  5. [P1] - 支持不使用链接触发(因为不是每个 Bug,都有 Crash 链接。可能是一个普通的体验 Bug,或者 UI Bug,顺便能复用,支持快速提单)

[P0] - 获取 Crash 基本信息并创建 Bug

创建 PopClip 插件前面说了很多,就不单独说了。我选择的是自定义脚本,语言是 Ruby。考虑到兼容性问题,选择 Ruby2.3 API,并且所有都用原生调用,避免依赖环境有问题。

当前插件目录结构与 Actions:

// - Config.plist
// - icon.png
// - script.rb

<key>Actions</key>
<array>
<dict>
<key>Script Interpreter</key>
<string>/usr/bin/ruby</string>
<key>Shell Script File</key>
<string>script.rb</string>
</dict>
</array>

由于涉及隐私,外部调用只有封装的方法,里面就一堆请求,不是重点:

#!/System/Library/Frameworks/Ruby.framework/Versions/Current/usr/bin/ruby

url = ENV['POPCLIP_TEXT']

begin
# 封装了数据请求、Bug 创建等逻辑
require_relative './utils'

# 解析 Crash URL,并请求 Crash 基本信息:堆栈、发生版本、处理人
slardar = SlardarIssue.new(url)
# 初始化 Jira 信息
rocket = Rocket.new(aid: slardar.aid, os: slardar.os, author: 'saitjr')
# 将 Crash 信息传入,创建一个 bug
bug_url = rocket.create_bug(version3: slardar.top_version, p: 0, title: slardar.title, desc: slardar.issue_url)
# 输出 Bug 创建成功后,返回的 URL
print bug_url
rescue => exception
# 输出任何异常
print exception
end

因为里面包含了多个请求,并且部分请求之间有先后顺序,耗时大概在 2s 左右。所以加了一个 Long Running 的参数:

<key>Long Running</key>
<true/>

[P0] 支持用户填写自己的 Jira Token

可以看到,在之前的脚本中,author 这个字段是写死的,这样就没办法共用,需要找一个地方配置自定义参数。对于这个需求来说,有两个输入框:邮箱 + Token。其实这类需求很常见,另一种表现形式是三方登录或鉴权,比如通过 Google 登录等。

这里会用到之前 Config.plist 结构略过的 Options 参数,结合需求来看下文档

Key Value
Option Identifier 唯一标识(注意小写)。可以理解为每个输入框的 key,比如这里叫 namejira。后续脚本中,可以用 POPCLIP_OPTION_NAME 读取输入框内容(AppleScript 为全小写)。
Option Type 输入类型。支持三种:string/boolean/multiple。分别对应:输入框/勾选框/下拉框
Option Label 描述。用于补充说明。比如「Token 可以在 xxx 获取」
Option Default Value 默认值(可选)。
Option Values 下拉框需要提供的选项。

除了 Options 的配置以外,发现很有必要介绍两个 undocument 的东西:

status code

文档在 Script Returning Result 中,简单介绍脚本的 Status Code 的含义(即 exit 0/1/2):

  • 0:没有错误
  • 1:通用错误码
  • 2:预置错误码。如果配置了 Options,这种错误码会重新执行一次 Options,也就是重新弹窗
  • 4:等待回调。三方登录这类操作,需要等待用户授权和三方平台回调。这种情况需要触发一个 Loading(和 Long Runing 的 Loading 不同,这个 Loading 会在 PopClip 的 StatusBar 上),等回调以后,再继续执行脚本(文档没有写这个错误码,可以参考 Trello 的 auth.py

Options After/Before

在 Trello 的 Config.plist 中,有两个文档中没有的参数:

<key>Option Available After Auth</key>
<true/>
<key>Option Available Before Auth</key>
<false/>

结合 Trello 安装后的操作流程,来解读一下这个配置(下面是 Trello 的配置,有删改):

// Trello 安装后:
// 1. exit 4 等待登录授权
// 2. 授权完后,弹出配置弹窗(即下面这个 board)
// 3. 授权和配置是有先后顺序的,而保证先后顺序的的 key 就是 Option Available After Auth
// 4. 置为 false 或移除这两个 key,则会登录和配置一起弹出

<key>Options</key>
<array>
<dict>
<key>Option Identifier</key>
<string>board</string>
<key>Option Available After Auth</key>
<true/>
<key>Option Available Before Auth</key>
<false/>
</dict>
</array>
<key>Options Script File</key>
<string>auth.py</string>
<key>Options Script Interpreter</key>
<string>/usr/bin/python</string>

左边:控制先后顺序;右边未控制

对于创建 Bug 这个需求来说,流程和解决方案为:

  1. 没有登录逻辑,只需要提醒用户配置。所以新增 Options 配置,包含两个输入框;
  2. 执行创建逻辑前,先验证是否有设置过 name 与 token,如果没设置,通过 exit 2,重新触发弹窗
  3. 如果验证通过,则直接用 name 和 token 创建 Bug

这里没有处理 name 和 token 合法性的校验,有必要可以加上。Options 和 Trello 的类似,就不贴代码了,参数校验为:

prefix = ENV['POPCLIP_OPTION_NAME']
token = ENV['POPCLIP_OPTION_TOKEN']

exit 2 if prefix.nil? || prefix.empty? || token.nil? || token.empty?

最后

最后还剩余两个 P1 的需求:

  1. 支持修改/新增自动获取到的信息:PopClip 在参数修改上不是很灵活,比如不能给人员名单,下拉筛选。最终决定在创建成功后,跳转至 Bug 详情。
  2. 支持不使用链接触发:用文字触发,只是少了解析 Crash 信息,基本流程一致

除此之外,还有选中特定链接的情况下,才展示插件等细节还没处理,再说吧。真的被 PopClip 坑好多,本来加上博客几个小时就能搞定的,结果写了大半天,先缓缓。

一些实用的插件分享,会不定期更新(也可能不会更新)。官方 Repo 里有各种插件源码,很多作业可以抄。