如何開發 Sublime Text 2 的外掛

oschina發表於2013-09-11

  Sublime Text 2是一個高度可定製的文字編輯器,一直以來對希望有一個快速強大現代的編輯工具的的程式設計師保持著持續的吸引力。現在,我們將建立自己的一個Sublime plugin,實現用Nettuts+ Prefixr API處理CSS實現跨瀏覽器CSS的目的。

  當完成時,你會深入的明瞭如何建立一個 Sublime Prefixr plugin,並且有能力去寫你自己的編輯器外掛。

 前言:術語和參考資料

The extension model for Sublime Text 2 is fairly full-featured.

  Sublime Text 2的擴充套件模型是相當的功能全面。你可以改變語法高亮,實際的編輯器外觀,以及所有的選單項。此外,還可以建立新的build環境,自動補全,語言定義,程式碼區段,巨集,鍵繫結,滑鼠繫結以及外掛。所有這些不同形式的改裝都是用組織在package中的檔案來實現的。

  所謂pacakage就是一個儲存在你的Packages目錄中的資料夾。你可以點選Preferences > Browse Packages… 選單進入你的Packages目錄。也可以通過建立一個zip檔案並且把副檔名改為.sublime-package來實現把pacakage打包成一個單獨檔案。我們將在本教程中討論一點怎麼打包。

  Sublime繫結了很多不同的package。大不多數繫結的都是和特定語言相關的package,包括語言定義,自動補全以及build環境。除了語言相關的package,還有兩個Default和User package。Defaultpackage包含了所有的標準鍵繫結,選單定義,檔案設定和一大堆用python寫的外掛。

During the process of writing a plugin, the   Sublime Text 2 API reference will be essential.

  要寫一個外掛, Sublime Text 2 API reference是根本。此外,Defaultpackage對於怎麼做我們的工作也是一個很好的參考。編輯器的大部分功能都是通過commans命令來實現,除了敲入字元之外的所有操作都可以通過commans完成。檢視Preferences > Key Bindings – Defaultmenu ,你可以找到很多有用的內建的功能。

  現在,pacakge和產檢的區別已經清楚了,可以開始寫我們的外掛了。

 第一步 - 起步

  Sublime有一個功能可以產生一個簡單外掛所需要的Python程式碼框架。選擇Tools > New Plugin…選單,可以開啟一個新的檔案,帶有下面的樣式:

import sublime, sublime_plugin

class ExampleCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        self.view.insert(edit, 0, "Hello, World!")

  可以看到,引入了兩個Sublime Python的模組,使得我們可以訪問其API並且建立一個新的類。在開始編輯建立我們自己的外掛之前,請先儲存這個檔案。

  要儲存這個檔案我們需要建立一個package來儲存它。 按下 ctrl+s(Windows/Linux) or cmd+s (OS X) 來儲存檔案。儲存對話方塊預設開啟Userpackage,不要把我們的檔案存在那裡,而是建立一個新的資料夾,命名為Prefixr。

