Cambiar idioma: English

Definiciones sintácticas

Mediante las definiciones sintácticas, Sublime Text puede reconocer lenguajes de programación y de marcado. Su utilidad más evidente es el coloreado del texto. En las definiciones sintácticas se crean contextos que dividen el texto del búfer en regiones nombradas. Varias funciones de Sublime Text usan la información contextual proporcionada por las definiciones sintácticas.

En esencia, las definiciones sintácticas consisten en expresiones regulares para buscar texto y cadenas más o menos arbitrarias llamadas contextos o nombres de contexto. Sublime Text asigna a cada coincidencia de una expresión regular su correspondiente nombre de contexto.

Requisitos

Para poder seguir este tutorial tendrás que isntalar AAAPackageDev, un módulo que facilita la creación de nuevas definiciones sintácticas para Sublime Text. AAAPackageDev reside en un repositorio público de Mercurial en Bitbucket.

Descárgate el .sublime-package más reciente y pulsa dos veces sobre él con el ratón para instalarlo si usas una intalación completa de Sublime Text, o realizar una instalación manual tal y como ese explica en: Instalación de módulos con archivos .sublime-package.

Formato

Sublime Text utiliza archivos Plist para guardar las definiciones sintácticas. Sin embargo, en esta guía emplearemos JSON en lugar de manipular XML. Posteriormente, convertiremos JSON en Plist con ayuda del módulo AAAPackageDev mencionado más arriba.

Note

Si encuentras errores durante este tutorial, lo más probable es que se deban a AAAPackageDev. No los interpretes inmediatamente como un problema de Sublime Text.

Naturalmente, es posible utilizar archivos Plist directamente si prefieres XML a JSON, pero entonces debes tener en cuenta los diferentes requisitos en cuanto a secuencias de escape, etc.

Contextos

Los contextos son un concepto fundamental en Sublime Text. En esencia, se trata de regiones nombradas en un búfer. Por sí solos no hacen nada, pero sirven de orientación a Sublime Text cuando necesita información contextual.

Por ejemplo, al accionar una plantilla, Sublime Text compara el contexto asignado a ella con la posición del cursor en el archivo. Si ambos contextos coinciden, se inserta la plantilla. De lo contrario, no ocurre nada.

Los contextos pueden estar anidados para mayor precisión. Se puede descender por esta jerarquía de forma muy parecida a como sucede con los selectores de CSS. Por ejemplo, gracias a los selectores de contexto, se puede activar una combinación de teclas exclusivamente dentro de cadenas entre comillas simples de archivos de Python, de tal manera que dicha combinación no funcionaría en cualquier otro lenguaje.

Sublime Text implementa el concepto de contexto de Textmate, un editor para Mac. El manual en línea de Textmate contiene más información sobre selectores de context que es también útil para usuarios de Sublime Text.

Functionamiento de las definiciones sintácticas

En el fondo, las definiciones sintácticas son matrices de expresiones regulares emparejadas con nombres de contexto. Sublime Text buscará dichos patrones en el texto del búfer y asignará el nombre de contexto correspondiente a todas las coincidencias. Estas parejas de expresiones regulares y nombres de contexto se conocen como reglas.

Las reglas se aplican en orden, de una en una. Cada una consume el texto concidente –si lo hay–, que, por lo tanto, será excluido de la siguiente búsqueda (salvo en algunas excepciones). En la práctica, esto implica qeu se deben definir antes reglas espefícicas que generales para evitar que una expresión regular glotona se trague partes que requieran otro nombre de contexto.

Se pueden combinar definiciones sintácticas de varios archivos, y también se pueden aplicar recursivamente.

Tu primera definición sintáctica

A modo de ejemplo, crearemos una definición sintáctica para las plantillas de Sublime Text. Decoraremos el contenido de la plantilla propiamente dicho, no el archivo .sublime-snippet.

Note

Dado que las definiciones sintácticas se emplean principalmente para colorear el texto, utilizaremos colorear con el sentido de dividir el código en contextos. Debe tenerse en cuenta que los mapas de colores son una cosa diferente a las definiciones sintácticas, y que los contextos tienen más funciones aparte de colorear el texto.

