如何編寫 Cloud9 JavaScript IDE 的功能擴展

上週末咱們在JSConf.eu發佈了 Cloud9 IDE ,同時發佈了對應的GitHub項目。在4天時間裏該項目獲得340我的的關注和將近50個fork。Cloud9的口號是由"由Javascripters 爲Javascripters建立的IED",這口號有點遞歸,它意味着你能夠hack這個ide使它變得更強大。Cloud9項目開始之初就尤爲注意考慮這點了;Cloud9中的每個功能點都是一個擴展(extension)。在IED啓動的時候咱們用優秀的 requireJS 庫加載全部的擴展。前端UI使用 ajax.org platform (apf),apf 使咱們輕鬆地模塊化Cloud9的用戶界面。下面開始詳細介紹怎樣爲Cloud9編寫擴展。javascript

一個擴展的生命週期是從它做爲requireJS的模塊開始的。我將簡述requireJS的基本語法,想深刻了解requireJS請參考這個文 檔。一個擴展會依賴其餘的擴展和一些核心模塊。咱們將編寫一個給編輯器中選定的JSON代碼進行格式化的擴展。該擴展依賴核心模塊:core/ide, core/ext, core/util 和編輯器管理擴展:ext/editors/editors.讓咱們稱該擴展爲formatjson,而後將其置於ext文件夾下。html

01 require.def("ext/formatjson/formatjson",
02     ["core/ide",
03      "core/ext",
04      "core/util",
05      "ext/editors/editors",
06      "text!ext/formatjson/formatjson.xml"],
07     function(ide, ext, util, editors, markup) {
08  
09 return ext.register("ext/formatjson/formatjson", {
10         //Object definition
11 });
12  
13     }
14 );
jingxing05
jingxing05
翻譯於 昨天(12:55)

0人頂前端

 翻譯的不錯哦! java

require.def第一個參數標識擴展的名字,第二參數中 ide,ext ,util和 editors 表明傳入該擴展依賴的對象引用,formatjson擴展的第五個依賴是加載爲一個文本的xml文件。 ‘text!’ 語法告訴 requireJS 不要將參數引入的文件解析爲 javascript,僅將其中的內容做爲文本返回便可。全部依賴加載完畢後將調用第三個參數表明的回調函數,在回調函數中將咱們的擴展註冊到擴展管理器 中,讓咱們看看擴展文件的結構。
{
    name   : "JSON Formatter",
    dev    : "Your Name Here",
    alone  : true,
    type   : ext.GENERAL,
    markup : markup,

    hook    : function(){},
    init    : function(){},
    enable  : function(){},
    disable : function(){},
    destroy : function(){}
}

屬性和方法詳解:node

屬性

屬性名 是否必須 描述
name 必須 擴展的名字,供管理器中顯示
dev 可選 開發者名字,供擴展管理器中顯示,主要是表彰開發者的榮譽
alone Boolean值,標識該擴展是個獨立的擴展仍是某個擴展的子擴展
type 擴展類型,如今只支持 ext.GENERAL和ext.EDITOR,這個屬性極有可能在將來版本中棄用
markup String,該擴展的UI定義的標記文本
visible Boolean值標識該擴展在加載時是否可見,該屬性僅對 Panel 擴展有效
jingxing05
jingxing05
翻譯於 昨天(13:25)

0人頂git

 翻譯的不錯哦! github

方法

方法名 必須 描述
hook 可選 在擴展註冊時調用該方法,容許你延遲該擴展的初始化。 例如你能夠添加一個菜單項來初始化該擴展。 初始化的時候, markup 參數的值被解析而後調用 init方法。若是沒有定義hook方法,則擴展註冊時會當即初始化。當你指定hook後,就要本身全權負責擴展的初始化。擴展的初始化是由調用 ext.initExtension(_self);完成的,其中 _self 是.Panel 擴展一個個引用。對於panel hook函數 一般只有一單單的一個聲明:panels.register(this);
init 必須 初始化時解析完UI markup標記字符串後 調用該函數。使全部markup中引入的該擴展的元素可用,並能夠對應到其正確的位置。在擴展管理器中啓動該擴展時也會調用該函數。

對editor 型擴展,第一個參數是tab page元素,指示該擴展能夠用之填充到本身的UI.Panel 對panel ,第一個參數應該給this.panel傳一個在Cloud9 UI的panel元素 (一般是window元素)。 ajax

