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

2023-11-06

本文是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插件的编写和执行,有了一个基本概念了。那么这篇文章的目的就达到了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

做一个统计单词数目的Atom插件 的相关文章

  • Flex、AngularJS + Masonry、akoenig/angular-deckgrid 等 [重复]

    这个问题在这里已经有答案了 我一直在发送此电子邮件 我即将发布一个用于 Web 应用程序安全的应用程序 它需要使用像 Masonry 这样的网格 我已经尝试过所有的 每一个角度模块 指令和不同的方法 包括基于 CSS 的技术 纯 Vanil
  • Brunch 源映射:在 Chrome 开发工具中未命中断点

    我正在使用 Brunch 中内置的默认源映射 我看到文件很好 但无法在源映射文件中命中断点 使用 Javascript 访问调试器debugger 有效 这让我相信早午餐方面出了问题 这是我的 brunch config js module
  • jQuery JSONP ajax,未设置身份验证标头

    我正在尝试使用以下设置向 google 联系人 API 发出 ajax 请求 ajax url https www opensocial googleusercontent com api people me all dataType js
  • 在 ajax 请求上启用 jQuery contextMenu 项

    我正在尝试更新上下文菜单 http medialize github com jQuery contextMenu docs htmlitem 如果 ajax 请求改变了我的 div 内容 这就是我的意思 我有一个这样的 div div c
  • querySelector 搜索直接子级[重复]

    这个问题在这里已经有答案了 我有一些类似 jquery 的函数 function elem return gt someselector elem 问题是我怎样才能做同样的事情querySelector 问题是 gt 选择器中querySe
  • 玉石压痕错误

    因此 对于我的 Express 网站 我使用 jade 所以我决定尝试修改我的布局文件 以便我可以开始设计我的网站 我修改了原始布局代码 有效 但我开始在任何扩展布局的文件中出现缩进错误 如下所示 500 Error home kevin
  • 如何正确地将节点从引用传递到上下文?

    我正在尝试将节点从引用传递到上下文 但是因为我在第一次渲染后没有重新渲染 所以传递的节 点是null 我考虑了两种变体 但我认为它们不是最好的 To pass ref代替ref current 但在用例中 我将被迫使用类似的东西contex
  • 鼠标移动时画布拖动

    我正在尝试构建一个可以使用鼠标移动拖动的画布 我做了一些我无法理解的错误 因为一开始似乎有效 然后出现了一个增量错误 使画布移动得太快 考虑以下代码 window onload function var canvas document ge
  • 用更好的模式替换开关(Javascript)

    我必须升级我的应用程序以根据用户类型和角色属性显示页面 目前 我使用一个简单的 switch 语句来根据用户类型来执行此操作 例如 switch type case a return CONSTANT ONE case b return C
  • 仅从功能区打开一个对话框

    我有一个带有登录按钮的功能区 可打开登录对话框 我想将对话框的数量限制为一个 我正在使用函数 displayDialogAsync startAddress options callback https learn microsoft co
  • 在给定索引上将字符串分成两部分并返回两部分

    我有一个字符串 需要在给定索引上拆分 然后返回两个部分 并用逗号分隔 例如 string 8211 8 211 98700 98 700 因此 我需要能够在任何给定索引上拆分字符串 然后返回字符串的两半 内置方法似乎执行分割 但只返回分割的
  • 如何在数据表角度中基于 JSON 动态填充表值?

    我在用着Angular 数据表 https l lin github io angular datatables 我需要能够根据返回的数据动态创建表 换句话说 我不想指定列标题 Example json数据 id 2 city Baltim
  • 当 Chrome 中嵌套滚动中的数据更改时防止页面滚动

    我在页面中有一个固定大小的元素 带有 溢出 滚动 其内容经常更改 我预计该元素内部发生的更改会影响该元素的滚动 但不会影响页面滚动 但是当这个元素位于页面顶部时 页面本身开始滚动 我怎样才能防止这种情况发生 要重现此行为 我在 chrome
  • 为什么 console.log() polyfill 不使用 Function.apply()?

    我一直在看一些流行的console log 包装 填充 保罗 爱尔兰的 http paulirish com 2009 log a lightweight wrapper for consolelog 本阿尔曼的 http benalman
  • 从浏览器访问本地文件?

    您好 我想从浏览器访问系统的本地文件 由于涉及大量安全检查 是否可以通过某种方式实现这一目标 或使用 ActiveX 或 Java Applet 的任何其他工作环境 请帮帮我 要通过浏览器访问本地文件 您可以使用签名的 Java Apple
  • jQuery UI 对话框 - 关闭后无法打开

    我有一个问题jquery ui dialog box https jqueryui com dialog 问题是 当我关闭对话框然后单击触发它的链接时 除非刷新页面 否则它不会再次弹出 如何在不刷新实际页面的情况下回调对话框 下面是我的代码
  • 如何得知客户端从服务器的下载速度?

    根据客户的下载速度 我想以低质量或高质量显示视频 任何 Javascript 或 C 解决方案都是可以接受的 Thanks 没有任何办法可以确定 您只能测量向客户端发送数据的速度 如果没有来自客户端的任何类型的输入来表明其获取信息的速度 您
  • 有序 JSON 对象

    我有一个 servlet 它与数据库通信 然后返回有序 按时间排序 对象的列表 在servlet部分 我有 access DB returns a list of User objects ordered ArrayList users M
  • 使用严格模式编译指示时如何声明全局变量

    使用自调用函数来包装严格模式兼容代码 通常称为严格模式编译指示 被认为是一种很好的做法 function use strict Strict code here 我的问题是在这种情况下如何声明全局变量 我今天知道的三种替代方案 替代方案 1
  • 突出显示单词并提取其附近文本的函数

    我有一个文本例如 Etiam porta semmalesuada magna mollis euismod 整数取数 ante venenatis dapibus posuere velit aliquet 埃蒂亚姆 门塔 塞姆 male