Estos son los elementos de las plantillas que colorearemos:

  • variables ($PARAM1, $USER_NAME…);
  • campos simples ($0, $1…);
  • campos con rellenos (${1:Hello});
  • campos anidados (${1:Hello ${2:World}!});
  • secuencias de escape (\\$, \\<…);
  • secuencias ilegales ($, <…).

Note

Antes de continuar, instala AAAPackageDev tal y como se ha explicado más arriba si aún no lo has hecho.

Cómo crear una definición sintáctica

Para crear una definición sintáctica, sigue estos pasos:

  • selecciona Tools | Packages | Package Development | New Syntax Definition;
  • guarda el archivo en Packages/User como Sublime Snippets (Raw).JSON-tmLanguage.

Deberías ver un archivo con un contenido similar a este:

{ "name": "Syntax Name",
  "scopeName": "source.syntax_name",
  "fileTypes": [""],
  "patterns": [
  ],
  "uuid": "ca03e751-04ef-4330-9a6b-9b99aae1c418"
}

Veamos cada una de las partes detenidamente:

uuid
Se puede ver al final. Se trata de un identificador exclusivo para esta definición sintáctica. Cada una tiene su propio uuid. No se debe modificar.
name
El nombre que Sublime Text mostrará en el menú desplegable de definiciones sintácticas. Emplea un nombre corto y descriptivo. Generalmente, se usará el nombre del lenguaje de programación para el que se crea la definición sintáctica.
scopeName
El contexto raíz de la definición sintáctica. Adopta las forma source.<lenguaje> o text.<lenguaje>. Para lenguajes de programación, usa source. Para lengujaes de marcado, text.
fileTypes
Lista de extensiones de archivo. Cuando se abran archivos con estas extensiones, se les aplicará automáticamente la definición sintáctica.
patterns
Contenedor de las reglas de la definición sintáctica.

Para seguir con nuestro ejemplo, rellena la plantilla con la siguiente información (salvo uuid):

{   "name": "Sublime Snippet (Raw)",
    "scopeName": "source.ssraw",
    "fileTypes": ["ssraw"],
    "patterns": [
    ],
    "uuid": "ca03e751-04ef-4330-9a6b-9b99aae1c418"
}

Note

JSON es un formato muy estricto y, por lo tanto, se debe ser paricularmente cuidadoso con la escritura de comas y comillas. Si la conversión a Plist falla, examina el panel de salida para obtener más información acerca del error. Más adelante se explica cómo realizar la conversión entre JSON y Plist.

Reglas

La matriz patterns puede contener diversos tipos de elementos. En las siguiente secciones estudiaremos algunos de ellos. Si quieres obtener más información acerca de las reglas, consulta el manual en línea de Textmate.

Elemento match

Adopta la siguiente forma:

{ "match": "[Mm]y \s+[Rr]egex",
  "name": "string.ssraw",
  "comment": "This comment is optional."
}
match
Expresión regular que Sublime Text utilizará para buscar texto.
name
Nombre del contexto que se asignará a las coincidencias de match.
comment
Comentario optativo.

Volvamosa nuestro ejemplo. Modifica el contenido de la siguiene forma:

{ "name": "Sublime Snippet (Raw)",
  "scopeName": "source.ssraw",
  "fileTypes": ["ssraw"],
  "patterns": [
  ],
  "uuid": "ca03e751-04ef-4330-9a6b-9b99aae1c418"
}

Es decir, asegúrate de que la matriz patterns esté vacía.

Ahora podemos empezar a añadir reglas para las plantillas de Sublime Text. Empecemos con los campos simples. Estos se pueden encontrar con una expresión regular tal que así:

\$[0-9]+
# o bien...
\$\d+

Sin embargo, como estamos utilizando JSON, debemos tener en cuenta las secuencias de escape de JSON. Así pues, el resultado es el siguiente:

\\$\\d+

Una vez solucionado el problema de las secuencias de escape, podemos construir nuestra regla de la siguiente manera:

{ "match": "\\$\\d+",
  "name": "keyword.source.ssraw",
  "comment": "Tab stops like $1, $2..."
}