enable 必須 前端啓用該擴展時調用。這個函數是經過在前端菜單中點擊某個panel擴展時被當即調用的(例如點擊完某個菜單項後顯現勾勾的這個動做)。不要與在擴展管理器中的啓用和禁用擴展混淆,啓擴展調用的是 destroy和init方法
disable 必須  前端停用擴展時調用。這個函數是經過在前端菜單中點擊隱藏panel擴展時被當即調用的(例如點擊完某個菜單項後勾勾不顯示的這個動做)。不要與在擴展管理器中的啓用和禁用擴展混淆,啓擴展調用的是 destroy和init方法
destroy 必須 註銷擴展時調用。註銷時清除引入的UI元素,事件處理器,和其餘狀態等。在擴展管理器中禁用擴展時調用。
jingxing05
jingxing05
翻譯於 昨天(14:41)

0人頂json

 翻譯的不錯哦! windows

實現 Format JSON擴展

好,如今咱們已經有了基本概念,讓咱們開始真正來實現 format json擴展。首先完成咱們須要屬性和方法。我將添加一nodes數組,其中包含該擴展所需的全部UI元素。咱們用hook方法來建立一個菜單來初始化 formatjson擴展,並顯示一個格式化窗口接受用戶輸入的縮進值。代碼以下:

{
    name   : "JSON Formatter",
    dev    : "Ajax.org",
    alone  : true,
    type   : ext.GENERAL,
    markup : markup,

    nodes : [],

    hook : function(){
        var _self = this;
        this.nodes.push(
            mnuEdit.appendChild(new apf.item({
                caption : "Format JSON",
                onclick : function(){
                    ext.initExtension(_self);
                    _self.winFormat.show();
                }
            }))
        );
    },

    init : function(amlNode){
        this.winFormat = winFormat;
    },

    enable : function(){
        this.nodes.each(function(item){
            item.enable();
        });
    },

    disable : function(){
        this.nodes.each(function(item){
            item.disable();
        });
    },

    destroy : function(){
        this.nodes.each(function(item){
            item.destroy(true, true);
        });
        this.nodes = [];
        this.winFormat.destroy(true, true);
    }
}

在hook方法中建立一個菜單依附到mnuEdit。mnuEdit是對編輯器菜單的全局引用。如今咱們的UI元素的名字掛靠在全局命名空間下(可能會在未來的版本中變動)。Cloud9中可用的UI元素表以下,並指定了哪些擴展添加了這個元素。

Name Extension Purpose
mnuFile
頂部菜單欄的 File 菜單
mnuEdit
頂部菜單欄的 Edit 菜單
mnuView
頂部菜單欄的 View菜單
mnuEditors
view菜單的 editors
mnuModes
Window菜單的佈局菜單
mnuPanels ext/panels/panels 頂部菜單欄的windows菜單
vbMain
佈局的主vbox
tbMain
主菜單欄
barMenu
菜單
barTools
主菜單欄的第一欄
sbMain
底部的狀態欄
mnuFile

mnuFile

winDbgConsole ext/console/console 控制檯面板
tabConsole ext/console/console 控制檯窗口的tab元素
winFilesViewer ext/tree/tree 樹面板
trFiles ext/tree/tree 樹面板中的樹元素
jingxing05
jingxing05
翻譯於 昨天(15:07)

0人頂

 翻譯的不錯哦!

還有更多建好的元素。能夠在各自的擴展或經過DOM/XPath操做找到他們。例如在工具欄和狀態欄之間有一個hbox包含3個vbox元素。
<a:hbox>
    <a:vbox />
    <a:vbox />
    <a:vbox />
</a:hbox>

能夠用XPath選擇器來訪問元素:

vbMain.selectSingleNode("a:hbox/a:vbox[2]");

這條查詢將定位到hbox中的第二個vbox。這個vbox含有了打開的文件tab和控制檯面板。而後你能夠像咱們在formatjson擴展中對菜單的處理方法同樣將你想要的元素添加到該vbox。

jingxing05
jingxing05
翻譯於 昨天(15:15)

0人頂

 翻譯的不錯哦!

MarkupUI 標記

而後format json 擴展會彈出個窗口給用戶來設置縮進的空格數。咱們用aml標記語法來建立這個窗口。我將aml代碼放到名爲formatjson.xml的xml文件中,並在最外層添加了一個擴展所需的根元素:a:application,看起來像這樣:

<a:application xmlns:a="http://ajax.org/2005/aml">
    <!-- Your UI markup here -->
</a:application>

UI標記能夠包含html和 AML元素。咱們使用AML的一個下拉列表spinner和兩個按鈕來描述對json格式化的窗口。

