做一个统计单词数目的Atom插件

本文是Atom 教程 制作单词计数插件的简化介绍,所有代码都来自这篇文章。如果希望参考详细的文档,请直接查看原文。这篇文章用一个简单的小例子,为我们讲解了如何编写一个Atom编辑器插件。

该例子使用的是CoffeeScript,所以为了更好地实现这个例子,我们需要打开Atom编辑器的官方插件package-generator,并设置默认语言为CoffeeScript。

新建插件

点击菜单栏Package->Package Generator-> Generate Atom Package,然后输入插件位置。这样就生成了一个空的插件。这个插件会自动添加到Atom的插件目录下,所以重启Atom之后,就会出现这个新安装的插件。以后如果向删除该插件,直接删除创建的文件夹即可,插件目录下的链接也会自动删除。

这里先说明一下,由于Atom官方的统计单词数插件已经发布到了Atom插件插件库中,所以如果我们创建的插件也叫word-count的话,有可能产生混淆。所以教程建议我们将自己的插件名字改为Your-Name-Word-Count,前面改成你自己的名字。例如我这里的就简单粗暴的叫fuck-word-count。如果你想直接复制粘贴我的代码运行插件的话,你的插件名字也得叫fuck-word-count才行。由于自动生成的代码多处引用了插件名字作为变量名,所以在修改代码的时候需要注意。

插件结构

插件的目录结构如下。如果编写过npm模块的话,会发现这个目录结构和npm模块的类似,只不过多了一些插件相关的东西。

my-package/
├─ grammars/
├─ keymaps/
├─ lib/
├─ menus/
├─ spec/
├─ snippets/
├─ styles/
├─ index.coffee
└─ package.json

具体生成了图上所示的文件。

目录结构

package.json

package.json是Atom插件的描述文件,基本上和npm的描述文件类似,只不过多了几个Atom插件的特定属性。一个典型的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": {
  }
}

源代码

package.json文件中的main属性所指的文件就是我们的源代码。默认情况下它指向lib/wordcount.coffee。打开该文件会发现类似下面的东西。在这里我们需要声明一个顶层模块,在顶层模块中需要包含一些函数,在插件的生命周期内执行相应的动作。

WordCountView = require './word-count-view'
{CompositeDisposable} = require 'atom'

