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!

Wednesday, September 5, 2012

Mobifying (Modifying for mobile) your website


Hi,
I want to share with you one of my presentations about how to modify your website to work on mobile (tablets and smartphones).
This presentation contains best practices and HTML5 features that you must know in order to make your website perfectly working on mobile devices.
I presented this a year ago at one of my company technology meetings.
Enjoy. 

Draw Something JS - Part #2

In my previous post, I talked about how to draw lines with different widths and colors on HTML5 canvas elements, very easy!
In this post, We will discuss about how to record the user movements and "play" it on the canvas.
The main idea is to save each mouse position to an array, loop over the array and paint on the canvas element.
The Array definition in Javascript: 
var recordDataArray = []; // best practice to use [] instead of new Array();
On each mousedown/mousemove event we need to push to the array the following data:
  • mouse x position
  • mouse y position
  • event type (mousedown to begin a new line path or mousemove to continue the line path on the canvas)
  • line color
  • line width
Define the event type enum in Javascript:
var DrawEventType = {
    START: 0,
    MOVE: 1
}
Now, We will modify our functions from the previous post to push the data to the array:
 var startPaintWithMouse = function(event) {
            mouseState = 1;
            brushCtx = board.getContext('2d');
            brushCtx.beginPath();
            brushCtx.lineWidth = lineWidth;
            brushCtx.strokeStyle = color;
            brushCtx.moveTo(event.offsetX,event.offsetY);
            // push to array
            recordDataArray.push({
                  type: DrawEventType.START,
                  x:event.offsetX,
                  y:event.offsetY,
                  color:color,
                 width:lineWidth
             });
};
var movePaintWithMouse = function(event) {
             if (mouseState == 1) {
                   event.preventDefault();
                   brushCtx = board.getContext('2d');
                   brushCtx.lineTo(event.offsetX,event.offsetY);
                   brushCtx.stroke();
                   recordDataArray.push({                            type:DrawEventType.MOVE,
                            x:event.offsetX,
                            y:event.offsetY,
                            color:color,
                            width:lineWidth
                   });
            }
};
We can create a button that clear the canvas.
Clearing the canvas in Javascript is easy too:
var brushCtx = board.getContext('2d');
brushCtx.clearRect(0, 0, board.width, board.height);
And now, we can create a play button that loop over the array and paint the user movements on the canvas.
We need to use the setInterval javascript method to give the user the drawing experience.
var playDataArray = function(dataArray,speed) {
            var dataIndex = 0;
            var playInterval = setInterval(function(){
                   if (typeof(dataArray[dataIndex]) == 'object') {
                            drawDataObject(dataArray[dataIndex]);
                            dataIndex++;
                   } else {
                            clearInterval(playInterval);
                   }
            },speed);
};
On each interval iteration we call the drawDataObject method which gets an object from our array and draw it.
var drawDataObject = function(data) {
            if (data.type == DrawEventType.START) {
                       brushCtx = board.getContext('2d');
                       brushCtx.beginPath();
                       brushCtx.lineWidth = data.width;
                       brushCtx.strokeStyle = data.color;
                       brushCtx.moveTo(data.x,data.y);
            } else if (data.type == DrawEventType.MOVE) {
                       brushCtx = board.getContext('2d');
                       brushCtx.lineTo(data.x,data.y);
                       brushCtx.stroke();
            }
};
That's all!

Draw Something JS - Part #1

This is my first post, so please be gentle with your comments :) 
Today, You are going to see the power of HTML5 and Javascript and I’m going to show you, step by step, how to create the Great DrawSomething application in Javascript.

DrawSomething JS



Ready?!?! so lets go.

The first thing we’re going to learn is how to draw inside your browser:

HTML5 has a new element called Canvas, to add a canvas inside your html, you just need to copy this code inside the html page:

<canvas id=”board” class=”board” ></canvas>

To draw inside this canvas with the mouse, we need to listen to three mouse events:
  • mousedown - to start drawing the line
  • mousemove - to continue drawing the line
  • mouseup - to stop drawing the line
var board = document.getElementById(“board”);
board.addEventListener(“mousedown”,startPaintWithMouse,false);
board.addEventListener(“mousemove”,movePaintWithMouse,false);
board.addEventListener(“mouseup”,stopPaintWithMouseUp,false);
Here are the methods implementation:
var startPaintWithMouse = function(event) {
    brushCtx = board.getContext(‘2d’); // get the context of the canvas
    brushCtx.beginPath();
    brushCtx.lineWidth = lineWidth; // set line width from variable
    brushCtx.strokeStyle = color; // set line color from variable
    brushCtx.moveTo(event.offsetX,event.offsetY); // start drawing from this point
};
var movePaintWithMouse = function(event) {
    if (mouseState == 1) {
        event.preventDefault();
        brushCtx = board.getContext(‘2d’);
        brushCtx.lineTo(event.offsetX,event.offsetY); // go to this point and draw
        brushCtx.stroke(); // stroke the line
    }
};
var stopPaintWithMouseUp = function(evet) {
    mouseState = 0; // set mouse state to 0 to stop the draw
};

Now, just add the buttons that change the line width and color and start to draw inside the html5 canvas element.
In the next post, We will learn how to record the user movements and to play it, just like the DrawSomething app.

My First Post

"It’s really hard to design products by focus groups. A lot of times, people don’t know what they want until you show it to them" (Steve Jobs)