Añadámosla ahora a nuestra definición sintáctica:

{   "name": "Sublime Snippet (Raw)",
    "scopeName": "source.ssraw",
    "fileTypes": ["ssraw"],
    "patterns": [
        { "match": "\\$\\d+",
          "name": "keyword.source.ssraw",
          "comment": "Tab stops like $1, $2..."
        }
    ],
    "uuid": "ca03e751-04ef-4330-9a6b-9b99aae1c418"
}

Con esto ya podemos convertir el archivo a .tmLanguage. Las definiciones sintácticas utilizan la extensión .tmLanguage de Textmate por razones de compatibilidad. Como se ha mencionado anteriormente, se trata simplemente de archivos XML en formato Plist.

Sigue estos pasos para realizar la conversión con AAAPackageDev:

  • selecciona Json to tmLanguage in Tools | Build System;
  • pulsa F7;
  • se generará un archivo .tmLanguage en el mismo directorio que el archivo .JSON-tmLanguage;
  • reinicia Sublime Text para que los cambios surtan efecto.

Note

Sublime Text no puede recargar definiciones sintácticas automáticamente cuando se modifican.

Acabas de crear tu primera definición sintáctica. A continuación, crea un archivo nuevo y guárdalo con la extensión .ssraw. El nombre de la definición sintáctica del búfer debería cmabiar automáticamente a “Sublime Snippet (Raw)”, y deberías ver colores si tecleas $1 u otro campo similar de las plantillas.

Continuemos con una regla para variables de entorno.

{ "match": "\\$[A-Za-z][A-Za-z0-9_]+",
  "name": "keyword.source.ssraw",
  "comment": "Variables like $PARAM1, $TM_SELECTION..."
}

Repite los pasos enumerados anteriormente para actualizar el archivo .tmLanguage y reinicia Sublime Text.

Refinamiento de los contextos

Si te fijas, el texto de $PARAM1, por ejemplo, se colorea entero de la mimsa manera. Según tus necesidades o preferencias, podrías preferir que el carácter $ se viera resaltado en otro color. Esa es la función del elemento captures. Con las capturas, se puede descomponer una expresión regular en subgrupos y asignarles diferentes contextos.

Cambiemos una de nuestras reglas para que usen captures:

{ "match": "\\$([A-Za-z][A-Za-z0-9_]+)",
  "name": "keyword.ssraw",
  "captures": {
      "1": { "name": "constant.numeric.ssraw" }
   },
  "comment": "Variables like $PARAM1, $TM_SELECTION..."
}

Las capturas añaden cierta complejidad a las reglas, pero su funcionamiento es simple. Observa en el ejemplo cómo los números se refieren a los grupos entre paréntesis empezando por la izquierda. Puedes crear tantos grupos como necesites.

Es posible que quieras cambiar la otra regla para que se visualmente similar a esta. Si es así, modifícala antes de continuar.

Regas begin..end

Hasta el momento solo hemos creado reglas simples. Aunque hemos visto cómo descomponer expresiones regulares en subgrupos, a veces es necesario considerar una parte mayor del código delimitada claramente por una marca de inicio otra de término.

Es el caso, por ejemplo, de las cadenas entrecomilladas. En estos casos, es más apropiado emplear reglas begin..end. He aquí un ejemplo de una:

{ "name": "",
  "begin": "",
  "end": ""
}

Esta es una versión mínima e inválida. Veamos a continuación otra con todas las opciones disponibles:

{ "name": "",
  "begin": "",
  "beginCaptures": {
    "0": { "name": "" }
  },
  "end": "",
  "endCaptures": {
    "0": { "name": "" }
  },
  "patterns": [
     {  "name": "",
        "match": ""
                  }
  ],
  "contentName": ""
}

Algunos elementos resultarán conocidos, pero su combinación podría confundir. Veámoslos individualmente.

