Cómo crear un complemento de texto sublime 2

Sublime Text 2 es un editor de texto altamente personalizable que ha capturado cada vez más la atención de los programadores que buscan una herramienta que sea potente, rápida y moderna. Hoy, vamos a recrear mi popular complemento Sublime que envía CSS a través de la API de Nettuts + Prefixr para un fácil CSS de múltiples navegadores..

Cuando termine, comprenderá bien cómo está escrito el complemento Sublime Prefixr y estará preparado para comenzar a escribir sus propios complementos para el editor!


Prefacio: Terminología y Material de Referencia.

El modelo de extensión para Sublime Text 2 es bastante completo..

El modelo de extensión para Sublime Text 2 es bastante completo. Hay formas de cambiar el resaltado de sintaxis, el cromo real del editor y todos los menús. Además, es posible crear nuevos sistemas de compilación, completaciones automáticas, definiciones de idioma, fragmentos de código, macros, enlaces de teclas, enlaces de mouse y complementos. Todos estos diferentes tipos de modificaciones se implementan a través de archivos organizados en paquetes..

Un paquete es una carpeta que se almacena en su Paquetes directorio. Puede acceder a su directorio de Paquetes haciendo clic en Preferencias> Buscar paquetes ... Entrada del menú. También es posible agrupar un paquete en un solo archivo creando un archivo zip y cambiando la extensión a .paquete sublime. Discutiremos el empaquetado un poco más adelante en este tutorial..

Sublime viene con una gran cantidad de paquetes diferentes. La mayoría de los paquetes son específicos del idioma. Estos contienen definiciones de lenguaje, auto-completaciones y sistemas de compilación. Además de los paquetes de idiomas, hay otros dos paquetes: Defecto y Usuario. los
Defecto el paquete contiene todos los enlaces de teclas estándar, definiciones de menú, configuración de archivos y un montón de complementos escritos en Python. los Usuario El paquete es especial porque siempre se carga el último. Esto permite a los usuarios anular los valores predeterminados personalizando los archivos en sus Usuario paquete.

Durante el proceso de escritura de un complemento, la referencia a la API de Sublime Text 2 será esencial.

Durante el proceso de escribir un complemento, la referencia a la API de Sublime Text 2 será esencial. además, el Defecto El paquete actúa como una buena referencia para descubrir cómo hacer las cosas y qué es posible. Gran parte de la funcionalidad del editor se expone a través de comandos. Cualquier operación que no sea escribir caracteres se realiza a través de comandos. Viendo el Preferencias> Enlaces de teclas - Predeterminadoentrada de menú, es posible encontrar un tesoro de funcionalidad incorporada.

Ahora que la distinción entre un complemento y un paquete es clara, comencemos a escribir nuestro complemento.


Paso 1 - Iniciar un complemento

Sublime viene con una funcionalidad que genera un esqueleto de código Python necesario para escribir un complemento simple. Selecciona el Herramientas> Nuevo Plugin ... entrada de menú, y un nuevo búfer se abrirá con este boilerplate.

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

Aquí puede ver que se importan los dos módulos de Suthime Python para permitir el uso de la API y se crea una nueva clase de comando. Antes de editar esto y comenzar a crear nuestro propio complemento, guardemos el archivo y activemos la funcionalidad incorporada.

Cuando guardemos el archivo, vamos a crear un nuevo paquete para almacenarlo. Presione Ctrl + s (Windows / Linux) o cmd + s (OS X) para guardar el archivo. Se abrirá el cuadro de diálogo Guardar. Usuario paquete. No guarde el archivo allí, sino que busque una carpeta y cree una nueva carpeta llamada Prefijo.

Paquetes /… - OCaml / - Perl / - PHP / - Prefixr / - Python / - R / - Rieles /… 

Ahora guarde el archivo dentro de la carpeta Prefixr como Prefijo. En realidad, no importa cuál sea el nombre del archivo, solo termina en .py. Sin embargo, por convención usaremos el nombre del complemento para el nombre de archivo.

Ahora que el plugin está guardado, probémoslo. Abre la consola Sublime pulsando Ctrl + '. Esta es una consola de Python que tiene acceso a la API. Ingrese el siguiente Python para probar el nuevo complemento:

