Thursday, September 20, 2012

Sublime Text auto-complete plugin development

Introduction

Sublime Text is my favorite code editor, It's very fast and lightweight and I find it very useful for web development.

Sublime Text has a nice API to create sublime packages (plugins) and today I'm going to show you step by step how to do so.

For more information, please visit the Sublime Text API documentation.

MySignature Plugin

The Sublime Text editor comes with default auto-complete feature which contains the words in your current file. 
you cannot auto-complete words from another file and if you have methods in your code (of course you have), it contains only the method names.

MySignature plugin solves this problem and add to the auto-complete pop-up box methods from all your project files with their signature:


When you choose a method from the pop-up box, It pastes the method with its signature and all you have to do is to replace the method parameters.

Step By Step AutoComplete Plugin Tutorial

Plugins in Sublime Text are written in Python.
You can view my full plugin code on MySignature gihub.

Create the plugin folder

Create your plugin folder inside the sublime plugins folder:
usually: 
\Users\<username>\AppData\Roaming\Sublime Text 2\Packages - on windows
/Users/<username>/Library/Application Support/Sublime Text 2/Packages - on mac

Create a new python file inside your plugin directory.
Example:
mysign.py

Listen to Sublime Text events

This plugin needs to listen to two Sublime Text events:
  1. When file is saved - to load all the methods from all files into the memory.
  2. When auto-complete pop-up box appears - to display the relevant methods inside.
Create a new class which inherit from sublime_plugin.EventListener (see API):

class MyPlugin(MySign, sublime_plugin.EventListener):
    def on_post_save(self, view):
    def on_query_completions(self, view, prefix, locations):

When the plugin is loaded into the memory, on_post_save becomes a callback to file save event and the on_query_completions becomes a callback to auto-complete events. (when you type ctrl+space or type an exists string)   

ON_POST_SAVE(self, view) Method

First, I get all the opened folders in the Sublime Text editor.

open_folder_array = view.window().folders() // view represents the sublime current view object. (see API)
The next step is to scan all the files inside and load the methods into the memory.
Note that this is a heavy task and the editor may stuck so I uses python thread to do it:

- Import python thread library: import threading

- Create a new thread class: class MySignCollectorThread(threading.Thread):

- Create constructor, send the open_folder_array as a parameter and save it as a class data member.

def __init__(self, collector, open_folder_arr, timeout_seconds):
    self.collector = collector
    self.timeout = timeout_seconds
    self.open_folder_arr = open_folder_arr
    threading.Thread.__init__(self)

- Implement the 'run' method which is called when the thread is started:
def run(self):
    for folder in self.open_folder_arr:
       jsfiles = self.get_javascript_files(folder)
       for file_name in jsfiles:
           self.save_method_signature(file_name)
- Implement the recursive method  get_javascript_files to get all files inside the folder:
def get_javascript_files(self, dir_name, *args):
  fileList = []
  for file in os.listdir(dir_name):
   dirfile = os.path.join(dir_name, file)
   if os.path.isfile(dirfile):
    fileName, fileExtension = os.path.splitext(dirfile)
    if fileExtension == ".js" and ".min." not in fileName: // ignore minified files
     fileList.append(dirfile)
   elif os.path.isdir(dirfile):
    fileList += self.get_javascript_files(dirfile, *args)
  return fileList
- Implement the method save_method_signature which search the javascript methods inside a specific file and store it inside an array:
def save_method_signature(self, file_name):
  file_lines = open(file_name, 'rU')
  for line in file_lines:
   if "function" in line:
    matches = re.search('(\w+)\s*[:|=]\s*function\s*\((.*)\)', line)
    if matches != None:
    // store the method somewhere
     ...
     ...
- Create the thread and run it
if self._collector_thread != None:
   self._collector_thread.stop()
   self._collector_thread = MySignCollectorThread(self, open_folder_arr, 30)
   self._collector_thread.start()

ON_QUERY_COMPLETIONS Method

This method is called when auto-complete pop-up box is displayed and this method returns an array with tuples:

[
('<label>', '<text-to-paste>'),
...
...
]

The '<label>' represents the label which will be displayed inside the pop-up box.
This label will be separated with tab ('\t') when the first string is the method signature and the second string is the description (method file in my plugin).
The method description will be displayed in italic font. 