<a:window
  id        = "winFormat"
  title     = "Format JSON"
  center    = "true"
  modal     = "false"
  buttons   = "close"
  kbclose   = "true"
  width     = "200">
    <a:vbox>
        <a:hbox padding="5" edge="10">
            <a:label width="100">Indentation</a:label>
            <a:spinner id="spIndent" flex="1" min="1" max="20" />
        </a:hbox>
        <a:divider />
        <a:hbox pack="end" padding="5" edge="10 10 5 10">
            <a:button default="2" caption="Format" 
              onclick = "
                require('ext/formatjson/formatjson').format(spIndent.value);
              "/>
            <a:button onclick="winFormat.hide()">Done</a:button>
        </a:hbox>
    </a:vbox>
</a:window>

格式化按鈕綁定了onclick事件來調用咱們擴展的format方法,它傳入了spinner的值。這就是咱們在擴展中須要實現的方法,讓咱們動手吧。

jingxing05
jingxing05
翻譯於 昨天(15:34)

0人頂

 翻譯的不錯哦!

自定義函數

格式化函數有一個參數,來指定json中縮進的空格數。首先獲取當前選擇的代碼,若是選中的代碼爲有效的json,則對其格式化,更新到當前選中的代碼,不然給用戶一個錯誤提示。

咱們須要加載另外一個依賴來完成該功能,就是ace編輯器的Range模塊。因而我在頂部將ace/Range添加到依賴列表中,而後調用參數"Range"。格式化函數看起來以下(我給每一個部分添加了註解)。

{
    ...

    format : function(indent){
        //獲取當前編輯器
        var editor = editors.currentEditor;

        //從當前編輯器獲取選中的對象
        var sel   = editor.getSelection();

        //獲取當前的文檔對象引用
        var doc   = editor.getDocument();

        //獲取當前選中對象的range對象
        var range = sel.getRange();

        //從range對象獲取選中的文本
        var value = doc.getTextRange(range);

        //嘗試將選中的文本轉換爲JSON,並格式化 
        //而後再回轉爲文本字符串,若是出現錯誤則給用戶顯示錯誤.
        try{
            value = JSON.stringify(JSON.parse(value), null, indent);
        }
        catch(e){
            util.alert(
                "Invalid JSON", 
                "The selection contains an invalid or incomplete JSON string",
                "Please correct the JSON and try again");
            return;
        }

        //若是格式化成功則用格式化後值替換掉range對象
        var end = doc.replace(range, value);

        //用格式化的值更新當前選中的部分

sel.setSelectionRange(Range.fromPoints(range.start, end));

},

    ...
}

咱們的擴展示在可使用了,但讓咱們再添加點東西。

jingxing05
jingxing05
翻譯於 昨天(17:27)

0人頂

 翻譯的不錯哦!

Key快捷鍵綁定

我但願使用快捷鍵來使用這個擴展,window使用: Ctrl-Shift-J,mac用Command-Shift-J。Cloud9中用戶能夠自行配置快捷鍵。要實現上述功能,還需幾個步驟。首先在 ext/keybindings_default文件中爲咱們的擴展新添windows和mac的默認鍵綁定部分。

...

"ext" : {
    ...
    "formatjson" : {
        "format" : "Ctrl-Shift-J" // Or "Command-Shift-J" for the mac file
    },
    ...
}

...

而後必需要讓快捷鍵管理器知道該擴展對什麼快捷鍵響應和顯示什麼UI元素。添加名爲hotkeys和hotitems的hash表:

hotkeys  : {"format":1},
    hotitems : {},

如今你有兩種途徑爲鍵綁定添加處理器了。直接的方式是在擴展中添加響應方法,方法的名稱與hotkeys中指定的名稱相同便可,此處就是「format」。由於咱們的json格式化擴展有一個菜單來顯示快捷鍵,我更喜歡將響應方法鏈接到菜單的onclick事件上,這樣當我按下快捷鍵時這個方法被執行。並且當我使用快捷鍵時這個菜單按鈕應該點亮。能夠在hotitems哈希表中添加菜單項來達到目的:

this.hotitems["format"] = [this.nodes[0]];

如今咱們能夠在Tools菜單下的Extendtion Manage中來激活該擴展了,能夠觀看下面這段視頻來看看,如何在3分鐘內完成這個擴展。(視頻下載)

jingxing05
jingxing05
翻譯於 昨天(18:04)

0人頂

 翻譯的不錯哦!

其餘資源

When you need help with creating an extension 在你開發擴展須要幫助的時候請到Cloud9的 Google Group 。能夠向github的issue跟蹤issue tracker of GitHub提交任何你發現的問題。Cloud9的全部開發者在Twitter上十分活躍。在擴展Cloud9的路上祝你好運。我都等不及要看你會擴展出什麼了。 咱們很是樂意將你酷斃了的擴展添加爲Cloud9的子模塊,或者在Github上提交pull request。

玩得開心!

相關文章
相關標籤/搜索