Packages/
…
- OCaml/
- Perl/
- PHP/
- Prefixr/
- Python/
- R/
- Rails/
…

  現在,把我們的檔案儲存在Prefixr資料夾中,命名為Prefixr.py。其實檔名並不重要,只要以.py為副檔名就可以。但方便起見,還是用我們的外掛的名字吧。

  現在,外掛已經做了儲存。我們可以試著執行了。輸入 ctrl+`開啟Sublime的控制檯,這是一個可以訪問API的Python控制檯。輸入下面的Python程式碼來測試我們的新外掛:

view.run_command('example')

  你將看到Hello World被插入到了我們的外掛檔案的開頭。接下來繼續之前先Undo掉這個新的插入。

 第二步 - Comman的型別和名字

  對於一個外掛,Sublime提供了三種型別的command。

  • Text commands 提供通過一個View物件訪問被選定的檔案或者buffer的內的能力
  • Window commands 提供一個Window物件,可引用當前的視窗
  • Application commands 沒有引用任何特定的視窗,檔案或者buffer,很少使用。

  因為我們要用我們的外掛來操作CSS檔案或者buffer裡面的內容,所以我們要使用 sublime_plugin.TextCommand 類作為我們定製的Prefixr命令的基類。這時,我們就需要命名我們命令的類名了。

  在我們的程式碼框架中,你可以看到下面的類:

class ExampleCommand(sublime_plugin.TextCommand):

  在我們執行命令時,在控制檯中執行的是下面的程式碼:

view.run_command('example')

  Sublime將把繼承自任意一個 sublime_plugin類(TextCommand,WindowCommand or ApplicationCommand)的類的名字的Command字尾去掉,並且用下劃線符號命名替換駝峰式命名。

  這樣一來,為建立一個名字是prefixr的command,類名就必須是PrefixrCommand。

class PrefixrCommand(sublime_plugin.TextCommand):

 第三步 - 選定文字

  Sublime最有用的功能之一就是具備多行選定的功能

  現在,我們已經正式命名了我們的外掛,可以開始從當前的buffer中獲取CSS並且傳送到Prefixr API上了。Sublime最有用的功能之一就是具備多行選定的功能。由於要獲取選定的檔案,我們需要把所有選定的行放入我們的外掛中處理,而不僅僅是第一個選定的。

  由於我們寫的是一個文字命令,所以可以通過self.view訪問當前view。view物件的self()方法將返回一個當前選定內容的iterable Region集合,我們可以通過花括號掃描到這些內容。若找不到花括號,可以擴大選定內容到周圍的括號,以保證整個塊有一個括號字首。選定內容中是否包含花括號還將有利於我們後面對Prefixr API返回的內容作空白調整和格式調整。

braces = False
sels = self.view.sel()
for sel in sels:
    if self.view.substr(sel).find('{') != -1:
        braces = True

  用這幾行程式碼替換框架中的run()方法中的程式碼。

  若未找到任何的花括號,我們需要迴圈檢測每一個選定區段,把每一個區段和後括弧關聯起來。之後,用帶有to引數設定為 brackets的內建命令 expand_selectionl來確保獲取了每個CSS塊的完整內容。

if not braces:
    new_sels = []
    for sel in sels:
        new_sels.append(self.view.find('\}', sel.end()))
    sels.clear()
    for sel in new_sels:
        sels.add(sel)
    self.view.run_command("expand_selection", {"to": "brackets"})

  若果你想再檢查一次你的程式碼,你可以和原始碼zip檔案中的Prefixr1.py檔案對比一下。

 第四步 - 執行緒

為防止糟糕的連線破壞其他正常工作,我們需要確保在後臺完成Prefixr API呼叫。

  此時,選定的文字已經擴充套件到了能抓取每個CSS塊的完整內容。現在,我們需要把他們傳送打牌Prefixr API上。這隻需要一個簡單的HTTP請求,用urllib和urllib2模組就可以實現。但是,在發起請求之前,需要想想一個潛在的web請求延遲會對編輯器效能造成的影響。如因為某些原因,使用者處在一個很慢的連線環境下,對Prefixr API的請求很可能需要好幾秒鐘乃至更多。

  為防止糟糕的連線破壞其他正常工作,我們需要確保在後臺完成Prefixr API呼叫。若你不瞭解執行緒,很基礎的一種解釋就是,執行緒是使你的程式的多個程式碼塊在同一時間執行的機制。這在我們的外掛環境中是很重要的,因為這樣做可以避免在向Prefixr傳送資料和等待響應的過程中Sublime處於不可用狀態。

 第五步 - 建立執行緒

  我們將使用Python threading 模組來建立執行緒。要使用該模組,需要建立一個繼承threading的新類。Thread包含一個所有執行緒程式碼都在其中執行的run()方法。

class PrefixrApiCall(threading.Thread):
    def __init__(self, sel, string, timeout):
        self.sel = sel
        self.original = string
        self.timeout = timeout
        self.result = None
        threading.Thread.__init__(self)

    def run(self):
        try:
            data = urllib.urlencode({'css': self.original})
            request = urllib2.Request('http://prefixr.com/api/index.php', data,
                headers={"User-Agent": "Sublime Prefixr"})
            http_file = urllib2.urlopen(request, timeout=self.timeout)
            self.result = http_file.read()
            return

        except (urllib2.HTTPError) as (e):
            err = '%s: HTTP error %s contacting API' % (__name__, str(e.code))
        except (urllib2.URLError) as (e):
            err = '%s: URL error %s contacting API' % (__name__, str(e.reason))

        sublime.error_message(err)
        self.result = False

  這裡,我們使用thread的__init__()方法來設定所有的在web請求中需要的資料變數。run()方法包含所有的設定程式碼和執行向Prefixr API 的http請求的程式碼。由於執行緒併發的執行,所以直接返回值是不可行的,取而代之的我們設定self.result作為呼叫的結果。

  鑑於我們在我們的外掛中開始使用其他一些模組,我們必須在程式頂端增加import語句。

import urllib
import urllib2
import threading

  現在我們有了一個執行緒類來執行HTTP請求,我們需要為每一個selection塊建立一個執行緒。為此,回到我們的PrefixrCommand類的run方法中,使用下面的迴圈:

threads = []
for sel in sels:
    string = self.view.substr(sel)
    thread = PrefixrApiCall(sel, string, 5)
    threads.append(thread)
    thread.start()

  我們記錄了每一個我們建立的執行緒,然後呼叫start()方法開啟每一個執行緒。如果想再次檢查你的工作,對比原始碼檔案zip檔案中的filePrefixr1.py檔案。

 第六步 - 準備結果

  現在,我們已經開始了實際的Prefixr API請求,在處理HTTP相應之前,我們需要處理最後幾個問題。

  首先,我們清楚所有的selection,因為之前我們修改了他們。稍後將把他們設定成一個更合理的狀態。

self.view.sel().clear()

  此外,我們開啟一個新的Edit物件,把undo和redo操作組織在一起。我們指定我們在為prefix命令建立一個這樣的組。

edit = self.view.begin_edit('prefixr')

  作為最後一步,我們呼叫下面的方法來處理API的相應結果。

 第七步 - 處理執行緒

  此時,我們的執行緒已經在執行了,認知已經執行結束了。下一次,我們需要完成剛剛呼叫的handle_threads()方法。這一方法將迴圈處理執行緒列表並且尋找不再執行的執行緒。

def handle_threads(self, edit, threads, braces, offset=0, i=0, dir=1):
    next_threads = []
    for thread in threads:
        if thread.is_alive():
            next_threads.append(thread)
            continue
        if thread.result == False:
            continue
        offset = self.replace(edit, thread, braces, offset)
    threads = next_threads

  如果執行緒仍然執行著,我們將其加入執行緒列表一遍後面再檢測。如果result是False,則忽略它。然後,對於返回正確的結果,再呼叫一個馬上就要說到的replace()方法。

  原文地址:http://net.tutsplus.com/tutorials/python-tutorials/how-to-create-a-sublime-text-2-plugin/

相關文章