示例 Word Count

如果你还没有接触过你的第一个插件章节,我们建议你先去了解一下。

本篇示例将会告诉你,如何制作一个Markdown辅助编辑工具。开始之前,我们先了解一下本篇你将接触到的插件功能:当编辑Makrdown文件时状态栏会显示编辑区字数,如果你编辑文档或者打开了另一个md文件字数也会随之改变。

示例 Word Count - 图1

?> 小贴士:如果你碰到了什么问题,可以在这里下载完整的项目进行调试

本节要点

本章将通过三个部分让你了解vscode有关的概念:

如果你还不熟悉生成插件的步骤,请先了解之前的章节:生成插件-运行Yo

就像你之前在示例:Hello-world中做的一样,使用F5或者Cmd + R运行该项目。

更新状态栏


将下列代码更新到extension.ts中。这段代码声明了一个WordCounter类用于控制文本计数并显示到状态栏中,我们依然用了”Hello World”这个命令来执行updateWordCount

  1. // 'vscode'模块包含了VS Code扩展性API
  2. // 导入你要用到的扩展类
  3. import {
  4. window,
  5. commands,
  6. Disposable,
  7. ExtensionContext,
  8. StatusBarAlignment,
  9. StatusBarItem, TextDocument
  10. } from 'vscode';
  11. // 当你的插件激活时,会调用这个方法。'activation'是package.json中定义好的activation events
  12. export function activate(context: ExtensionContext) {
  13. // 用console对象输出诊断和错误信息(console.log / console.error).
  14. // 下面这行代码,只会在你的插件激活时执行一次
  15. console.log('Congratulations, your extension "WordCount" is now active!');
  16. // 新建一个字数计数器
  17. let wordCounter = new WordCounter();
  18. let disposable = commands.registerCommand('extension.sayHello', () => {
  19. wordCounter.updateWordCount();
  20. });
  21. // 把disposables添加到一个列表中,以便关闭插件时释放资源
  22. context.subscriptions.push(wordCounter);
  23. context.subscriptions.push(disposable);
  24. }
  25. class WordCounter {
  26. private _statusBarItem: StatusBarItem = window.createStatusBarItem(StatusBarAlignment.Left);
  27. public updateWordCount() {
  28. // 获取当前编辑器
  29. let editor = window.activeTextEditor;
  30. if (!editor) {
  31. this._statusBarItem.hide();
  32. return;
  33. }
  34. let doc = editor.document;
  35. // 只对markdown文件生效
  36. if (doc.languageId === "markdown") {
  37. let wordCount = this._getWordCount(doc);
  38. // 更新状态栏
  39. this._statusBarItem.text = wordCount !== 1 ? `${wordCount} Words` : '1 Word';
  40. this._statusBarItem.show();
  41. } else {
  42. this._statusBarItem.hide();
  43. }
  44. }
  45. public _getWordCount(doc: TextDocument): number {
  46. let docContent = doc.getText();
  47. // 去除多余空格
  48. docContent = docContent.replace(/(< ([^>]+)<)/g, '').replace(/\s+/g, ' ');
  49. docContent = docContent.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
  50. let wordCount = 0;
  51. if (docContent !== "") {
  52. wordCount = docContent.split(" ").length;
  53. }
  54. return wordCount;
  55. }
  56. dispose() {
  57. this._statusBarItem.dispose();
  58. }
  59. }

现在让我们试试更新插件吧。

在VS Code中(非开发主机窗口)按下F5或者直接按重启按钮。假设你已经打开了一个markdown文件,然后和Hello-World示例一样,我们在命令面板中输入Hello World启动插件。

示例 Word Count - 图2

很棒,我们接下来做更cool的事情——实时更新字数。

订阅事件


我们先来看看事件中的类方法:

  • onDidChangeTextEditorSelection - 鼠标位置变动时触发。
  • onDidChangeActiveTextEditor - 激活编辑器(打开的编辑器)切换的时候触发。

为了实现这个目标,我们给extension.ts添加一个新类,订阅上述事件然后让WordCounter更新字数。

?>在实现时,你需要注意我们是如何把消息订阅( subscription )转换为释放器( Disposables )来管理的,它将监听并释放自己。

