让我们开始写一个非常简单的而又实用的插件吧。文本计数插件:统计当前缓冲里有多少文字,并用一个小窗口把结果显示出来。
创建插件最简单的方法是用Atom内置的插件包生成器( package generator)。 你可能已经猜到了, package generator 自己也是一个插件包。(详情参见:package-generator)
你可以在command palette里搜索 "Generate Package"来运行它。这时会弹出一个对话框,让你输入插件的名字,建议输入: your-name-word-count
. Atom 会创建一个目录并插入默认的插件代码。默认位置是:%USERPROFILE%\.atom\packages
,这样重启Atom时,就会自动加载这个插件了。
注意: 如果你的插件包没有被加载。那很可能是因为有一个相同名称的插件已经被上传到了atom.io 。所以建议大家建立插件时,加上自己名字的缩写或者公司缩写,来保证插件名称的唯一性。
如上图, Atom 已经创建了插件包的基础代码。下面我们来介绍插件包的代码是怎样构成的:
最基础的路径结构如下:
my-package/
├─ grammars/
├─ keymaps/
├─ lib/
├─ menus/
├─ spec/
├─ snippets/
├─ styles/
├─ index.coffee
└─ package.json
不是每个插件都需要这些文件,自动创建的代码里,也没有snippets
和 grammars
目录。
package.json
类似于 Node modules, Atom 插件包的根目录必须有一个 package.json
文件。这里包含了插件包的基础信息,比如 "main" 模块的路径,依赖库, 和指定的资源加载顺序。
下面介绍 package.json
文件中各个参数的意义:
main
: CoffeeScript(JavaScript)代码文件的路径,如果不填写此项目, Atom 会自动寻找 index.coffee
或 index.js。
styles
: 一个字符串数组,说明Styles文件的加载顺序,如果不填写,Atom会自动在styles目录下按文件名字母排序的方式去加载样式文件。注意,样式支持css和less文件。keymaps
: 一个字符串数组,说明keymaps文件的加载顺序,如果不填,则Atom自动在keymaps
目录下按字母排序的方式去加载。menus
: 一个字符串数组,说明memus配置文件在哪里加载。如果不填, 则Atom自动在menus
目录下按字母排序的方式加载。snippets
: 一个字符串数组,说明插件中使用的snippets从哪里加载。如果不填,则Atom自动在 snippets
目录下按字母排序的方式去加载。activationCommands
: 一个对象, 说明哪个命令来激活你的插件。如果不填写, 则Atom会在插件激活时,自动调用 activate()
函数。activationHooks
: 一个字符串数组,说明使用哪种语法来解释脚本。一般来说,我们填写: language-package-name:grammar-used
(e.g.,language-javascript:grammar-used
) 下面是一个 package.json
的例子:
{
"name": "wordcount",
"main": "./lib/wordcount",
"version": "0.0.0",
"description": "A short description of your package",
"activationCommands": {
"atom-workspace": "wordcount:toggle"
},
"repository": "https://github.com/atom/your-name-word-count",
"license": "MIT",
"engines": {
"atom": ">=1.0.0 <2.0.0"
},
"dependencies": {
}
}
如果使用了 activationHooks, 代码是:
{
"name": "wordcount",
"main": "./lib/wordcount",
"version": "0.0.0",
"description": "A short description of your package",
"activationHooks": ["language-javascript:grammar-used", "language-coffee-script:grammar-used"],
"repository": "https://github.com/atom/your-name-word-count",
"license": "MIT",
"engines": {
"atom": ">=1.0.0 <2.0.0"
},
"dependencies": {
}
}
首先,要填写这些基本信息。
Warning: 切记要更新 repository URL. 否则你的更改将无法分享给他人。
首先你的插件必须包含一个[top-level]模块, 在 package.json
文件里的main字段下声明它。对于我们这个例子来说,[top-level]文件是 lib/wordcount.coffee
. 你编写的源代码应该放在 lib
目录下,并且用[top-level]文件中required它。 如果main
没写,则Atom自动选择 index.coffee
或 index.js
作为[top-level]文件。
插件的[top-level] 模块是一个单例类。生命周期由 Atom来管理。 不论你创建多少Dom或者View, 它们全部由[top-level]来管理。
插件的[top-level] 模块可以实现下列的基本接口:
activate(state)
: 插件激活时被调用。如果你实现了 serialize()
接口,它会传递窗口上次的state. 一般用这个接口来初始化插件。initialize(state)
: (Atom 1.14以后的版本支持) 与activate()
功能类似,不过被调用的更早,可以说在所有操作之前被调用。 如果你想等环境运行完毕了再初始化,请用 activate()
。如果需要在画面构造前执行什么,请用initialize()
。serialize()
: 在窗口被关闭的时候,允许你返回 JSON 序列,来保存当前的状态。你保存的信息,可以传递给activate()
接口,来在下次启动时恢复你的窗口状态。deactivate()
: 窗口关闭时会调用这个接口,如果你的插件正在使用某些资源,或关联着某些文件,请在这里释放他们。 之前说过,插件的样式应该放在 styles
目录里。 支持 CSS 或 Less, 推荐使用Less。
理想的来说, 你不需要这种方式来做样式。Atom提供了基础的UI样式。 打开 command palette Ctrl+Shift+P + Ctrl+Shift+G来查找Atom默认提供的插件UI样式吧。
如果你需要特别的UI样式,建议只保留结构样式。如果你要使用特别的颜色、字体等,建议使用Atom提供的主题样式。参见 ui-variables.less.
插件可以绑定多个快捷键, 举例参见 keymaps/wordcount.cson
文件:
'atom-workspace':
'ctrl-alt-o': 'wordcount:toggle'
这将快捷键 Alt+Ctrl+O, 设置为运行 your-name-word-count:toggle
命令。创建插件时,Atom会自动分配一个快捷键给它,当然你也可以自行定义。
快捷键配置文件在 keymaps
子路径中。默认的所有快捷键配置文件按字母排序来加载。如果需要特别的加载顺序,请在 package.json
文件中配置。
快捷键的启动,也受到当前Dom元素(当前正在操作的View)是哪个的影响。在本例中 your-name-word-count:toggle
命令是在atom-workspace
元素上按下Alt+Ctrl+O 时会被执行。因为 atom-workspace
是整个 Atom UI的上级。这意味着,任何时候按下Alt+Ctrl+O 都会被执行。
如果想更深入的了解快捷键有关的内容,请参见 Keymaps in Depth.
菜单在 menus
子目录下。它定义了右键弹出菜单和工具栏菜单。
菜单默认按字母排序加载。 你可以在package.json
中编写自定义的加载顺序。
如果你的插件不是对特定元素使用的,建议你在Packages菜单下创建一个application菜单项。 在menus/your-name-word-count.cson
文件中,我们能看到如下代码片段:
'menu': [
{
'label': 'Packages'
'submenu': [
'label': 'Word Count'
'submenu': [
{
'label': 'Toggle'
'command': 'your-name-word-count:toggle'
}
]
]
}
]
上述代码在Packages菜单下,添加了WorkCount子项,并在其中添加了Toggle项目。
如果选择这个选项,会执行 your-name-word-count:toggle
命令。
菜单项会自动和其他菜单项合并,他们按照加载的顺序来显示。
建议为插件制作一些环境菜单。 在 menus/your-name-word-count.cson
文件中,你会看到一些默认的代码片段。
'context-menu':
'atom-text-editor': [
{
'label': 'Toggle Word Count'
'command': 'your-name-word-count:toggle'
}
]
在编辑页面点击右键,我们会看到这个菜单项。
点击它,会执行 your-name-word-count:toggle
方法。
环境菜单也可以添加子项目, 如下:
'context-menu':
'atom-workspace': [
{
label: 'Text'
submenu: [
{label: 'Inspect Element', command: 'core:inspect'}
{type: 'separator'}
{label: 'Selector All', command: 'core:select-all'}
{type: 'separator'}
{label: 'Deleted Selected Text', command: 'core:delete'}
]
}
]
在我们之前创建的插件框架基础上,运行 your-name-word-count:toggle
我们会看到一个弹窗: "The Wordcount package is Alive! It's ALIVE!".
看一下 lib
目录。
在 lib
目录下,现在有两个文件。其中 (lib/your-name-word-count.coffee
)是主文件, 在 package.json
文件中我们指定它为主文件。它负责整个插件的逻辑。
第二要注意的是 View class文件lib/your-name-word-count-view.coffee
, 这里主要是插件的UI代码
module.exports =
class YourNameWordCountView
constructor: (serializedState) ->
# Create root element
@element = document.createElement('div')
@element.classList.add('your-name-word-count')
# Create message element
message = document.createElement('div')
message.textContent = "The Your Name Word Count package is Alive! It's ALIVE!"
message.classList.add('message')
@element.appendChild(message)
# Returns an object that can be retrieved when package is activated
serialize: ->
# Tear down any state and detach
destroy: ->
@element.remove()
getElement: ->
@element
代码的具体含义这里不在赘述。
这里重点要注意的是 DOM methods: createElement()
和appendChild()
.两个函数的用法。
第二个文件有两个主入口,它们是在 package.json
文件里配置的.文件代码如下:
YourNameWordCountView = require './your-name-word-count-view'
{CompositeDisposable} = require 'atom'
module.exports = YourNameWordCount =
yourNameWordCountView: null
modalPanel: null
subscriptions: null
activate: (state) ->
@yourNameWordCountView = new YourNameWordCountView(state.yourNameWordCountViewState)
@modalPanel = atom.workspace.addModalPanel(item: @yourNameWordCountView.getElement(), visible: false)
# Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
@subscriptions = new CompositeDisposable
# Register command that toggles this view
@subscriptions.add atom.commands.add 'atom-workspace',
'your-name-word-count:toggle': => @toggle()
deactivate: ->
@modalPanel.destroy()
@subscriptions.dispose()
@wordcountView.destroy()
serialize: ->
yourNameWordCountViewState: @yourNameWordCountView.serialize()
toggle: ->
console.log 'YourNameWordCount was toggled!'
if @modalPanel.isVisible()
@modalPanel.hide()
else
@modalPanel.show()
首先,我们看到这里定义了4个方法,其中只有 activate
. 是必要的。 deactivate
是 serialize
Atom推荐但可选的。 toggle
方法不是由Atom调用的,我们必须手动或者配置的方式去调用它。一般来说,都是在 activationCommands
section (package.json文件中)
或者菜单中。
deactivate
函数只负责销毁,serialize
只负责序列化View的信息,建议不要在这两个函数里做其他事情。
activate
函数不是在Atom启动时就被调用的。它仅在 activationCommands(package.json中声明)
中定义的方法被调用时 才会被调用。所以 activate
仅当 toggle
command被调用时,才会被调用。如果没人点菜单,则它永远不会被调用。
对于本例来说,它做了两件事:一、创建了一个窗口并把这个窗口插入到 Atom workspace中。
@yourNameWordCountView = new YourNameWordCountView(state.yourNameWordCountViewState)
@modalPanel = atom.workspace.addModalPanel(
item: @yourNameWordCountView.getElement(),
visible: false
)
这里我们可以无视state 参数 本例中并没有用到它。
二、创建了一个CompositeDisposable 的实例,以注册插件中所有可以被调用的命令。
# Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
@subscriptions = new CompositeDisposable
# Register command that toggles this view
@subscriptions.add atom.commands.add 'atom-workspace', 'your-name-word-count:toggle': => @toggle()
下一步,我们来编写 toggle
函数。这个函数仅仅是把我们在activate
函数中声明的View做了显示/隐藏 处理。
toggle: ->
console.log 'YourNameWordCount was toggled!'
if @modalPanel.isVisible()
@modalPanel.hide()
else
@modalPanel.show()
现在,我们来验证一下插件是否有效。
现在,我们来回顾一下整个包的执行流程:
package.json
your-name-word-count:toggle来运行你的插件
activate
函数(本例中,插入了一个窗口)your-name-word-count:toggle
时,Atom隐藏了你的插件窗口。your-name-word-count:toggle
command ,Atom再次显示你的插件窗口提示: 注意,如果你用了activationCommands
,流程会有所不同。
现在,我们已经理解了Atom插件的基本工作原理,让我们来修改代码,实现一个小插件吧。
当插件被激活时,我们来数一数Atom当前正在编辑的文档窗口里,有多少个单词吧。
toggle: ->
if @modalPanel.isVisible()
@modalPanel.hide()
else
editor = atom.workspace.getActiveTextEditor()
words = editor.getText().split(/\s+/).length
@yourNameWordCountView.setCount(words)
@modalPanel.show()
atom.workspace.getActiveTextEditor()
.获得了当前Atom编辑窗口。
getText()
获得了字符串。
最后,我们调用setCount()(在view中我们稍后会声明它),来把结果显示出来。
在 your-name-word-count-view.coffee
文件中,我们声明了函数setCount()
setCount: (count) ->
displayText = "There are #{count} words."
@element.children[0].textContent = displayText
运行结果如下:
你应该注意到了代码中的 console.log
因为Atom是基于 Chromium 开发的,所以有一些常规的调试工具供你使用。
用快捷键 Ctrl+Shift+I打开开发者模式,激活开发者工具。
对于做过Web开发的同学们来说,这应该是很熟悉的。
建议测试一下你的插件,如果把插件放置到 spec
目录下,Atom就可以运行它。
Jasmine v1.3 可以用来执行测试
点击 Alt+Ctrl+P 或通过菜单的 View > Developer > Run Package Specs 项目。
也可以在控制台用 atom --test spec
命令来运行它。
现在,你已经完成了你的第一个插件,现在来发布它吧。详见 publish