随机推荐

  • android Intent常用标识

    Intent常用标识 FLAG ACTIVITY BROUGHT TO FRONT 这个标志一般不是由程序代码设置的 如在launchMode中设置singleTask模式时系统帮你设定 FLAG ACTIVITY CLEAR TOP 如果
  • 简单有限状态机代码实现

    学习链接 Unity有限状态机编写 木子微冷 博客园 实现思路 1 创建状态基类BasState 所有具体状态类继承该类 基类有3个方法 进入状态 状态中 离开状态 2 创建管理类来管理状态机 StateMgr 并实现状态机的各个方法 状态
  • MariaDB数据库

    MariaDB数据库是MySQL数据库的一个分支 仍是一个开放的数据库 数据和表定义文件是兼容的 所有客户端 API 和协议都是兼容的 文件名 二进制文件和路径在MySQL和MariaDB上是相同的 端口和套接字是相同的 所有MySQL连接
  • 接口测试问题以及经验合集

    一 接口测试常见问题 前景提要 由于本人测试小白 可能所遇问题都较为基础 测试小白可以参考 Q1 postman会报connect ECONNREFUSED jemeter会报org apache http conn HttpHostCon
  • 14:00面试,14:06就出来了,问的问题有点变态。。。

    从小厂出来 没想到在另一家公司又寄了 到这家公司开始上班 加班是每天必不可少的 看在钱给的比较多的份上 就不太计较了 没想到5月一纸通知 所有人不准加班 加班费不仅没有了 薪资还要降40 这下搞的饭都吃不起了 还在有个朋友内推我去了一家互联
  • async_retrying

    from async retrying import retry import aiohttp import asyncio retry attempts 6 async def fetch print 1 async with aioht
  • 南京特殊教育师范:校外如何登陆Webplus站群发布文章

    因为网络安全需要 Webplus站群2021年完成三级等保工作 为了满足三级等保整改要求 网站群后台禁止外网访问 如果在家需要发布文章 可以使用学校的VPN进入后台 发布文章 1 访问学校的WebVPN https vpn njts edu
  • javascript小说阅读器分页算法的实现

    好久好久不写代码了 也好久没更新博客了 这次就和大家分享一个电子书阅读器分页的算法吧 像一些主流的阅读器 如QQ阅读 iReader等 都实现了txt文档分页显示的功能 打开一个txt文档可以快速把文档分割成若干页 每页文字正好铺满屏幕 点
  • ctfshow web入门 命令执行34-38

    1 web34 和web33差不多 过滤了冒号 第一个为官方解法 payload c include GET 1 gt 1 php filter read convert base64 encode resource flag php c
  • 立一个小小的flag

    距离上次写博客已经很久了 下个月也是我入职一周年了 很久没有沉下心好好写一些文章 内心还有有点不安的 准备接下来花点时间好好写点东西 把工作学习中的事情记录下来 希望对你我都有帮助 17年还是大三的时候 每天都会坚持学习写博客 沉淀自己学到
  • 基础实验篇

    导读 uORB是PX4 Pixhawk系统中非常重要且关键的模块之一 是用于无人机模块间通信的协议机制 本篇将详细介绍uORB并详细拆解uORB消息读写与自定义实验 一 基础实验篇 uORB消息读写与自定义实验 一 ect Request
  • ctfshow--web入门(web101--web115&web123&web125-web133)

    ctfshow web入门 web101 web115 web123 web125 web133 web101
  • 什么是CSRF

    目录 1 CSRF是什么 2 CSRF漏洞检测 3 防御CSRF攻击 3 1 验证 HTTP Referer 字段 3 2 在请求地址中添加 token 并验证 3 3 在 HTTP 头中自定义属性并验证 1 CSRF是什么 CSRF Cr
  • 结合高阶函数聊聊useMemo和useCallback

    使用useMemo可以实现useCallback useCallback fn deps 其实相当于 useMemo gt fn deps 所以说 使用useMemo完全可以实现useCallback useCallback functio
  • 如何用MODIS数据在TIMESAT中提取物候参数

    感觉研究植被物候的帖子好少呀 也找不到关于TIMESAT的操作教程 这里便记载我学习植被物候信息提取的学习笔记 下面是我综合了很多零零散散的信息学习到的东西 但是还是有很多看不明白的地方 比如一些参数的设置上面 希望有知道的可以让我学习一下
  • Spring Boot + Jpa(Hibernate) 架构基本配置

    1 基于springboot 1 4 0 RELEASE版本测试 2 springBoot Hibernate Druid Mysql servlet jsp 不废话 直接上代码 一 maven的pom文件
  • Git 常用指令

    Git 常用指令 bashrc 文件 用于输出git提交日志 alias git log git log pretty oneline all graph abbrev commit 用于输出当前目录所有文件及基本信息 alias ll l
  • pandas数据预处理------去除‘特征重复’的2种方法

    常见的数据重复包括 1 记录重复 一个或多个特征的某条记录的值完全相同 2 特征重复 存在一个或者多个特征名称不同 但数据完全相同的情况 去除特征重复的2种方法 1 通过相似度矩阵去重 要去除连续的特征重复 可以利用特征间的相似度将两个相似
  • permission denied while trying to connect to the Docker daemon socket 错误

    安装 docker 执行错误如下 docker ps permission denied while trying to connect to the Docker daemon socket at unix var run docker
  • 做一个统计单词数目的Atom插件

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