view.run_command ('ejemplo')

Debería ver Hola Mundo insertado en el principio del archivo de plugin. Asegúrate de deshacer este cambio antes de continuar.


Paso 2 - Tipos de comandos y nombres

Para los complementos, Sublime proporciona tres tipos diferentes de comandos.

  • Comandos de texto proporcionar acceso a los contenidos del archivo / búfer seleccionado a través de un Ver objeto
  • Comandos de ventana proporcionar referencias a la ventana actual a través de una Ventana objeto
  • Comandos de aplicación no tiene una referencia a ninguna ventana o archivo / búfer específico y se usa más raramente

Ya que manipularemos el contenido de un archivo / búfer CSS con este complemento, vamos a utilizar el sublime_plugin.TextCommand clase como la base de nuestro comando personalizado Prefixr. Esto nos lleva al tema de nombrar clases de comandos..

En el esqueleto de complementos proporcionado por Sublime, notará la clase:

clase ExampleCommand (sublime_plugin.TextCommand):

Cuando quisimos ejecutar el comando, ejecutamos el siguiente código en la consola:

view.run_command ('ejemplo')

Sublime tomará cualquier clase que extienda uno de los sublime_plugin clases
(TextCommand, WindowCommand o AplicaciónComando), quite el sufijo Mando y luego convertir el El caso de Carmeldentro anotación de subrayado para el nombre del comando.

Así, para crear un comando con el nombre. prefijo, la clase necesita ser PrefixrCommand.

clase PrefixrCommand (sublime_plugin.TextCommand):

Paso 3 - Seleccionando Texto

Una de las características más útiles de Sublime es la capacidad de tener múltiples selecciones.

Ahora que tenemos nuestro complemento con el nombre correcto, podemos comenzar el proceso de capturar CSS del búfer actual y enviarlo a la API de Prefixr. Una de las características más útiles de Sublime es la capacidad de tener múltiples selecciones. Como estamos agarrando el texto seleccionado, debemos escribir nuestro conector en el asa no solo de la primera selección, sino de todos..

Dado que estamos escribiendo un comando de texto, tenemos acceso a la vista actual a través de autoevaluación. los sel () método de la Ver objeto devuelve un iterable RegionSet de las selecciones actuales. Comenzamos por escanear a través de estos para las llaves. Si las llaves no están presentes, podemos expandir la selección a las llaves circundantes para asegurarnos de que todo el bloque esté prefijado. Si nuestra selección incluye llaves o no también será útil más adelante para saber si podemos ajustar el espacio en blanco y el formato en el resultado que obtenemos de la API de Prefixr.

llaves = Falsas sels = self.view.sel () para sel in sels: if self.view.substr (sel) .find ('')! = -1: braces = True

Este código reemplaza el contenido del esqueleto. correr() método.

Si no encontramos llaves, hacemos un bucle a través de cada selección y ajustamos las selecciones a la llave de cierre más cercana. A continuación, utilizamos el comando incorporado. expand_selection con el a arg establecido en soportes Para asegurarnos de que tenemos los contenidos completos de cada bloque CSS seleccionado..