根据下列代码,将WordCounterController类添加到extension.ts文件底部。

  1. class WordCounterController {
  2. private _wordCounter: WordCounter;
  3. private _disposable: Disposable;
  4. constructor(wordCounter: WordCounter) {
  5. this._wordCounter = wordCounter;
  6. // 订阅 文本选区变更 和 编辑器激活事件
  7. let subscriptions: Disposable[] = [];
  8. window.onDidChangeTextEditorSelection(this._onEvent, this, subscriptions);
  9. window.onDidChangeActiveTextEditor(this._onEvent, this, subscriptions);
  10. // 为当前文件更新计数器
  11. this._wordCounter.updateWordCount();
  12. // 把两个事件订阅器整合成一个临时容器
  13. this._disposable = Disposable.from(...subscriptions);
  14. }
  15. dispose() {
  16. this._disposable.dispose();
  17. }
  18. private _onEvent() {
  19. this._wordCounter.updateWordCount();
  20. }
  21. }

我们不希望通过执行命令才启动词汇计数插件,而是markdown文件一打开插件就应该启动。

首先,我们将active函数替换成这样:

  1. // 输出诊断信息
  2. console.log('Congratulations, your extension "WordCount" is now active!');
  3. // 新建一个词汇计数器
  4. let wordCounter = new WordCounter();
  5. let controller = new WordCounterController(wordCounter);
  6. // 插件关闭时,释放器会自动释放
  7. context.subscriptions.push(controller);
  8. context.subscriptions.push(wordCounter);

然后,我们必须保证Markdown文件打开时才激活。在之前的示例中,我们通过extension.sayHello命令激活插件,而我们现在用不到了,删除package.json文件中原来的contributes键。

  1. "contributes": {
  2. "commands":
  3. [{
  4. "command": "extension.sayHello",
  5. "title": "Hello World"
  6. }
  7. ]
  8. }

用下面的代码替换掉:

  1. "activationEvents": [
  2. "onLanguage:markdown"
  3. ]

onLanguage: ${language}配置一个语言,在这里是”markdown” 。这样一来打开这一类文件就会触发这个事件了。

重启插件(按下Cmd + R或者重启按钮),然后新建一个.md文件,你应该能看到下图的效果。

示例 Word Count - 图3

如果你在active函数上打了断点,你应该能看到markdown文件被打开时只触发了一次。WordCountController构造器运行之后,订阅了编辑器事件,这样我们整个插件就正常运行了。

自定义状态栏


VS Code允许你定制状态栏的颜色、图标、提示文本等额外样式。如果你不清楚状态栏相关的API,你可以查看该类型的代码提示,你也可以通过vscode.d.tsVS Code扩展性API查看,这个文件就在你生成的项目文件夹里,在编辑器中打开node_modules\vscode\vscode.d.ts,你能看到完整的扩展性 API和注释。

示例 Word Count - 图4

更新StatusBarItem接口的代码

  1. // 更新状态栏
  2. this._statusBarItem.text = wordCount !== 1 ? `$(pencil) ${wordCount} Words` : '$(pencil) 1 Word';
  3. this._statusBarItem.show();

这样就在计数左边显示了一个Github Octionpencil图标

示例 Word Count - 图5

释放插件资源


现在,我们来深入了解一下VS Code是怎么通过释放器(Disposables)控制资源的。

当一个插件被激活,它会传入一个ExtensionContext对象, 这个对象有一个用于订阅释放器(Disposable)的subscriptions方法。插件将=释放器添加到这个订阅列表中,VS Code则会在插件关闭的时候释放这些对象。

很多能生成工作区(workspace)或者UI对象的VS Code API(比如:registerCommand)会自动返回一个释放器,我们可以直接调用他们的dispose方法释放UI元素。

事件则有所不同,比如onDid*这类事件订阅器会返回一个释放器。插件通过释放事件的释放器(Disposable)来取消已经订阅的事件。在我们的例子里,WordCountController把事件订阅的释放器直接保存到自己的释放器列表中,在插件关闭时释放。

  1. // 订阅选区变动事件和编辑器激活事件
  2. let subscriptions: Disposable[] = [];
  3. window.onDidChangeTextEditorSelection(this._onEvent, this, subscriptions);
  4. window.onDidChangeActiveTextEditor(this._onEvent, this, subscriptions);
  5. // 把两个事件订阅器生成一个组合的临时容器
  6. this._disposable = Disposable.from(...subscriptions);

在本地安装你的插件


到目前为止,你的插件都还跑在插件开发模式中,要想让你的插件在正常的VS Code中运行起来将你的插件复制到.vscode/extensions目录下。

发布插件


参阅分享插件

下一步

插件生成器 - 学习Yo Code插件生成器的更多选项

Extenstion API - 插件API概览

发布插件 - 学会如何在应用市场发布一个公共插件

编辑器 API - 学习更多有关文档, 文档编辑器和编辑的内容

更多插件示例 - 在插件示例列表学习其他用法