begin
Expresión regular que marca el principio del bloque.
end
Expresión regular que marca el final del bloque.
beginCaptures
Optativo Capturas para el elemeno begin únicamente. Similares a las capturas ya explicadas.
endCaptures
Optativo. Igual que beginCaptures, pero para el elemento end. Optativo.
contentName
Optativo. Nombre del contexto para todo el bloque, desde la marca del principio hasta la del final. En la practica, crea un contexto adicional —y común— para beginCaptures, endCaptures y patterns.
patterns
Matriz de reglas que se aplicarán únicamente al texto comprendido entre el final del marcador begin y el principio de end.

Utilizaremos esta regla para colorear campos complejos de las plantillas:

{ "name": "variable.complex.ssraw",
   "begin": "(\\$)(\\{)([0-9]+):",
   "beginCaptures": {
       "1": { "name": "keyword.ssraw" },
       "3": { "name": "constant.numeric.ssraw" }
   },
   "patterns": [
       { "include": "$self" },
       {  "name": "string.ssraw",
          "match": "."
       }
   ],
   "end": "\\}"
}

Esta es la regla más compleja que veremos en esta guía. Los elementos begin y end son obvios: definen una región enmarcada entre ${{<NUMBER: y }. El elemento beginCaptures divide el marcador de inicio en contextos más pequeños.

Sin embargo, la parte más interesante es patterns. La recursión y la importancia del orden entran por fin en juego.

Más arriba hemos visto que los campos se pueden anidar. Para reflejar esto en nuestra definición sintáctica, debemos colorear recursivamente campos anidados. Y eso es exactamente lo que hace el elemento include al pasarle la variable $self: aplica recursivamente la definición sintáctica al contenido de la regla begin..end excluyendo el texto consumido por begin y end.

Recuerdemos que el texto coincidente con cada regla se exclude de la siguiente búsqueda.

Para terminar los campos complejos, colorearemos los rellenos como cadenas. Dado que ya hemos despachado todos los elementos de un campo complejo, podemos decirle a Sublime Text que asigne al resto del texto (.) un contexto de cadena de caracteres.

Toques finales

Por últimos, creemos contextos para secuencias de escapes y secuencias ilegales:

{  "name": "constant.character.escape.ssraw",
   "match": "\\\\(\\$|\\>|\\<)"
},

{  "name": "invalid.ssraw",
   "match": "(\\$|\\<|\\>)"
}

La mayor dificultad en este caso reside en las secuencias de escape. Aparte de esto, las reglas son sencillas si estás familirizado con las expresiones regulares.

No obstante, es importante colocar la segunda regla tras todas las que busquen el carácter $ o, de lo contrario, el resultado final podría no ser el esperado.

Además, observa que tras la adición de estas dos últimas reglas la regla recursiva begin..end sigue funcionando correctamente.

Por fin, he aquí la definición sintáctica completa:

{   "name": "Sublime Snippet (Raw)",
    "scopeName": "source.ssraw",
    "fileTypes": ["ssraw"],
    "patterns": [
        { "match": "\\$(\\d+)",
          "name": "keyword.ssraw",
          "captures": {
              "1": { "name": "constant.numeric.ssraw" }
           },
          "comment": "Tab stops like $1, $2..."
        },

        { "match": "\\$([A-Za-z][A-Za-z0-9_]+)",
          "name": "keyword.ssraw",
          "captures": {
              "1": { "name": "constant.numeric.ssraw" }
           },
          "comment": "Variables like $PARAM1, $TM_SELECTION..."
        },

        { "name": "variable.complex.ssraw",
          "begin": "(\\$)(\\{)([0-9]+):",
          "beginCaptures": {
              "1": { "name": "keyword.ssraw" },
              "3": { "name": "constant.numeric.ssraw" }
           },
           "patterns": [
              { "include": "$self" },
              { "name": "string.ssraw",
                "match": "."
              }
           ],
           "end": "\\}"
        },

        { "name": "constant.character.escape.ssraw",
          "match": "\\\\(\\$|\\>|\\<)"
        },

        { "name": "invalid.ssraw",
          "match": "(\\$|\\>|\\<)"
        }
    ],
    "uuid": "ca03e751-04ef-4330-9a6b-9b99aae1c418"
}

Aún existes más constructos y técnicas de reciclado de código, pero con esta introducción puedes lanzarte a crear tus propias definiciones sintácticas.