如何開發一個 Atom 插件
準備
基礎知識
在開始編寫插件之前,了解一些基本的 Atom 知識是必要的。以下是我根據官方文檔加自己在開發過程中的一些理解的歸納,沒有包含所有的細節,但在稍微復雜點的插件中基本都會用到。
1、生成一個插件
Atom 生成插件很簡單,打開命令面板( cmd+shift+p )輸入“Generate Package”,將出現一個對話框,輸入你將要建立的包名字,回車即可。Atom 會自動創建一個已剛輸入的包名字命名的文件夾,里面包含了默認生成的文件,默認的位置在 ~/atom/package 中,其目錄結構如下:
my-package
├─ keymaps/
├─ lib/
├─ menus/
├─ spec/
├─ styles/
└─ package.json
其實,基本的路徑結構還包括 snippets 和 grammars 目錄,下面實戰中將會用到 snippets,用來存放自定義的代碼段文件。
keymaps
我們可以為插件自定義快捷鍵,形如上面創建包的 kemaps/my-package.json 文件中定義:
{
"atom-workspace": {
"ctrl-alt-o": "my-package:toggle"
}
}
這樣,我們在鍵盤上按組合鍵 ctrl+alt+o 就會執行命令 my-package:toggle, 其中鍵 atom-workspace 是設置快捷鍵起作用范圍,這里 atom-workspace 元素是 Atom UI 的父級,所以只要在工作區域按快捷鍵就可觸發命令。如果定義 atom-text-editor,那么就只能在編輯區域觸發命令。 了解更多
lib
該目錄下是主要是實現插件功能的代碼,并且必須包含插件的主入口文件(其將在 package.json 的 main 字段指定。如不指定,默認 lib 下的 index.js 或 index.coffee 文件), 主入口文件可以實現以下基本方法:
- config: 該對象中可以為包自定義配置。
- activate(state):該方法是在包激活的時候調用。如果你的包實現了 serialize() 方法,那么將會傳遞上次窗口序列化的 state 數據給該方法
- initialize(state):( Atom1.14 以上版本可用 ) 類似于 activate(), 但在它之前調用。intialize() 是在你的發序列化器或視圖構建之前調用,activate() 是在工作區環境都已經準備后調用。
- serialize():窗口關閉后調用,允許返回一個組件狀態的 JSON 對象。當你下次窗口啟動時傳遞給 activate() 方法。
- deactivate():當窗口關閉時調用。如果包正在使用某些文件或其他外部資源,將在這里釋放它們。
styles
styles 目錄下存放的是包的樣式文件。可以使用 CSS 或 LESS 編寫。
snippets
snippets 目錄下存放的是包含常用的代碼段文件,這樣就可以通過輸入縮寫前綴快速生成常用的代碼語法,實戰中將會詳細講解。
menus
該目錄下存放的是創建應用菜單和編輯區域菜單文件。形如:
{
"context-menu": {
"atom-text-editor": [{
"label": "Toggle my-package",
"command": "my-package:toggle"
}]
},
"menu": [{
"label": "Packages",
"submenu": [{
"label": "my-package",
"submenu": [{
"label": "Toggle",
"command": "my-package:toggle"
}]
}]
}]
}
context-menu 字段定義的上下文菜單,通過在你定義的元素(示例中是 atom-text-editor 字段)范圍內點擊右鍵呼出菜單欄,點擊 Toggle my-package 會執行 my-package:toggle 命令。
menu 字段定義應用的菜單,其出現在 Atom 主菜單欄中,定義類似 context-menu。
2、配置
為了讓開發的包可配置化,Atom 為我們提供了 Configuration API。
atom.config.set 方法為包寫入配置。
atom.config.setSchema 方法為包寫入配置 schema, 下面示例 demo 中定了一個 type 為 string 的枚舉類型 schema。 了解更多
atom.config.get 方法讀取包配置。
下面是一些 demo:
const versionSchema = {
title: 'Element Version',
description: 'Document version of Element UI.',
type: 'string',
default: '1.3',
enum: ['1.1', '1.2', '1.3'],
order: 1
};
// 設置配置schema
atom.config.setSchema('element-helper.element_version', versionSchema);
// 修改配置的element-helper.element_version值為1.2
atom.config.set('element-helper.element_version', '1.2');
// 獲取配置element-helper.element_version
atom.config.get('element-helper.element_version'); // 1.2
為了監聽 config 的變化 Atom 提供了 observe 和 onDidChange 兩個方法。前者會在指定keypath 的值時立即調用它的回調函數,以后改變也會調用。后者則是在 keypath 下次改變后調用它的回調函數。使用 demo 如下:
atom.config.observe('element-helper.element_version', (newValue) => {
console.log(newValue);
});
atom.config.onDidChange('element-helper.element_version', (event) => {
console.log(event.newValue, event.oldValue);
});
3、作用域
Atom 中的作用域是一個很重要的概念,類似于 CSS 的 class,通過作用域的名稱來選擇操作作用的范圍。打開一個 .vue 文件,按 alt+cmd+i 打開開發者工具,切換到 Elements 選項。會看到類似如下的 HTML 結構:
圖上 span 元素的 class 的值就是作用域名稱。那怎么來利用作用域名稱來選擇作用范圍呢?比如要選擇 .vue 文件中所有元素標簽名稱作用范圍。其實就像 CSS 選擇器一樣,如 “text.html.vue .entity.name.tag.other.html” 是選擇 .vue 文件中所有標簽名稱節點,注意這里是不要加 syntax-- 前綴的,Atom 內部會處理。其可用于 snippets、config 等需要限定作用范圍的功能中,后面實戰中很多地方都有用到。 了解更多
4、package.json
類似于 Node modules,Atom 包也包含一個 package.json 文件,但是 Atom 擁有自己定義的字段,如 main 指定包的主入口文件;activationCommands 指定那些命令可以激活入口文件中 activate 方法;menus, styles, keymaps, snippets 分別指定目錄,樣式,快鍵鍵映射,代碼段的文件加載位置和順序。 了解更多
5、與其他包交互
可以在 package.json 中指定一個或多個版本號的外包來為自己提供服務。如:
"providedServices": {
"autocomplete.provider": {
"versions": {
"2.0.0": "provide"
}
}
}
這里使用 autocomplete+ 包提供的 provide API,版本是 2.0.0。這樣只要在 package.json 中main 字段指定的主文件中實現 provide 方法,就可以在包激活的任何時候調用。如:
export default {
activate(state) {}
provide() {
return yourProviderHere;
}
}
實戰
經過簡單的基礎知識介紹,是不是有點躍躍欲試了?好,滿足你。接下來我們將通過實戰來運用這些知識,深入了解其原理和工作機制。下面示例都是以 Element-Helper 插件為樣本,你先不必著急去看源碼。
1、自動補全
這里通過使用 autocomplete+ 提供的服務( Provide API )來實現自動補全功能。關于怎么建立交互請看基礎知識中的第 5 小節,那么我們要實現的就是定義自己的 yourProiverHere, 它是一個對象包含如下示例中的方法:
const provider = {
// 選擇器,指定工作的作用域范圍,這里是只有在選擇器 '.text.html' 作用域下才能工作, 如 html 文件有作用域 '.text.html.basic', vue 文件有作用域 '.text.html.vue' 都是包含于 '.text.html' 的
selector: '.text.html',
// 排除工作作用域中子作用域,如 html, vue 文件中的注釋。可選
disableForSelector: '.text.html .comment',
// 表示提示建議顯示優先級, 數字越高優先級越高,默認優先級是0。可選
inclusionPriority: 1,
// 如果為 true,那么根據 inclusionPriority 大小,高優先級就會阻止其他低優先級的建議服務提供者。可選
excludeLowerPriority: true,
// 在提示下拉選項中的排序, 默認為 1,數字越高越靠前。可選
suggestionPriority: 2
// 返回一個 promise 對象、包含提示的數組或者 null。這里返回提示列表給 autocomplete+ 提供的服務展示,我們不用關心如何展示
getSuggestions(request) {
// todo
return [];
},
// provide 提供的建議(即 getSuggetion 方法返回的提示)插入到緩沖區時被調用。可選
onDidInsertSuggestion({editor, triggerPosition, suggestion}) {
// todo
},
// provider 銷毀后的善后工作,可選
dispose() {
// todo
}
}
重點介紹下 getSuggestion 的參數 request 對象,它包含下面屬性:
- editor: 當前的 文本編輯上下文
- bufferPosition:當前光標的 位置 ,包含屬性 row 和 column。
- scopeDescriptor: 當前光標位置所在的作用域描述符,可通過其 .getScopesArray 方法獲取到包含所有自己和祖先作用域選擇器的數組。你可以通過按 cmd+shift+p 打開命令面板輸入 Log Cursor scope 來查看作用描述符。
- prefix:當前光標輸入位置所在單詞的前綴,注意 autocomplete+ 不會捕獲 ‘<’, ‘@’ 和 ‘:’ 字符,所以后面我們得自己做處理。原來沒有仔細閱讀文檔(衰),我發現我原來實現的方法比較局限,其實這里教你怎么定義 自己的 prefix 了
- activateManually:這個提示是否是用戶 手動觸發
介紹完 API 了,是時候來一起小試牛刀了。這里就以 Element UI 標簽的屬性值自動提示為例:
autocomplete+ 提供的 provider 會在用戶包激活后任何時候調用(比如輸入字符),我們只需在 getSuggestion 方法中返回提示信息(建議)數組就好了。那么問題重點來了,怎么獲取這個提示信息數組?觀察示例,想一想,可以分兩大部分:判斷提示出現的時機和過濾出提示信息數組。
1、判斷提示出現時機
示例中的時機是是否是標簽屬性值開始(isAttrValueStart),我們先實現三個方法:是否在字符串作用域范圍內(hasStringScope)、是否在標簽作用域范圍內(hasTagScope)和輸入字符位置前是否具有屬性名稱(getPreAttr):
// scopes 是否包含單引號和雙引號作用域選擇器來決定是否在字符串中
function hasStringScope(scopes) {
return (scopes.includes('string.quoted.double.html') ||
scopes.includes('string.quoted.single.html'));
}
// scopes 是否存在標簽(tag)的作用域選擇器來決定是否在標簽作用域內,這里也是存在多種 tag 作用域選擇器
function hasTagScope(scopes) {
return (scopes.includes('meta.tag.any.html') ||
scopes.includes('meta.tag.other.html') ||
scopes.includes('meta.tag.block.any.html') ||
scopes.includes('meta.tag.inline.any.html') ||
scopes.includes('meta.tag.structure.any.html'));
}
// 獲取當前輸入位置存在的屬性名
function getPreAttr(editor, bufferPosition) {
// 初始引號的位置
let quoteIndex = bufferPosition.column - 1;
// 引號的作用域描述符
let preScopeDescriptor = null;
// 引號的作用域描述符字符串數組
let scopes = null;
// 在當前行循環知道找到引號或索引為 0
while (quoteIndex) {
// 獲取位置的作用描述符
preScopeDescriptor = editor.scopeDescriptorForBufferPosition([bufferPosition.row, quoteIndex]);
scopes = preScopeDescriptor.getScopesArray();
// 當前位置不在字符串作用域內或為引號起始位置, 則跳出循環
if (!this.hasStringScope(scopes) || scopes.includes('punctuation.definition.string.begin.html')) {
break;
}
quoteIndex--;
}
// 屬性名匹配正則表達
let attrReg = /\s+[:@]*([a-zA-Z][-a-zA-Z]*)\s*=\s*$/;
// 正則匹配當前行引號之前的文本
let attr = attrReg.exec(editor.getTextInBufferRange([[bufferPosition.row, 0], [bufferPosition.row, quoteIndex]]));
return attr && attr[1];
}
說明:
- 參數 scopes 是前面講的作用域描述符。如果不是很清楚,可以打開命令面板輸入 Log Cursor scope 來查看。
- scopeDescriptorForBufferPosition 方法是獲取給定位置的作用域描述符,具體請查看 這里 。
- getTextInBufferRange 方法是根據位置范圍( Range )獲取文本字符串,具體請查看 這里 ,他有個別稱 getTextInRange(官方文檔里是沒有的,可以查看源代碼 L1024 和 L931 ,實現一毛一樣)。
那么接下來結合三個方法來實現 isAttrValueStart 方法:
// 參數解釋請看 ‘自動補全’ 小節
function isAttrValueStart({scopeDescriptor, bufferPosition, editor}) {
// 獲取作用域描述符字符串數組, 形如 ['text.html.vue', 'meta.tag.other.html', 'string.quoted.double.html', 'punctuation.definition.string.end.html']
const scopes = scopeDescriptor.getScopesArray();
// 獲取當前位置的前一個字符位置
const preBufferPosition = [bufferPosition.row, Math.max(0, bufferPosition.column - 1)];
// 獲取前一個字符位置的作用域描述符
const preScopeDescriptor = editor.scopeDescriptorForBufferPosition(preBufferPosition);
// 獲取作用域描述符字符串數組
const preScopes = preScopeDescriptor.getScopesArray();
// 當前鼠標位置 and 前一個位置(這個里主要是判斷 attr= 再輸入 ' 或 " 這種情況)是包含在字符串作用域中 and 前一個字符不能是字符串定義結束字符(' or ")為真,就說明是開始輸入屬性值
return (this.hasStringScope(scopes) &&
this.hasStringScope(preScopes) &&
!preScopes.includes('punctuation.definition.string.end.html') &&
this.hasTagScope(scopes) &&
this.getPreAttr(editor, bufferPosition));
}
2、過濾出提示信息數組
前面已經判斷提示信息出現的時機,剩下就是如何展示相應標簽屬性的值了, 這真是個精細化工作。慣例,先做些準備工作:1.獲取輸入位置所在的標簽名(getPreTag); 2.獲取輸入位置所在的屬性名(getPreAttr) - 這個上小節已實現;3.既然知道標簽名和屬性名,那么就可以從事先純手工打造的 attributes.json 文件(具體請看 element-helper-json )中找到對應的屬性值了(getAttrValues)- 這個就是遍歷 json 對象屬性,不具體解釋。
// 標簽名匹配正則表達式 - 標簽匹配有很多情況,這里并不完善...,僅供參考。
let tagReg = /<([-\w]*)(?:\s|$)/;
// 參數請查看上面 getSuggestion 參數對象屬性解析
function getPreTag(editor, bufferPosition) {
// 當前行
let row = bufferPosition.row;
// 標簽名
let tag = null;
// 文件逐行向上遍歷知道找到正則匹配的字符串,或 row = 0;
while (row) {
// lineTextForBufferRow 獲取當前行文本字符串
tag = tagReg.exec(editor.lineTextForBufferRow(row));
if (tag && tag[1]) {
return tag[1];
}
row--;
}
return;
}
OK,準備工作好了,我們來對獲取到的屬性值數組進行格式化處理,獲得 getSuggestions 能識別的數據結構數組:
function getAttrValueSuggestion({editor, bufferPosition, prefix}) {
// 存放提示信息對象數據
const suggestions = [];
// 獲取當前所在標簽名
const tag = this.getPreTag(editor, bufferPosition);
// 獲取當前所在屬性名稱
const attr = this.getPreAttr(editor, bufferPosition);
// 獲取當前所在標簽屬性名下的屬性值
const values = this.getAttrValues(tag, attr);
// 屬性值數組進行格式化處理
values.forEach(value => {
if (this.firstCharsEqual(value, prefix) || !prefix) {
suggestions.push(buildAttrValueSuggestion(tag, attr, value));
}
});
// 返回符合 autocompete+ 服務解析的數據結構數組
return suggestions;
}
// 對原始數據加工處理
function buildAttrValueSuggestion(tag, attr, value) {
// ATTRS 是 attributes.json 文件解析出的 json 對象
const attrItem = ATTRS[`${tag}/${attr}`] || ATTRS[attr];
// 返回 suggestion 對象 具體格式說明請看:https://github.com/atom/autocomplete-plus/wiki/Provider-API#suggestions
return {
text: value, // 插入文本編輯器,替換 prefix
type: 'value', // 提示類型,用于列表提示左邊的 icon 展示,有變量(varabale), 方法(method)和函數(function)等可選
description: attrItem.description, // 用于選中提示條目后,提示框下面展示的信息
rightLabel: attrItem.global ? 'element-ui' : `<${tag}>` // 右邊展示的文本信息
};
}
經過以上兩步,只需在 getSuggestions 方法中返回數組給 autocomplete+ 服務即可:
// ...
getSuggestions(request) {
if (this.isAttrValueStart(request)) {
return this.getAttrValueSuggestion(request);
}
}
// ...
到這里大家應該明白自動補全工作原理了吧,其他的可以依葫蘆畫瓢啦,be happy。
2、代碼段
定義代碼段的方式有三種方式:
- 全局定義。在 Atom -> Snippets 菜單中定義,定義方式同第二種
- 包內定義。在基礎知識部分,我們介紹了生成包后的文件目錄結構和作用,其中 snippets 文件夾里放的就是我們自定義的常用代碼塊 json 文件,這里我使用為 coffeescript 對象提供的 cson 文件,類似 json,但語法沒有那么嚴格且支持多行字符串,如官方介紹:
Which is far more lenient than JSON, way nicer to write and read, no need to quote and escape everything, has comments and readable multi-line strings, and won't fail if you forget a comma.
現在來看下如何編寫一個代碼段,基本格式如下:
".source.js":
"notification":
"prefix": "notify",
"body": """
this.$notify({
title: '${1:title}',
message: '${2:string|VNode}'
});
"""
最頂層的鍵字符串(.source.js)是作用域選擇器,指定在文本編輯器中那個范圍內可觸發匹配(示例中是在 js 文件或 script 標簽域中觸發代碼段匹配)。下一層鍵字符串(notification)是表示代碼段的描述,將展示在下拉條目的右邊文本;prefix 字段是匹配輸入字符的前綴;body 字段是插入的文本,可以通過 """ 來使用多行語法,body 中 $ 符表示占位符,每按一次 tab 鍵,都會按 ${num} 中的 num 順序移動位置。如果要在占位符位置填充字符的話,可以這樣 ${num: yourString}。示例效果如下: 了解更多
- 在‘自動補全’小節中可以定義返回 snippet 的提示,只需在 suggestion 對象中定義 snippet 和 displayText 屬性即可,不要定義 text 屬性。snippet 語法同第二種方式中基本格式中的body字段, displayText 用于提示展示文本,snippet 是插入文本編輯器的代碼段。
3、創建一個 modal 下拉列表和文檔視圖
ATOM 為實現這兩個功能提供了 npm 包 atom-space-pen-views ,它包括三個視圖類:文本編輯器視圖類( TextEditorView )、滾動文檔視圖類( ScrollView )和下拉選項視圖類( SelectListView ),我們只需繼承視圖類,實現類方法即可。下面重點講下拉列表和滾動視圖類:
模態下拉列表
我們只要提供用于展示的條目給 SelectListView,實現兩個必選方法,它會幫我們的條目渲染成一個下拉列表形式,如下:
// file: search-veiw.js
import { SelectListView } from 'atom-space-pen-views';
class SearchView extends SelectListView {
// keyword:用于初始化列表搜索框值,items:用于展示列表的條目數組,eg: [{name: 'el-button'}, {name: 'el-alert'}]
constructor(keyword, items) {
super(); // 執行 SelectListView 的構造函數
// ATOM API:addModalPanel(options), 添加一個模態框, item 是用于模塊框展示的 DOM 元素,JQuery元素或實現veiw model
this.panel = atom.workspace.addModalPanel({item: this});
// 給下拉列表搜索框寫入文本
this.filterEditorView.setText(keyword);
// 下拉列表可展示條目的最大數目
this.setMaxItems(50);
// 設置用于展示的下拉條目數組
this.setItems(items);
// 鼠標焦距到列表搜索框
this.focusFilterEditor();
}
// 必須實現。自定義列表條目展示視圖,該方法會在 setItems(items) 中單條 item 插入到下拉列表視圖時調用
veiwForItem(item) {
return `<li>${item.name}</li>`;
}
// 必須實現。 當下拉列表條目被選中后觸發,參數 item 為選中條目對象
confirmed(item) {
// todo
this.cancel(); // 選中后關閉視圖
}
// 當列表視圖關閉后調用
cancelled () {
// todo
}
// 搜索框輸入值,按 item 對象哪個鍵值模糊匹配,eg: item.name
getFilterKey() {
return 'name';
}
}
export default SearchView;
視圖效果和 DOM 樹結構如下圖,我們可以看到通過 addModalPanel 把 selectListView 的 HTML 元素添加到模態框元素中了
定義好了視圖類,那怎么渲染展示呢?少年莫慌,我們可以在主文件 activate 方法中注冊命令來觸發視圖展示(當然你可以用其他方式,只要你確定想要觸發時機執行方法就行了):
import SearchView from './search-view.js';
export default {
activate() {
// 實例化一個銷毀容器,便于清除訂閱到 Atom 系統的事件
this.subscriptions = new CompositeDisposable();
// 這里需在 keymaps 目錄下的文件中配置 keymap.
this.subscriptions.add(atom.commands.add('atom-workspace', {
'element-helper:search': () => {
// 獲取當前正在編輯(活躍)的文本編輯器
if (editor = atom.workspace.getActiveTextEditor()) {
// 獲取你光標選中的文本
const selectedText = editor.getSelectedText();
// 獲取光標下的單詞字符串
const wordUnderCursor = editor.getWordUnderCursor({ includeNonWordCharacters: false });
// 用于下拉列表展示的數據, 這里只是個 demo
const items = [{
"type": "basic",
"name": "Button 按鈕",
"path": "button",
"tag": "el-button",
"description": "el-button,Button 按鈕"
}];
// 沒有范圍選中文本,就用當前光標下的單詞
const queryText = selectedText ? selectedText : wordUnderCursor;
// 實例化搜索下拉列表視圖
new SearchView(queryText, items);
}
}
}));
}
}
文檔視圖
Atom 提供了打開一個空白文本編輯器的 API ( atom.workspace.open ) 和注冊 URI 打開鉤子(opener)函數的 API ( atom.workspace.addOpener(opener) ),那么再結合 ScrollView 可以打開一個可滾動的文檔窗口。No BB, show my code:
/**
* file: doc-view.js
* 繼承 ScrollView 類, 實現自己的文檔視圖類
*/
import { Emitter, disposable } from 'atom';
import { ScrollView } from 'atom-space-pen-views';
class DocView extends ScrollView {
// 視圖html
static content(){
// this.div 方法將會創建一個包含參數指定屬性的 div 元素,可以換成其他 html 標簽,eg: this.section()將創建 section 標簽元素
return this.div({class: 'yourClassName', otherAttributeName: 'value'});
}
constructor(uri) {
super();
// 實例化事件監聽器,用于監聽和觸發事件
this.emitter_ = new Emitter();
// 文檔標題,tab名稱
this.title_ = 'Loading...';
this.pane_ = null;
this.uri_ = uri;
}
// 自定義方法,用戶可自定義視圖中展示的內容, 具體可查看 Element-Helper 源碼
setView(args) {
// todo, demo
this.element.innerHTML = '<h1>Welcome to use Atom</h1>';
this.title_ = "Welcome!";
this.emitter_.emit('did-change-title');
}
// 當視圖 html 插入文本編輯器后觸發,注意 view 被激活后才會觸發視圖的插入
attached() {
// 這里可以在視圖插入 DOM 后做些操作,比如監聽事件,操作 DOM 等等
// 通過 atom.workspace.open 打開文本編輯器的URI獲取視圖所在的窗口容器,看下圖比較容易理解什么是窗口容器
this.pane_ = atom.workspace.paneForURI(this.uri_);
this.pane_.activateItem(this);
}
// 文檔標題被激活執行
onDidChangeTitle(callback) {
// 監聽自定義事件
return this.emitter_.on('did-change-title', callback);
}
// 文檔視圖關閉后銷毀函數
detory() {
// 銷毀文檔視圖
this.pane_.destroyItem(this);
// 如果當前窗口容器中只有文檔視圖,那么把容器都銷毀掉
if (this.pane_.getItems().length === 0) {
this.pane_.destroy();
}
}
// 標題改變后觸發事件 did-change-title, callback 內部將調用改方法
getTitle {
return this.title_;
}
}
/**
* 主文件, 這里只寫 activate 函數里的關鍵代碼
*/
import Url from 'url';
import DocView from './doc-view.js';
// ....
// 初始化文檔視圖對象
this.docView_ = null;
// 便于測試沿用上面搜索命令,觸發打開視圖
this.subscriptions.add(atom.commands.add('atom-workspace', {
'element-helper:search': () => {
// 異步打開一個給定 URI 的資源或文本編輯器,URI 定義請看:https://en.wikipedia.org/wiki/Uniform_Resource_Identifier, 參數 split 確定打開視圖的位置,activatePane 是否激活窗口容器
atom.workspace.open('element-docs://document-view', { split: 'right', activatePane: false})
.then(docView => { // docView 是經過 addOpener 添加鉤子處理后的視圖對象,如果沒有相應的 opener 返回數據,默認為文本編輯器對象(TextEditor)
this.docView_ = docView;
// 為docView填充內容,具體展示的內容請在DocView中定義的setView方法中操作
this.docView_.setView(yourArgments);
});
}
});
// 為 URI 注冊一個打開鉤子,一旦 WorkSpace::open 打開 URI 資源,addOpener 里面的方法就會將會執行
this.subscriptions.add(atom.workspace.addOpener((url) => {
if (Url.parse(url).protocol == 'element-docs:') {
return new DocView(url);
}
}));
// ...
如果還對這兩個API有所疑慮,請查看上面提供的鏈接。那么最終效果如下:
寫在最后
這算是我在編寫 Element-Helper 插件時的一些總結和過程吧,實現方式不一定完善或優雅,表述不清楚或錯誤的地方請留言指正。題外話,如果你想開發 VSCode 插件,以 Atom 插件開發為入門也是不錯的選擇,它們都是基于 Electron 框架,很多概念都互通,但 Atom 更易入手和靈活(個人見解)。最后,希望本文能為大家提供些許幫助。Enjoy it!
來自:https://zhuanlan.zhihu.com/p/27913291