{"id":32,"date":"2021-06-21T10:13:36","date_gmt":"2021-06-21T10:13:36","guid":{"rendered":"http:\/\/kinori.tech\/blog\/es\/?p=32"},"modified":"2021-06-21T10:15:00","modified_gmt":"2021-06-21T10:15:00","slug":"un-hogar-para-tu-lenguaje-de-programacion-en-intellij-parte-1","status":"publish","type":"post","link":"http:\/\/kinori.tech\/blog\/es\/2021\/06\/21\/un-hogar-para-tu-lenguaje-de-programacion-en-intellij-parte-1\/","title":{"rendered":"Un hogar para tu Lenguaje (de programaci\u00f3n) en IntelliJ &#8211; Parte 1"},"content":{"rendered":"\n<p class=\"has-text-align-center\">(you can also read this post in\u00a0<a href=\"http:\/\/kinori.tech\/blog\/blog\/2019\/01\/29\/welcome\/\">english<\/a>)<\/p>\n\n\n\n<p class=\"has-medium-font-size\">Esta es la Parte 1 de la serie <strong>&#8220;Un hogar para tu Lenguaje (de programaci\u00f3n) en IntelliJ&#8221;<\/strong>.<\/p>\n\n\n\n<p>Por fin lograste escribir el compilador o m\u00e1quina de ejecuci\u00f3n para tu nuevo y reluciente lenguaje. Asegurarse que todo funciona (de acuerdo a la sem\u00e1ntica del lenguaje) solo nos lleva hasta la puerta de nuestros usuarios potenciales. Para que te abran las puertas de su entorno de desarrollo y dejen entrar a tu lenguaje se necesita la siguiente pieza del rompecabezas: un editor.<\/p>\n\n\n\n<p>Bueno, al menos en mi entorno de desarrollo. Me gusta ver la sintaxis sobresaltada con diferentes colores y las herramientas que nos proveen los IDEs (integrated development environment, en ingles). Si eres como yo, entonces sigue leyendo.<\/p>\n\n\n\n<p>Hay diferentes maneras de construir un editor para un lenguaje: Eclipse (utilizando XText &#8211; o desde cero), Atom, Notepad++, etc. En esta entrada les voy a presentar como se puede hacer para la familia de IDEs de <a href=\"https:\/\/www.jetbrains.com\">Jet Brains<\/a> (IngelliJ, PyCharm, WebStorm, etc.). \u00bfPor qu\u00e9 Jet Brains? Me gustan sus editores. Me gusta su tema oscuro, sus accesos r\u00e1pidos y las herramientas integradas. La tienen clara, en mi humilde opini\u00f3n.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Requisitos<\/h2>\n\n\n\n<p>Jet Brains tiene un <a href=\"https:\/\/jetbrains.org\/intellij\/sdk\/docs\/tutorials\/custom_language_support_tutorial.html\">tutorial oficial<\/a> pata el soporte de lenguajes personalizados. Si nunca has desarrollado un editor para Jet Brains, sugiero leer el tutorial para entender el concepto y lo que se necesita hacer (solo en ingles). En el resto de la entrada voy a utilizar los nombres de clases y t\u00e9rminos utilizados all\u00ed, as\u00ed que estar familiarizados con ellos puede ayudar a seguir la entrada. Har\u00e9 referencia a la secci\u00f3n en particular del tutorial que habla de lo que estoy describiendo, as\u00ed que puede ser ben\u00e9fico ir y venir de la p\u00e1gina del tutorial.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lo que vamos a hacer<\/h2>\n\n\n\n<p>Podemos categorizar los editores, a grandes rasgos, en dos tipos <em>visuales <\/em>y <em>funcionales<\/em>. En los primeros, est\u00e1n los editores que solo hacen realce de sintaxis y plegado de bloques (ej. colapsar las l\u00edneas entre dos corchetes). Los segundos manejan referencias cruzadas y puede hacer cambios estructurales (cambiar nombres, extraer m\u00e9todos, etc.). Es esta entrada solo me enfoco en los componentes <em>visuales<\/em>. Esto es:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Definici\u00f3n del lenguaje (secci\u00f3n 2 del tutorial)<\/li><li>An\u00e1lisis gram\u00e1tico y sint\u00e1ctico (secci\u00f3n 3 del tutorial)<\/li><li>Definici\u00f3n del analizador l\u00e9xico y sint\u00e1ctico (secci\u00f3n 4 del tutorial)<\/li><li>Realce de sintaxis (secci\u00f3n 5 del tutorial)<\/li><li>Plegado\/colapsado (secci\u00f3n 12 del tutorial)<\/li><\/ul>\n\n\n\n<p>M\u00e1s que tan solo repetir lo que dice el tutorial, mi intenci\u00f3n es mencionar los peque\u00f1os detalles que no son evidentes en el tutorial y ayudarlos a entender como los diferentes aspectos del editor est\u00e1n relacionadas a trav\u00e9s de la API (application programming interface &#8211; en ingles) de PSI y editores de IntelliJ.<\/p>\n\n\n\n<p>Como ejemplo voy a utilizar los lenguajes de <a href=\"https:\/\/www.eclipse.org\/epsilon\/\">Epsilon<\/a>, pues mi intenci\u00f3n inicial es poder editor programas de Epsilon desde IntelliJ. En la parte 1 de esta serie hablo sobre un solo lenguaje, EOL; en la Parte 2 hablar\u00e9 de como soportar los dem\u00e1s lenguajes.<\/p>\n\n\n\n<p>El c\u00f3digo del editor est\u00e1 en el <a href=\"https:\/\/bitbucket.org\/kinoritech\/epsilon-intellij\/\">repositorio<\/a> de Kinori Tech por si quieres ver el c\u00f3digo mientras lees esta entrada.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Definici\u00f3n del Lenguaje<\/h2>\n\n\n\n<p>No hay mucho m\u00e1s que agregar sobre esta secci\u00f3n. La \u00fanica cosa que quiero mencionar es que si est\u00e1s creando una familia de lenguajes, la clase &#8220;Icons&#8221; (secci\u00f3n 2.2) puede ser utilizada para todos los lenguajes. Otro aspecto al cual debes poner atenci\u00f3n es la creaci\u00f3n del icono(s) de los lenguajes. Personalmente encuentro muy \u00fatil ver la estructura del proyecto e identificar r\u00e1pidamente los archivos por tipo.  Estas dos p\u00e1ginas tiene la informaci\u00f3n relevante sobre la creaci\u00f3n de iconos para JetBrains:<br>&#8211; https:\/\/plugins.jetbrains.com\/docs\/intellij\/work-with-icons-and-images.html<br>&#8211; https:\/\/jetbrains.design\/intellij\/principles\/icons\/<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Gram\u00e1tica y sintaxis<\/h2>\n\n\n\n<p>Mi \u00fanico cambio sugerido es el el Token Type (secci\u00f3n 3.1):<\/p>\n\n\n\n<pre class=\"wp-block-syntaxhighlighter-code\">public class EolTokenType extends IElementType {\n\n   public EolTokenType(@NotNull @NonNls String debugName) {\n      super(debugName, EolLanguage.INSTANCE);\n   }\n\n   @Override\n   public String toString() {\n      return super.toString(); \/\/ vs return \"SimpleTokenType.\" + super.toString();\n   }\n}<\/pre>\n\n\n\n<p>Una vez en uso, utilizar &#8220;&lt;nombre>TokenType&#8221; como prefijo en el m\u00e9todo &#8220;toString()&#8221; hace los mensajes de error dif\u00edciles de leer para los usuarios, en efecto el prefijo no da ninguna informaci\u00f3n \u00fatil al usuario final cuando se generan errores de tipo &#8220;Se esperaba una COMMA, se encontr\u00f3 LEFT_BRACKET&#8221;.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Gram\u00e1tica<\/h3>\n\n\n\n<p>Escribir la gram\u00e1tica de un lenguaje (y luego el analizador l\u00e9xico) son temas que merecen su propia entrada. Sin embargo, si ya has escrito una versi\u00f3n de tu gram\u00e1tica en (E)BNF, traducirla al BNF the IntelliJ toma tiempo pero debe ser relativamente directo. Aseg\u00farate de tener una definici\u00f3n correcta de la gram\u00e1tica antes de continuar.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"> An\u00e1lisis l\u00e9xico y sint\u00e1ctico<\/h2>\n\n\n\n<p>La clase que necesita m\u00e1s atenci\u00f3n es la &#8220;Parser Definiton&#8221; (an\u00e1lisis sint\u00e1ctico). Para m\u00ed, los m\u00e9todos clave son <em>getWhitespaceTokens<\/em>, <em>getCommentTokens <\/em>y <em>getStringLiteralElements<\/em>. Estos tres m\u00e9todos le dicen al editor que s\u00edmbolos deben ser considerados como espacio (usualmente espacios y fin de l\u00ednea), comentarios y cadenas de letras. Aseg\u00farate de retornar todos los tipos de s\u00edmbolos, ej. comentarios de una o de m\u00faltiples l\u00edneas. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\">An\u00e1lisis L\u00e9xico<\/h3>\n\n\n\n<p>En el pasado he utilizado Antlr y XText para dise\u00f1ar lenguajes, as\u00ed que tener que definir el an\u00e1lisis l\u00e9xico por separado fue una experiencia nueva. Creo que la documentaci\u00f3n de JFlex es un poco dif\u00edcil de entender, y no puede encontrar buenos tutoriales en la red. De nuevo, es un tema que merece su propia entrada. Solo ten presente que los &#8220;estados l\u00e9xicos&#8221; son muy \u00fatiles en JFlex. Paciencia, yo solo logr\u00e9 ajustar mi an\u00e1lisis l\u00e9xico luego de muchas pruebas y errores.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Realce de Sintaxis<\/h2>\n\n\n\n<p>En este punto, el an\u00e1lisis l\u00e9xico debe estar creando s\u00edmbolos (tokens en ingles) y el an\u00e1lisis sint\u00e1ctico los debe estar usando para validar la gram\u00e1tica de un programa. Si estas usando la vista PSI (como recomienda el tutorial), debes ver como se construye el CST (\u00e1rbol de sintaxis concreta &#8211; concrete syntax tree) y los errores se deben estar reportando.<\/p>\n\n\n\n<p class=\"has-text-align-center has-medium-font-size\">\u00a1<em>Ahora viene la magia!<\/em><\/p>\n\n\n\n<p>Primero debemos agrupar los s\u00edmbolos en &#8220;grupos de realce&#8221;. Usualmente se utilizan diferentes colores para las palabras del lenguaje (ej. <em>for<\/em>, <em>then, class<\/em>, etc.), cadenas de caracteres, n\u00fameros, comentarios, etc. Define los grupos que consideres necesarios\/\u00fatiles y asigna los diferentes s\u00edmbolos a cada grupo. A cada grupo de realce le podemos asignar un color y un estilo (negritas, it\u00e1licas, etc.). Para cada grupo se debe definir un extAttributesKey y un <em>array <\/em>TextAttributesKey. Creo que se pueden combinar m\u00faltiples estilos, pero no he jugado con esa posibilidad (y la documentaci\u00f3n al respecto no es muy buena). <\/p>\n\n\n\n<p>El tutorial sugiere el uso de campos est\u00e1ticos para los grupos:<\/p>\n\n\n\n<pre class=\"wp-block-syntaxhighlighter-code\">public static final TextAttributesKey EOL_OPERATORS =\n\t\t\tcreateTextAttributesKey(\"EOL_OPERATORS\", DefaultLanguageHighlighterColors.OPERATION_SIGN);\n...\npublic static final TextAttributesKey[] EOL_OPERATOR_KEYS = new TextAttributesKey[]{EOL_OPERATORS};<\/pre>\n\n\n\n<p>Adicionalmente, usan la clase &#8220;DefaultLanguageHighlighterColors&#8221; que define un set predeterminado de estilos. Los estilos predeterminados son los utilizados por IntelliJ as\u00ed que son un buen punto de inicio. Para estilos diferentes se debe crear &#8220;TextAttributesKey&#8221; propias a partir de objetos TextAttributes. El API favorece el reuso, al menos para que las definiciones sean est\u00e1ticas. Sin embargo, se pueden crear de manera no est\u00e1tica en el constructor de nuestro &#8220;Highlighter&#8221;. La clase <a href=\"https:\/\/github.com\/JetBrains\/intellij-community\/blob\/master\/platform\/core-api\/src\/com\/intellij\/openapi\/editor\/markup\/TextAttributes.java\">TextAttributes<\/a> es su amiga. <\/p>\n\n\n\n<p>Para EOL, defin\u00ed 8 grupos:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>operadores: aritm\u00e9ticos (<em>+<\/em>, -, \/, etc.) y espec\u00edficos de EOL (<em>!<\/em>, <em>|<\/em>, <em>::=<\/em>, etc.).<\/li><li>operadores de texto: operadores l\u00f3gicos (<em>and<\/em>, <em>or<\/em>, etc.)<\/li><li>palabras clave: for, if, class, operation, etc.<\/li><li>anotaciones @cached, @lazy, etc.<\/li><li>numeros<\/li><li>funciones del lenguage: select, collect, includes, etc.<\/li><li>cadena de caracteres<\/li><li>comentarios<\/li><\/ul>\n\n\n\n<p>Los s\u00edmbolos que perteneces a cada grupo son definidos en el m\u00e9todo <em>getTokenHighlights<\/em>. Segu\u00ed la idea del tutorial con multiples &#8220;or&#8221;s, pero una optimizaci\u00f3n puede ser definirlos como sets.<\/p>\n\n\n\n<pre class=\"wp-block-syntaxhighlighter-code\">package org.eclipse.epislon.labs.intellij.grammar.editor.eol;\n\nimport com.intellij.lexer.Lexer;\nimport com.intellij.openapi.editor.DefaultLanguageHighlighterColors;\nimport com.intellij.openapi.editor.HighlighterColors;\nimport com.intellij.openapi.editor.colors.TextAttributesKey;\nimport com.intellij.openapi.fileTypes.SyntaxHighlighterBase;\nimport com.intellij.psi.TokenType;\nimport com.intellij.psi.tree.IElementType;\nimport org.eclipse.epislon.labs.intellij.grammar.parser.eol.EolLexerAdapter;\nimport org.eclipse.epislon.labs.intellij.grammar.psi.eol.EolTypes;\nimport org.jetbrains.annotations.NotNull;\n\nimport static com.intellij.openapi.editor.colors.TextAttributesKey.createTextAttributesKey;\n\npublic class EolSyntaxHighlighter extends SyntaxHighlighterBase {\n  public static final TextAttributesKey EOL_OPERATORS =\n      createTextAttributesKey(\"EOL_OPERATORS\", DefaultLanguageHighlighterColors.OPERATION_SIGN);\n  public static final TextAttributesKey EOL_TEXT_OPERATORS =\n      createTextAttributesKey(\"EOL_TEXT_OPERATORS\", DefaultLanguageHighlighterColors.LOCAL_VARIABLE);\n  public static final TextAttributesKey EOL_KEYWORD =\n      createTextAttributesKey(\"EOL_KEYWORD\", DefaultLanguageHighlighterColors.INSTANCE_METHOD);\n  public static final TextAttributesKey EOL_ANNOTATIONS =\n      createTextAttributesKey(\"EOL_ANNOTATIONS\", DefaultLanguageHighlighterColors.METADATA);\n  public static final TextAttributesKey EOL_NUMBERS =\n      createTextAttributesKey(\"EOL_NUMBERS\", DefaultLanguageHighlighterColors.NUMBER);\n  public static final TextAttributesKey EOL_BUILTIN =\n      createTextAttributesKey(\"EOL_BUILTIN\", DefaultLanguageHighlighterColors.FUNCTION_CALL);\n  public static final TextAttributesKey EOL_STRING =\n      createTextAttributesKey(\"STRING\", DefaultLanguageHighlighterColors.STRING);\n  public static final TextAttributesKey EOL_COMMENT =\n      createTextAttributesKey(\"SIMPLE_COMMENT\", DefaultLanguageHighlighterColors.LINE_COMMENT);\n  public static final TextAttributesKey BAD_CHARACTER =\n      createTextAttributesKey(\"SIMPLE_BAD_CHARACTER\", HighlighterColors.BAD_CHARACTER);\n\n\n  public static final TextAttributesKey[] EOL_OPERATOR_KEYS = new TextAttributesKey[]{EOL_OPERATORS};\n  public static final TextAttributesKey[] EOL_TEXT_OPERATOR_KEYS = new TextAttributesKey[]{EOL_TEXT_OPERATORS};\n  public static final TextAttributesKey[] EOL_KEYWORD_KEYS = new TextAttributesKey[]{EOL_KEYWORD};\n  public static final TextAttributesKey[] EOL_ANNOTATIONS_KEYS = new TextAttributesKey[]{EOL_ANNOTATIONS};\n  public static final TextAttributesKey[] EOL_NUMBER_KEYS = new TextAttributesKey[]{EOL_NUMBERS};\n  public static final TextAttributesKey[] EOL_BUILTIN_KEYS = new TextAttributesKey[]{EOL_BUILTIN};\n  public static final TextAttributesKey[] EOL_STRING_KEYS = new TextAttributesKey[]{EOL_STRING};\n  public static final TextAttributesKey[] EOL_COMMENT_KEYS = new TextAttributesKey[]{EOL_COMMENT};\n  public static final TextAttributesKey[] EMPTY_KEYS = new TextAttributesKey[0];\n  private static final TextAttributesKey[] BAD_CHAR_KEYS = new TextAttributesKey[]{BAD_CHARACTER};\n\n  @NotNull\n  @Override\n  public Lexer getHighlightingLexer() {\n    return new EolLexerAdapter();\n  }\n\n  @NotNull\n  @Override\n  public TextAttributesKey[] getTokenHighlights(IElementType tokenType) {\n    if (tokenType.equals(EolTypes.EOL_ARROW_OP)\n      || tokenType.equals(EolTypes.EOL_ASSIGN_OP)\n      || tokenType.equals(EolTypes.EOL_COLON_OP)\n      || tokenType.equals(EolTypes.EOL_DIV_ASSIG_OP)\n      || tokenType.equals(EolTypes.EOL_DIV_OP)\n      || tokenType.equals(EolTypes.EOL_ENUM_OP)\n      || tokenType.equals(EolTypes.EOL_EQ_OP)\n      || tokenType.equals(EolTypes.EOL_EQUIV_ASSIGN_OP)\n      || tokenType.equals(EolTypes.EOL_GE_OP)\n      || tokenType.equals(EolTypes.EOL_GT_OP)\n      || tokenType.equals(EolTypes.EOL_IMPLIES_OP)\n      || tokenType.equals(EolTypes.EOL_IN_OP)\n      || tokenType.equals(EolTypes.EOL_INC_OP)\n      || tokenType.equals(EolTypes.EOL_LE_OP)\n      || tokenType.equals(EolTypes.EOL_LT_OP)\n      || tokenType.equals(EolTypes.EOL_MINUS_ASSIGN_OP)\n      || tokenType.equals(EolTypes.EOL_MODEL_OP)\n      || tokenType.equals(EolTypes.EOL_NE_OP)\n      || tokenType.equals(EolTypes.EOL_NEG_OP)\n      || tokenType.equals(EolTypes.EOL_PLUS_ASSIGN_OP)\n      || tokenType.equals(EolTypes.EOL_PLUS_OP)\n      || tokenType.equals(EolTypes.EOL_POINT_OP)\n      || tokenType.equals(EolTypes.EOL_POINT_POINT_OP)\n      || tokenType.equals(EolTypes.EOL_QUAL_OP)\n      || tokenType.equals(EolTypes.EOL_SPECIAL_ASSIGN_OP)\n      || tokenType.equals(EolTypes.EOL_THEN_OP)\n      || tokenType.equals(EolTypes.EOL_TIMES_ASSIGN_OP)\n      || tokenType.equals(EolTypes.EOL_TIMES_OP)\n    ) {\n      return EOL_OPERATOR_KEYS;\n    }\n    else if(tokenType.equals(EolTypes.EOL_AND_OP)\n        || tokenType.equals(EolTypes.EOL_NOT_OP)\n        || tokenType.equals(EolTypes.EOL_OR_OP)\n        || tokenType.equals(EolTypes.EOL_XOR_OP)\n    ) {\n      return EOL_TEXT_OPERATOR_KEYS;\n    }\n    else if (tokenType.equals(EolTypes.EOL_ABORT_KEY)\n        || tokenType.equals(EolTypes.EOL_BAG_KEY)\n        || tokenType.equals(EolTypes.EOL_BREAK_ALL_KEY)\n        || tokenType.equals(EolTypes.EOL_BREAK_KEY)\n        || tokenType.equals(EolTypes.EOL_CASE_KEY)\n        || tokenType.equals(EolTypes.EOL_COL_KEY)\n        || tokenType.equals(EolTypes.EOL_CONTINUE_KEY)\n        || tokenType.equals(EolTypes.EOL_DEFAULT_KEY)\n        || tokenType.equals(EolTypes.EOL_DELETE_KEY)\n        || tokenType.equals(EolTypes.EOL_ELSE_KEY)\n        || tokenType.equals(EolTypes.EOL_EXT_KEY)\n        || tokenType.equals(EolTypes.EOL_FALSE_KEY)\n        || tokenType.equals(EolTypes.EOL_FOR_KEY)\n        || tokenType.equals(EolTypes.EOL_FUNCTION_KEY)\n        || tokenType.equals(EolTypes.EOL_IF_KEY)\n        || tokenType.equals(EolTypes.EOL_IMPORT_KEY)\n        || tokenType.equals(EolTypes.EOL_IN_KEY)\n        || tokenType.equals(EolTypes.EOL_LIST_KEY)\n        || tokenType.equals(EolTypes.EOL_MAP_KEY)\n        || tokenType.equals(EolTypes.EOL_NATIVE_KEY)\n        || tokenType.equals(EolTypes.EOL_NEW_KEY)\n        || tokenType.equals(EolTypes.EOL_OPERATION_KEY)\n        || tokenType.equals(EolTypes.EOL_ORDSET_KEY)\n        || tokenType.equals(EolTypes.EOL_RETURN_KEY)\n        || tokenType.equals(EolTypes.EOL_SEQ_KEY)\n        || tokenType.equals(EolTypes.EOL_SET_KEY)\n        || tokenType.equals(EolTypes.EOL_SWITCH_KEY)\n        || tokenType.equals(EolTypes.EOL_THROW_KEY)\n        || tokenType.equals(EolTypes.EOL_TRANS_KEY)\n        || tokenType.equals(EolTypes.EOL_TRUE_KEY)\n        || tokenType.equals(EolTypes.EOL_VAR_KEY)\n        || tokenType.equals(EolTypes.EOL_WHILE_KEY)\n        ) {\n      return EOL_KEYWORD_KEYS;\n    }\n    else if (tokenType.equals(EolTypes.EOL_ANNOTATION)\n        || tokenType.equals(EolTypes.EOL_EXEC_ANNOT)\n        || tokenType.equals(EolTypes.EOL_EXECUTABLE_ANNOTATION)) {\n      return EOL_ANNOTATIONS_KEYS;\n    }\n    else if (tokenType.equals(EolTypes.EOL_FLOAT)) {\n      return EOL_NUMBER_KEYS;\n    }\n    else if (tokenType.equals(EolTypes.EOL_SELF_BIN)\n        || tokenType.equals(EolTypes.EOL_LOOP_CNT_BIN)\n        || tokenType.equals(EolTypes.EOL_HAS_MORE_BIN)) {\n      return EOL_BUILTIN_KEYS;\n    }\n    else if (tokenType.equals(EolTypes.EOL_STRING)) {\n      return EOL_STRING_KEYS;\n    }\n    else if (tokenType.equals(EolTypes.EOL_BLOCK_COMMENT) || tokenType.equals(EolTypes.EOL_LINE_COMMENT)) {\n      return EOL_COMMENT_KEYS;\n    }\n    else if (tokenType.equals(TokenType.BAD_CHARACTER)) {\n      return BAD_CHAR_KEYS;\n    }\n    else {\n      return EMPTY_KEYS;\n    }\n  }\n}<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Creador de plegados<\/h2>\n\n\n\n<p>Esta fue la parte m\u00e1s dif\u00edcil de descifrar. El m\u00e9todo <em>buildFoldRegions<\/em> (construir regiones de plegado) en la clase FoldingBuilder es la clave; en resumen es necesario construir las regiones de plagado para los elementos PSI que se requiera (usualmente bloques de c\u00f3digo, e.g. condicionales y bucles). Cabe notar que la creaci\u00f3n de plegados utiliza la API de PSI, es decir que tenemos que trabajar con los constructos de nuestro lenguaje, no con tokens. El m\u00e9todo <em>buildFoldRegions<\/em> recibe el par\u00e1metro <em>quick.<\/em> Debido a que la construcci\u00f3n de plegados puede tomar mucho tiempo, el par\u00e1metro <em>quick<\/em> (r\u00e1pido) se debe utilizar para construir un subset de los plegados que se pueda hacer r\u00e1pidamente.<\/p>\n\n\n\n<p>Para EOL, escogimos dos constructos para plegar, inspirados por lo que se hace en Java: imports y code blocks (todos los bloques entre corchetes). Si el par\u00e1metro <em>quick<\/em> es verdadero, solo construimos los plegados para imports.<\/p>\n\n\n\n<pre class=\"wp-block-syntaxhighlighter-code\">\/\/ imports\nEpsilonModule module = PsiTreeUtil.findChildOfAnyType(root, false, EpsilonModule.class);\nif ((module != null) &amp;&amp; !module.getImportStatementList().isEmpty()) {\n  descriptors.add(new ImportStatementBlockFolding(\n      module.getNode(),\n      new TextRange(\n       \n module.getImportStatementList().get(0).getTextRange().getStartOffset() + 7,\n       \n module.getImportStatementList().get(module.getImportStatementList().size()-1).getTextRange().getEndOffset() + 1))\n  );\n}<\/pre>\n\n\n\n<p>La parte m\u00e1s dif\u00edcil de ajustar son los par\u00e1metros para los TextRange.  Para los imports, queremos que cuando el plegado est\u00e1 cerrado, se vea la palabra import seguido de puntos suspensivos. Como la palabra &#8220;import&#8221; tiene 7 letras, el text rage empieza en el inicio del offset + 7. El fin del plegado es es \u00faltimo punto y coma (&#8220;;&#8221;) + 1, para que no se vea el &#8220;;&#8221;.  Para los bloques, recordemos que est\u00e1n definidos por:<\/p>\n\n\n\n<pre class=\"wp-block-syntaxhighlighter-code\">...\n\/\/ Statements\nstatementBlock ::= '{' statement* '}'\n...<\/pre>\n\n\n\n<p>En este caso no queremos cambiar el TextRange ya que quermos que se vean los corchetes que est\u00e1n plegados. Por \u00faltimo, el m\u00e9todo <em>getPlaceholderText<\/em> nos permite cambiar el texto que se muestra en los pliegues. En nuestro caso retornamos puntos suspensivos.<\/p>\n\n\n\n<pre class=\"wp-block-syntaxhighlighter-code\">package org.eclipse.epislon.labs.intellij.grammar.language.eol;\n\nimport com.intellij.lang.ASTNode;\nimport com.intellij.lang.folding.*;\nimport com.intellij.openapi.editor.*;\nimport com.intellij.openapi.util.TextRange;\nimport com.intellij.psi.*;\nimport com.intellij.psi.util.PsiTreeUtil;\nimport org.eclipse.epislon.labs.intellij.grammar.psi.EpsilonModule;\nimport org.eclipse.epislon.labs.intellij.grammar.psi.eol.*;\nimport org.jetbrains.annotations.*;\n\nimport java.util.*;\n\npublic class EolFoldingBuilder extends FoldingBuilderEx {\n\n  public class EolStatementBlockFolding extends FoldingDescriptor {\n\n    public EolStatementBlockFolding(@NotNull ASTNode node, @NotNull TextRange range) {\n      super(node, range);\n    }\n\n    @Nullable\n    @Override\n    public String getPlaceholderText() {\n      return \"...\";\n    }\n  }\n\n  public class ImportStatementBlockFolding extends FoldingDescriptor {\n\n    public ImportStatementBlockFolding(@NotNull ASTNode node, @NotNull TextRange range) {\n      super(node, range);\n    }\n\n    @Nullable\n    @Override\n    public String getPlaceholderText() {\n      return \"...\";\n    }\n  }\n\n\n  @NotNull\n  @Override\n  public FoldingDescriptor[] buildFoldRegions(@NotNull PsiElement root, @NotNull Document document, boolean quick) {\n\n    List&lt;FoldingDescriptor> descriptors = new ArrayList&lt;>();\n    \/\/ imports\n    EpsilonModule module = PsiTreeUtil.findChildOfAnyType(root, false, EpsilonModule.class);\n    if ((module != null) &amp;&amp; !module.getImportStatementList().isEmpty()) {\n      descriptors.add(new ImportStatementBlockFolding(\n          module.getNode(),\n          new TextRange(module.getImportStatementList().get(0).getTextRange().getStartOffset() + 7,\n                module.getImportStatementList().get(module.getImportStatementList().size()-1).getTextRange().getEndOffset() + 1))\n      );\n    }\n    if (!quick) {\n      \/\/ all StatementBlocks are collapsible, and since most expressions with a block use a StatementBlock we cover\n      \/\/ most required foldings\n      for (final EolStatementBlock stmt : PsiTreeUtil.findChildrenOfAnyType(root, false, EolStatementBlock.class)) {\n        createBlockFoldingDescriptor(descriptors, stmt, stmt.getNode());\n      }\n    }\n    return descriptors.toArray(new FoldingDescriptor[descriptors.size()]);\n  }\n\n  @Nullable\n  @Override\n  public String getPlaceholderText(@NotNull ASTNode node) {\n    return \"...\";\n  }\n\n  \/\/ Only calls for nodes that have a FoldingDescriptor\n  @Override\n  public boolean isCollapsedByDefault(@NotNull ASTNode node) {\n    if (node.getElementType().equals(EolTypes.EOL_MODULE)) {\n      return true;\n    }\n    return false;\n  }\n\n  protected void createBlockFoldingDescriptor(\n      List&lt;FoldingDescriptor> descriptors,\n      EolStatementBlock statementBlock,\n      ASTNode node) {\n    if (statementBlock != null) {\n      descriptors.add(new EolStatementBlockFolding(\n          node,\n          new TextRange(\n              statementBlock.getTextRange().getStartOffset() + 1,\n              statementBlock.getTextRange().getEndOffset() - 1)));\n    }\n  }\n}\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">El resutlado<\/h2>\n\n\n\n<p>A continuaci\u00f3n hay una captura de pantalla con el editor de EOL abierto en el fondo y las opciones de realce de sintaxis en el frente.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"http:\/\/kinori.tech\/blog\/wp-content\/uploads\/2020\/10\/result-808x1024.jpg\" alt=\"\"\/><\/figure>\n\n\n\n<p>Los editores se pueden instalar <a href=\"https:\/\/plugins.jetbrains.com\/plugin\/15202-epsilon-languages-editors\">ac\u00e1<\/a>.<\/p>\n\n\n\n<script type=\"text\/javascript\" src=\"https:\/\/cdnjs.buymeacoffee.com\/1.0.0\/button.prod.min.js\" data-name=\"bmc-button\" data-slug=\"KinoriTech\" data-color=\"#79D6B5\" data-emoji=\"\" data-font=\"Cookie\" data-text=\"Comprame un caf\u00e9\" data-outline-color=\"#000\" data-font-color=\"#fff\" data-coffee-color=\"#fd0\"><\/script>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>(you can also read this post in\u00a0english) Esta es la Parte 1 de la serie &#8220;Un hogar para tu Lenguaje (de programaci\u00f3n) en IntelliJ&#8221;. Por fin lograste escribir el compilador o m\u00e1quina de ejecuci\u00f3n para tu nuevo y reluciente lenguaje. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"series":[],"class_list":["post-32","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"http:\/\/kinori.tech\/blog\/es\/wp-json\/wp\/v2\/posts\/32","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/kinori.tech\/blog\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/kinori.tech\/blog\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/kinori.tech\/blog\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/kinori.tech\/blog\/es\/wp-json\/wp\/v2\/comments?post=32"}],"version-history":[{"count":23,"href":"http:\/\/kinori.tech\/blog\/es\/wp-json\/wp\/v2\/posts\/32\/revisions"}],"predecessor-version":[{"id":57,"href":"http:\/\/kinori.tech\/blog\/es\/wp-json\/wp\/v2\/posts\/32\/revisions\/57"}],"wp:attachment":[{"href":"http:\/\/kinori.tech\/blog\/es\/wp-json\/wp\/v2\/media?parent=32"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/kinori.tech\/blog\/es\/wp-json\/wp\/v2\/categories?post=32"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/kinori.tech\/blog\/es\/wp-json\/wp\/v2\/tags?post=32"},{"taxonomy":"series","embeddable":true,"href":"http:\/\/kinori.tech\/blog\/es\/wp-json\/wp\/v2\/series?post=32"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}