si no son llaves: new_sels = [] para sel in sels: new_sels.append (self.view.find ('\', sel.end ())) sels.clear () para sel en new_sels: sels.add (sel ) self.view.run_command ("expand_selection", "to": "brackets")

Si desea verificar su trabajo hasta el momento, compare la fuente con el archivo Prefixr-1.py en el archivo zip de código fuente.


Paso 4 - Roscado

Para evitar que una conexión deficiente interrumpa otros trabajos, debemos asegurarnos de que las llamadas a la API de Prefixr se realicen en segundo plano.

En este punto, las selecciones se han expandido para capturar el contenido completo de cada bloque CSS. Ahora, necesitamos enviarlos a la API de Prefixr. Esta es una solicitud HTTP simple, la cual usaremos urllib y urllib2 módulos para. Sin embargo, antes de comenzar a despedir solicitudes web, debemos pensar en cómo una solicitud web potencialmente lenta podría afectar el rendimiento del editor. Si, por alguna razón, el usuario tiene una latencia alta o una conexión lenta, las solicitudes a la API de Prefixr podrían demorar un par de segundos o más..

Para evitar que una conexión deficiente interrumpa otros trabajos, debemos asegurarnos de que las llamadas a la API de Prefixr se realicen en segundo plano. Si no sabe nada acerca de los subprocesos, una explicación muy básica es que los subprocesos son una forma en que un programa puede programar múltiples conjuntos de código para que se ejecuten aparentemente al mismo tiempo. Es esencial en nuestro caso porque permite que el código que está enviando datos y esperando una respuesta de la API de Prefixr impida que el resto de la interfaz de usuario Sublime se congele.


Paso 5 - Creación de hilos

Estaremos usando el Python enhebrar Módulo para crear hilos. Para utilizar el módulo de subprocesamiento, creamos una nueva clase que se extiende hilo de rosca. llamado PrefixrApiCall. Clases que se extienden hilo de rosca. Incluir un correr() Método que contiene todo el código para ser ejecutado en el hilo..

clase 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 excepto (urllib2.HTTPError) as (e): err = '% s: error HTTP% s contactando API '% (__name__, str (e.code)) excepto (urllib2.URLError) como (e): err ='% s: URL error% s contactando API '% (__name__, str ( e.reason)) sublime.error_message (err) self.result = False

Aquí usamos el hilo. __en eso__() Método para establecer todos los valores que se necesitarán durante la solicitud web. los correr() El método contiene el código para configurar y ejecutar la solicitud HTTP para la API de Prefixr. Dado que los subprocesos funcionan simultáneamente con otro código, no es posible devolver valores directamente. En lugar de eso, establecemos auto.resultado al resultado de la llamada.

Como recién comenzamos a usar más módulos en nuestro complemento, debemos agregarlos a las declaraciones de importación en la parte superior de la secuencia de comandos..

importar urllib importar urllib2 importar hilos

Ahora que tenemos una clase de subprocesos para realizar las llamadas HTTP, debemos crear un subproceso para cada selección. Para ello saltamos de nuevo a la correr() método de nuestro PrefixrCommand clase y usar el siguiente bucle:

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

Realizamos un seguimiento de cada hilo que creamos y luego llamamos al comienzo() método para comenzar cada.

Si desea verificar su trabajo hasta el momento, compare la fuente con el archivo Prefixr-2.py en el archivo zip de código fuente.


Paso 6 - Preparación para los resultados

Ahora que hemos comenzado las solicitudes reales de la API de Prefixr, necesitamos configurar algunos últimos detalles antes de manejar las respuestas.

Primero, borramos todas las selecciones porque las modificamos anteriormente. Más tarde volveremos a ponerlos en un estado razonable..

self.view.sel (). clear ()

Además comenzamos un nuevo Editar objeto. Esto agrupa las operaciones para deshacer y rehacer. Especificamos que estamos creando un grupo para el prefijo mando.

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

Como paso final, llamamos a un método que escribiremos a continuación que manejará el resultado de las solicitudes de API..

self.handle_threads (editar, hilos, llaves)

Paso 7 - Manipulación de hilos

En este punto, nuestros hilos se están ejecutando, o posiblemente incluso se han completado. A continuación, necesitamos implementar el handle_threads () método que acabamos de hacer referencia. Este método recorrerá la lista de subprocesos y buscará subprocesos que ya no se estén ejecutando.

def handle_threads (self, edit, threads, braces, offset = 0, i = 0, dir = 1): next_threads = [] para threads en threads: if thread.is_alive (): next_threads.append (thread) continúa en thread. resultado == Falso: continuar offset = self.replace (editar, subproceso, llaves, offset) threads = next_threads

Si un hilo aún está vivo, lo agregamos a la lista de hilos para verificarlo más tarde. Si el resultado fue un error, lo ignoramos, sin embargo, para obtener buenos resultados, llamamos a un nuevo reemplazar() método que estaremos escribiendo pronto.

Si hay algún subproceso que aún esté vivo, debemos verificarlo nuevamente en breve. Además, es una buena mejora de la interfaz de usuario para proporcionar un indicador de actividad para mostrar que nuestro complemento todavía se está ejecutando.

if len (hilos): # Esto anima un pequeño indicador de actividad en el área de estado antes = i% 8 después = (7) - antes si no después: dir = -1 si no antes: dir = 1 i + = dir self. view.set_status ('prefixr', 'Prefixr [% s =% s]'% \ ("* before," * after)) sublime.set_timeout (lambda: self.handle_threads (editar, hilos, llaves, desplazamiento, i, dir), 100) volver

La primera sección del código utiliza un valor entero simple almacenado en la variable yo para mover un = ida y vuelta entre dos soportes. La última parte es la más importante. Esto le dice a Sublime que ejecute el handle_threads ()Método de nuevo, con nuevos valores, en otros 100 milisegundos. Esto es como el setTimeout () función en JavaScript.

los lambda La palabra clave es una característica de Python que nos permite crear una nueva función anónima o sin nombre.

los sublime.set_timeout () el método requiere una función o método y el número de milisegundos hasta que se debe ejecutar. Sin lambda podríamos decir que queríamos correr handle_threads (), pero no podríamos especificar los parámetros.

Si todos los subprocesos se han completado, no necesitamos establecer otro tiempo de espera, sino que terminamos nuestro grupo de deshacer y actualizamos la interfaz de usuario para que el usuario sepa que todo está hecho..

self.view.end_edit (editar) self.view.erase_status ('prefixr') selecciones = len (self.view.sel ()) sublime.status_message ('Prefixr se ejecuta con éxito en% s selection% s'% (selecciones, " si selecciones == 1 else 's'))

Si desea verificar su trabajo hasta el momento, compare la fuente con el archivo Prefixr-3.py en el archivo zip de código fuente.


Paso 8 - Realizando reemplazos

Con nuestros hilos manejados, ahora solo tenemos que escribir el código que reemplaza el CSS original con el resultado de la API de Prefixr. Como mencionamos anteriormente, vamos a escribir un método llamado reemplazar().

Este método acepta una serie de parámetros, incluyendo la Editar objeto para deshacer, el hilo que tomó el resultado de la API de Prefixr, si la selección original incluía llaves, y finalmente el desplazamiento de la selección.

def replace (self, edit, thread, braces, offset): sel = thread.sel original = thread.original result = thread.result # Aquí ajustamos cada selección para cualquier texto que ya hayamos insertado si offset: sel = sublime.Region (sel.begin () + offset, sel.end () + offset)

El desplazamiento es necesario cuando se trata de selecciones múltiples. Cuando reemplazamos un bloque de CSS con el CSS prefijado, la longitud de ese bloque aumentará. El desplazamiento asegura que estamos reemplazando el contenido correcto para las selecciones subsiguientes ya que las posiciones del texto se desplazan en cada reemplazo.

El siguiente paso es preparar el resultado de la API de Prefixr para incluirlo como CSS de reemplazo. Esto incluye la conversión de finales de línea y sangría para coincidir con el documento actual y la selección original.

result = self.normalize_line_endings (result) (prefix, main, suffix) = self.fix_whitespace (original, result, sel, braces) self.view.replace (edit, sel, prefix + main + sufix)

Como paso final, configuramos la selección del usuario para que incluya el final de la última línea del nuevo CSS que insertamos, y luego devolvemos el desplazamiento ajustado para usarlo en cualquier otra selección..

end_point = sel.begin () + len (prefix) + len (main) self.view.sel (). add (sublime.Region (end_point, end_point)) return offset + len (prefix + main + sufix) - len ( original)

Si desea verificar su trabajo hasta el momento, compare la fuente con el archivo Prefixr-4.py en el archivo zip de código fuente.


Paso 9 - Manipulación de espacios en blanco

Usamos dos métodos personalizados durante el proceso de reemplazo para preparar el nuevo CSS para el documento. Estos métodos toman el resultado de Prefixr y lo modifican para que coincida con el documento actual.

normalize_line_endings () toma la cadena y se asegura de que coincida con los finales de línea del archivo actual. Usamos el Ajustes clase de la API Sublime para obtener los finales de línea adecuados.

def normalize_line_endings (self, string): string = string.replace ('\ r \ n', '\ n'). replace ('\ r', '\ n') line_endings = self.view.settings (). get ('default_line_ending') si line_endings == 'windows': string = string.replace ('\ n', '\ r \ n') elif line_endings == 'mac': string = string.replace ('\ n', '\ r') devuelve cadena

los fix_whitespace () El método es un poco más complicado, pero hace el mismo tipo de manipulación, solo para la sangría y el espacio en blanco en el bloque CSS. Esta manipulación solo funciona con un solo bloque de CSS, por lo que salimos si se incluyeron uno o más llaves en la selección original.

def fix_whitespace (self, original, prefixed, sel, braces): # Si hay llaves disponibles, podemos hacer toda la magia de los espacios en blanco si las llaves: return (", prefixed,")

De lo contrario, comenzamos por determinar el nivel de sangría del CSS original. Esto se hace buscando espacios en blanco al comienzo de la selección.

(row, col) = self.view.rowcol (sel.begin ()) indent_region = self.view.find ('^ \ s +', self.view.text_point (row, 0)) si self.view.rowcol ( indent_region.begin ()) [0] == row: indent = self.view.substr (indent_region) else: indent = "

A continuación, recortamos el espacio en blanco del CSS prefijado y usamos la configuración de la vista actual para sangrar el CSS recortado al nivel original usando pestañas o espacios, dependiendo de la configuración actual del editor.

prefixed = prefixed.strip () prefixed = re.sub (re.compile ('^ \ s +', re.M), ", prefixed) settings = self.view.settings () use_spaces = settings.get ('translate_tabs_to_spaces' ) tab_size = int (settings.get ('tab_size', 8)) indent_characters = '\ t' if use_spaces: indent_characters = "* tab_size prefixed = prefixed.replace ('\ n', '\ n' + indent + indent_characters)

Terminamos el método utilizando el inicio original y el espacio en blanco al final para garantizar que el nuevo CSS con prefijo se ajuste exactamente en lugar del original..

match = re.search ('^ (\ s *)', original) prefijo = match.groups () [0] match = re.search ('(\ s *) \ Z', original) sufijo = match.groups () [0] return (prefijo, prefijo, sufijo)

Con el fix_whitespace () método que utilizamos el módulo (re) de expresiones regulares de Python, por lo que necesitamos agregarlo a la lista de importaciones en la parte superior del script.

importar re

Y con esto, hemos completado el proceso de escribir el prefijo comando. El siguiente paso es hacer que el comando sea fácil de ejecutar al proporcionar un atajo de teclado y una entrada de menú.


Paso 10 - Encuadernaciones clave

La mayoría de las configuraciones y modificaciones que se pueden realizar en Sublime se realizan a través de archivos JSON, y esto es cierto para los enlaces de teclas. Los enlaces de claves suelen ser específicos del sistema operativo, lo que significa que se deberán crear tres archivos de enlaces de claves para su complemento. Los archivos deben ser nombrados Predeterminado (Windows) .sublime-keymap, Predeterminado (Linux) .sublime-keymap y Predeterminado (OSX) .sublime-keymap.

 Prefixr /… - Default (Linux) .sublime-keymap - Default (OSX) .sublime-keymap - Default (Windows) .sublime-keymap - Prefixr.py

los .mapa de teclas sublime los archivos contienen una matriz JSON que contiene objetos JSON para especificar los enlaces de teclas. Los objetos JSON deben contener un llaves y mando clave, y también puede contener una args clave si el comando requiere argumentos. La parte más difícil de elegir un enlace de clave es asegurarse de que el enlace de clave no esté en uso. Esto se puede hacer yendo a la Preferencias> Enlaces de teclas - Predeterminado ingrese al menú y busque la combinación de teclas que desea utilizar. Una vez que haya encontrado un enlace no utilizado adecuadamente, agréguelo a su .mapa de teclas sublime archivos.

 ["keys": ["ctrl + alt + x"], "command": "prefixr"]

Normalmente, los enlaces de teclas de Linux y Windows son los mismos. La clave cmd en OS X es especificada por la cadena súper en el .mapa de teclas sublime archivos. Al portar un enlace de clave a través de sistemas operativos, es común que ctrl clave en Windows y Linux para ser intercambiada por súper en OS X. Sin embargo, esto puede no ser siempre el movimiento más natural de la mano, así que si es posible intente probar sus combinaciones de teclas en un teclado real.


Paso 11 - Entradas de menú

Una de las cosas más interesantes acerca de la extensión de Sublime es que es posible agregar elementos a la estructura del menú creando .menú sublime archivos. Los menufiles deben nombrarse nombres específicos para indicar a qué menú afectan:

  • Main.sublime-menu controla el menú principal del programa
  • Side Bar.sublime-menu controla el menú del botón derecho en un archivo o carpeta en la barra lateral
  • Context.sublime-menu controla el menú del botón derecho sobre un archivo que se está editando

Hay un puñado de otros archivos de menú que afectan a otros menús en toda la interfaz. Navegando por el Defecto El paquete es la forma más fácil de aprender sobre todos estos..

Para Prefixr queremos agregar un elemento de menú a la Editar menú y algunas entradas a la Preferencias Menú para ajustes. El siguiente ejemplo es la estructura JSON para el Editar Entrada del menú. He omitido las entradas para el Preferencias Menú, ya que son bastante detallados al estar anidados unos pocos niveles de profundidad.

["id": "editar", "children": ["id": "wrap", "command": "prefixr"]]

La única pieza a la que hay que prestar atención es la carné de identidad llaves. Al especificar el carné de identidadde una entrada de menú existente, es posible anexar una entrada sin redefinir la estructura existente. Si abres el Main.sublime-menu archivo de la Defecto paquete y navegar alrededor, puede determinar qué carné de identidadquieres añadir tu entrada a.

En este punto, su paquete de Prefixr debería verse casi idéntico a la versión oficial en GitHub.


Paso 12 - Distribuyendo tu paquete

Ahora que has tomado el tiempo para escribir un útil complemento de Sublime, es hora de ponerte en manos de otros usuarios.

Sublime admite la distribución de un archivo zip de un directorio de paquetes como una forma sencilla de compartir paquetes. Simplemente cierre la carpeta de su paquete y cambie la extensión a .paquete sublime. Otros usuarios ahora pueden colocar esto en su Paquetes instalados Directorio y reiniciar Sublime para instalar el paquete..

Junto con la fácil disponibilidad para muchos usuarios, tener su paquete disponible a través del Control de Paquetes asegura que los usuarios se actualicen automáticamente a sus últimas actualizaciones.

Si bien esto ciertamente puede funcionar, también hay un administrador de paquetes para Sublime llamado Control de Paquetes que admite una lista maestra de paquetes y actualizaciones automáticas. Para agregar su paquete al canal predeterminado, simplemente alojelo en GitHubor BitBucket y luego bifurque el archivo del canal (en GitHub o BitBucket), agregue su repositorio y envíe una solicitud de extracción. Una vez que se acepte la solicitud de extracción, su paquete estará disponible para miles de usuarios utilizando Sublime. Junto con la fácil disponibilidad para muchos usuarios, tener su paquete disponible a través del Control de Paquetes asegura que los usuarios se actualicen automáticamente a sus últimas actualizaciones.

Si no desea alojar en GitHub o BitBucket, hay un sistema de repositorio / canal customJSON que se puede usar para alojar en cualquier lugar, al mismo tiempo que proporciona el paquete a todos los usuarios. También proporciona una funcionalidad avanzada como especificar la disponibilidad de paquetes por sistema operativo. Vea la página de PackageControl para más detalles.


Ve a escribir algunos complementos!

Ahora que hemos cubierto los pasos para escribir un complemento Sublime, ¡es hora de que te sumerjas! La comunidad de complementos Sublime está creando y publicando nuevas funcionalidades casi todos los días. Con cada lanzamiento, Sublime se vuelve cada vez más potente y versátil. El Sublime Text Forum es un excelente lugar para obtener ayuda y hablar con otros sobre lo que estás construyendo..