- Implements the on_query_completions:
def on_query_completions(self, view, prefix, locations):
  current_file = view.file_name()
  completions = []
  if self.get_lang(current_file) == 'javascript':
   completions = // get the saved methods from the memory which contains the prefix string
                        // remove duplicate lines
   completions = list(set(completions))
   completions.sort()
                        // return the array
   return (completions,sublime.INHIBIT_EXPLICIT_COMPLETIONS)
- Implements the method get_autocomplete_list
def get_autocomplete_list(self, word):
  autocomplete_list = []
  for method_obj in self._functions:
   if word in method_obj.name():
    method_str_to_append = method_obj.name() + '(' + method_obj.signature()+ ')'
    method_file_location = method_obj.filename();
    autocomplete_list.append((method_str_to_append + '\t' + method_file_location,method_str_to_append))
  return autocomplete_list

Publish Sublime Text Plugin

Sublime Package Control is a repository of Sublime Text plugins.




You can activate it with Ctrl+Shift+P shortcut in the Sublime Text editor, type 'package install' and you get a list of plugins to install with just one click.

To publish your plugin to the Sublime Package Manager you must store it as a project inside Gihub.

These are the steps to publish your plugin:

  1. Push your plugin source code to Github as an open source project.
  2. Fork the 'Sublime Package Manager' project from: https://github.com/wbond/package_control_channel
  3. Add your plugin url from github to the repositories.json file:
Example:
{
     "schema_version": "1.2",
     "repositories": [
         ... 
         "https://github.com/eibbors/Bubububububad",
         "https://github.com/eladyarkoni/MySignaturePlugin",
         "https://github.com/EleazarCrusader/nexus-theme",
         ...
     "package_name_map": {
         ... 
         "modx-sublimetext-2": "MODx Revolution Snippets",
         "MySignaturePlugin": "Autocomplete Javascript with Method Signature",
         "Nette-package-for-Sublime-Text-2": "Nette",
         ...
}

Then, make a pull request with your changes.
Wait for your pull request to be approved by wbond.

That's It, You are now a Sublime Text Plugin Expert :)
Very simple, very useful and you can do whatever you want to make your perfect code editor!

14 comments:

  1. Hi!

    If you're interested in doing the same for Python, use the easy interface of Jedi: https://github.com/davidhalter/jedi#api-design-for-ides

    Cheers!
    David

    ReplyDelete
    Replies
    1. Thanks. its very cool. I'm consider to do something similar for javascript :)

      Delete
  2. Hi! Sorry for my bad Engish. I need autocomplete from my personal dictionary (maybe .txt file with words). This dictionary was dont included to project. As example, I use Sublime for write code on Lua for game framefork and I want add functions of that framework to autocomplete. Is it possible with that plugin?

    ReplyDelete
  3. Hi Dmitry,

    This plugin detects functions inside Javascript files (files with .js extension.) which included inside your project directory.
    If the framework is a part of your project and its not minified, MySign plugin will detect its functions.
    I hope that I answered your question.
    Elad.

    ReplyDelete
  4. Elad,

    This is tremendous! Thanks!

    -- Bobby

    ReplyDelete
  5. hello Elad,

    for install the plugin on mac i just need to drag the folder "MySignaturePlugin-master" to "Packages" ?
    i didn't understand all the above explanations (just a beginner)
    does the plgin available via package manager?

    best regards

    muli

    ReplyDelete
  6. Would you like to support Sublime Text 3?

    ReplyDelete
  7. Yes indeed!
    As soon as It come out from beta.

    ReplyDelete
    Replies
    1. the troops are chomping at their bits for sublime 3

      Delete
  8. Hi Elad,

    This is awesome plugin, just what I needed for my project. I edited this plugin to work with Lua. Is it ok to use and share it it if I credit you in the source?

    There is no license information in this site or in GitHub. Can I assume that this is licensed with MIT license or something similar?

    ReplyDelete
  9. Hi.
    Im very glad to know that my plugun helps you. You can use it as a MIT license and give me some credit.
    Thank you.

    ReplyDelete
  10. Hi Elad. I'm beginner. Can you please describe step-by-spep:
    1) howto install this wonderful plugin on my Windows + ST3. I did not find it in Packadge Control => Install Package.
    2) Howto run it ? I have some .js in my folder. When I creating new file and start typing the name of some func which located in another file nothing happing.. running only default text autocomplete.
    Many thanks=)

    ReplyDelete