module.exports = WordCount =
  wordCountView: null
  modalPanel: null
  subscriptions: null

  activate: (state) ->
    @wordCountView = new WordCountView(state.wordCountViewState)
    @modalPanel = atom.workspace.addModalPanel(item: @wordCountView.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', 'word-count:toggle': => @toggle()

  deactivate: ->
    @modalPanel.destroy()
    @subscriptions.dispose()
    @wordCountView.destroy()

  serialize: ->
    wordCountViewState: @wordCountView.serialize()

  toggle: ->
    console.log 'WordCount was toggled!'

    if @modalPanel.isVisible()
      @modalPanel.hide()
    else
      @modalPanel.show()

可用的函数如下:

  • activate(state),在插件激活的时候触发,这时候工作区已经准备就绪了。常用于执行初始化,例如绑定事件等等。
  • initialize(state),在Atom 1.14之后引入,这个函数触发的更早,如果你想执行更多初始化控制,可以使用该方法。
  • serialize(),在窗口关闭的时候,如果你的插件需要保存某些状态信息,可以使用该函数。当窗口再次打开的时候,状态信息会传递给activate(state)
  • deactivate(),该方法在窗口关闭的时候执行,在这里需要解绑事件绑定、清理插件需要的资源。

以上几个方法都是可选的。

其他文件

styles文件夹下存放着插件使用的样式表,这些文件用于设定插件的样式、编辑器的外观等等。

keymaps目录包括一个cson文件,用于设定插件的快捷键。

menus目录包括一个cson文件,用于设置插件的菜单项。默认的文件如下,其中context-menu设置上下文菜单,也就是右键打开的菜单;menu设置菜单项中打开的菜单。

# See https://atom.io/docs/latest/hacking-atom-package-word-count#menus for more details
'context-menu':
  'atom-text-editor': [
    {
      'label': 'Toggle word-count'
      'command': 'word-count:toggle'
    }
  ]
'menu': [
  {
    'label': 'Packages'
    'submenu': [
      'label': 'word-count'
      'submenu': [
        {
          'label': 'Toggle'
          'command': 'word-count:toggle'
        }
      ]
    ]
  }
]

核心代码

下面我们来编码实现统计单词数的功能。

视图文件

首先打开lib/XXX.view.coffee文件,这个文件是视图模板,我们的插件的外观需要在这里设置。将代码修改为如下所示。如果你的插件名和这里的不一样需要仔细修改对应的变量名,否则插件跑不起来,最好使用大小写敏感批量替换的方式修改代码。

module.exports =
class FuckWordCountView
  constructor: (serializedState) ->
    # Create root element
    @element = document.createElement('div')
    @element.classList.add('fuck-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
  setCount: (count) ->
      displayText = "There are #{count} words."
      @element.children[0].textContent = displayText

这个插件使用了CoffeeScript语法,如果你对CoffeeScript不熟悉的话,可以看看我的这篇文章《CoffeeScript 简介》。当然,JavaScript也得非常熟悉,因为这里用的就是JavaScript操作DOM树的方式。

首先,module.exports =指定要向外暴露的接口,这里暴露了FuckWordCountView这个类,以后可以在其他代码中调用。然后看看这个类的构造器,用操作DOM树的方式,创建了几个HTML元素,用于显示初始化信息。由于统计单词数不需要记录状态变量,所以serialize方法啥也不干。类似地,destroy方法简单的销毁在构造器中创建的元素。

值得注意的是最后两个方法。getElement方法会在其他地方调用,就是简单的返回显示元素。setCount方法用于显示单词数,在接受单词数之后,会将单词数添加到显示元素中。

这个类就讲解完毕了,是不是很简单?

主要代码

然后来看看主要代码文件,就是package.jsonmain属性指定的这个文件。它就是我们插件的核心文件,作用很简单——统计单词数。在这个文件中,我们会看到Atom插件的编写方式。

首先,照例将代码直接替换。文件的前两句引用了两个模块,第一个就是刚才我们编写的WordCountView类。第二个是Atom编辑器的官方接口,我们引用了其中的CompositeDisposable组件,它主要用于事件订阅,在关闭插件的时候统一取消事件订阅。

和前面的WordCountView类一样,WordCount类需要对外暴露。一开始定义了三个实例变量,并将它们置空。在激活插件的时候,会调用activate: (state)方法,在这里会执行一些初始化操作:创建WordCountView;创建modalPanel用于显示View;创建CompositeDisposable并向其添加事件订阅。

这里用到了一些Atom的API,不过这里不做介绍,因为这篇文章主要是介绍插件的执行流程。如果需要查阅文档的话,直接看Atom API reference documentation,其中定义了大量接口用于操作编辑器。Atom的强大可定制功能就来源于此。

FuckWordCountView = require './fuck-word-count-view'
{CompositeDisposable} = require 'atom'

module.exports = FuckWordCount =
    fuckWordCountView: null
    modalPanel: null
    subscriptions: null

    activate: (state) ->
        @fuckWordCountView = new FuckWordCountView(state.fuckWordCountViewState)
        @modalPanel = atom.workspace.addModalPanel(item: @fuckWordCountView.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',
            'fuck-word-count:toggle': => @toggle()

    deactivate: ->
        @modalPanel.destroy()
        @subscriptions.dispose()
        @wordcountView.destroy()

    serialize: ->
        fuckWordCountViewState: @fuckWordCountView.serialize()

    toggle: ->
        console.log 'FuckWordCount was toggled!'

        if @modalPanel.isVisible()
            @modalPanel.hide()
        else
            editor = atom.workspace.getActiveTextEditor()
            words = editor.getText().split(/\s+/).length
            @fuckWordCountView.setCount(words)
            @modalPanel.show()

剩下几个方法就很简单了:deactivate方法销毁所创建的资源;serialize方法在关闭窗口的时候保存序列化数据,不过这个插件用不着所以实际上啥也没干;toggle方法算是最重要的了,它执行了我们这个插件的核心功能统计单词数。不过这个方法其实也很简单,如果提示是可见的,就把它隐藏;如果提示不可见,就统计单词数,整个统计过程其实就是一句话:editor.getText().split(/\s+/).length,正则表达式分词,然后获取长度。然后把单词数传递给View,让View来显示。

如果代码全部编写正确,然后按下默认快捷键Ctrl+Alt+O,或者点击菜单项中的Toggle word-count,就会显示插件定义的界面,再次点击菜单该界面会消失。

插件界面

在开发过程中,可能需要多次调试插件。修改插件代码并保存之后,并不能马上生效,我们需要按快捷键Ctrl+Shift+F5,或者按Ctrl+Shift+P呼出调试版并输入Window Reload重新加载窗口,插件才能生效。一种常见做法是打开两个Atom窗口,一个用于编写代码,另一个随时重载并测试插件。

另外按Ctrl+Shift+I可以打开开发人员工具,基本上和Chrome的完全一样,从这个调试工具可以看到,Atom编辑器整个其实就是一个浏览器,拥有自己的DOM树,所有界面都是HTML。

工作流

介绍完了插件的代码,我们再来看看Atom编辑器的基本执行顺序。如果你在package.json中指定了activationCommands,那么这个执行顺序会略微不同。

  1. Atom 启动
  2. Atom 开始加载插件
  3. Atom 读取插件的package.json
  4. Atom 加载你的插件的键位、菜单、样式和主模块
  5. Atom 加载插件完成
  6. 在某个时候,用户触发了插件的 your-name-word-count:toggle命令
  7. Atom 执行主模块的 activate方法, 设置隐藏的用户界面
  8. Atom 执行插件的your-name-word-count:toggle方法 ,显示隐藏的界面
  9. 在某个时候,用户再次出发了your-name-word-count:toggle命令
  10. Atom 执行了toggle命令并隐藏插件界面,这个过程可以来回进行多次
  11. 最后,Atom关闭,同时会触发你的插件定义的序列化操作。

测试

最后再来说说Atom的测试。测试代码全都放在spec文件夹下,我们可以看到默认生成了两个测试文件。由于我们修改了默认文件,所以测试应该是通不过的。

要运行测试,点击菜单项View->Developer->Run Package Specs,这样就会打开一个窗口并运行测试。底层的测试框架是 Jasmine v1.3,想编写测试的话,需要先看看它的文档。

运行测试

到此为止,整个插件就介绍完了。这篇文章说了这么长时间,其实核心代码就一行而已。看到这里,你应该对Atom插件的编写和执行,有了一个基本概念了。那么这篇文章的目的就达到了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • 作者:王子亭 Atom 是 GitHub 在 2014 年发布的一款基于 Web 技术构建的文本编辑器,我从 20...
    LeanCloud官方帐号阅读 3,066评论 0 13
  • 上次为大家介绍了package.json的内容,文件在nodejs是非常有用的,发布npm是必须使用的,那么这次给...
    乖小鬼阅读 5,180评论 2 12
  • 11.17 晴 星期五(181) 晚上儿子上完围棋课,还没等我去接他,自己跑回来了,我看着满头大汗的儿子...
    王界程阅读 161评论 0 0
  • 时光匆匆,一去不回,如果我们从那里离开了是否会永远记得彼此,这没有人可以肯定的说,但可以肯定的去想,我们在一起的那...
    时光请你追上我阅读 307评论 0 0
  • 我想写诗,可是我不会,于是挑出押韵难解的中国字排列组合了好一阵; 我想唱歌,可是我不会,于是手里的麦晃了又晃拐着我...
    九松子阅读 145评论 0 0