Outils pour utilisateurs

Outils du site


prog:vsc:module_highlight

Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentesRévision précédente
Prochaine révision
Révision précédente
prog:vsc:module_highlight [2021/02/01 16:50] – Ajout de "xxx.tmLanguage.json" rootprog:vsc:module_highlight [2021/02/26 13:22] (Version actuelle) – Ajout de "Utilisation d'un langage existant" root
Ligne 1: Ligne 1:
-====Prérequis====+=====Prérequis=====
  
 La documentation est [[https://code.visualstudio.com/api/|Extension API]] de Visual Studio Code. {{ :prog:vsc:module_highlight:code.visualstudio.com.api.zip |Archive du 11/12/2020 le 01/02/2021}} La documentation est [[https://code.visualstudio.com/api/|Extension API]] de Visual Studio Code. {{ :prog:vsc:module_highlight:code.visualstudio.com.api.zip |Archive du 11/12/2020 le 01/02/2021}}
  
-===Node.js===+====Node.js====
  
 Il faut installer [[prog:nodejs|Node.js]] qui possède une application pour créer un squelette de module. Il faut installer [[prog:nodejs|Node.js]] qui possède une application pour créer un squelette de module.
Ligne 13: Ligne 13:
 </code> </code>
  
-====Création du module====+=====Création du module=====
  
 Générer le module de base pour la coloration syntaxique : Générer le module de base pour la coloration syntaxique :
Ligne 78: Ligne 78:
 </code> </code>
  
-====Syntaxe des fichiers====+=====Syntaxe des fichiers=====
  
-===package.json===+====package.json====
  
 Dans ce fichier, il faut uniquement modifier la partie ''contributes'' (documentation [[https://code.visualstudio.com/api/references/contribution-points|Extension points]]).  Dans ce fichier, il faut uniquement modifier la partie ''contributes'' (documentation [[https://code.visualstudio.com/api/references/contribution-points|Extension points]]). 
  
-  * ''languages''+===languages===
  
 <code javascript> <code javascript>
Ligne 102: Ligne 102:
 </code> </code>
  
-Dans ''languages'', on définit un tableau de langages identifié par un ''id''.+  * ''languages'', on définit un tableau de langages identifié par un ''id''
 +  * ''aliases'' : le premier alias servira de label. 
 +  * ''extensions'' : filtreront les fichiers où s'appliquera la coloration syntaxique. Chaque extension unique ne doit être que dans un seul langage. Si une extension est présente dans deux langages, l'un des deux sera simplement ignoré. 
 +  * ''filenames'' : c'est un filtre qui prend en compte le nom du fichier et pas uniquement son extension. C'est utile pour les fichiers sans extension. 
 +  * ''firstLine'' : on applique une expression régulière sur le contenu de la première ligne du fichier.
  
-Le premier alias servira de label. +===grammars===
- +
-Les ''extensions'' filtreront les fichiers où s'appliquera la coloration syntaxique. Chaque extension unique ne doit être que dans un seul langage. Si une extension est présente dans deux langages, l'un des deux sera simplement ignoré. +
- +
-Avec ''filenames'', c'est un filtre qui prend en compte le nom du fichier et pas uniquement son extension. C'est utile pour les fichiers sans extension. +
- +
-Avec ''firstLine'', on applique une expression régulière sur le contenu de la première ligne du fichier. +
- +
-  * ''grammars''+
  
 <code javascript> <code javascript>
Ligne 138: Ligne 134:
 Il est aussi possible d'étendre un langage existant. Pour cela, il faut connaître le nom de la coloration syntaxique attribué au texte. Cette information peut s'afficher en saisissant la commande ''Developer: Inspect Editor Tokens and Scopes'' via ''CTRL+SHIFT+P''. On note alors ce nom dans le tableau ''injectTo''. La couleur pourra alors être surchargée par les règles définies dans le fichier ''path''. Il est aussi possible d'étendre un langage existant. Pour cela, il faut connaître le nom de la coloration syntaxique attribué au texte. Cette information peut s'afficher en saisissant la commande ''Developer: Inspect Editor Tokens and Scopes'' via ''CTRL+SHIFT+P''. On note alors ce nom dans le tableau ''injectTo''. La couleur pourra alors être surchargée par les règles définies dans le fichier ''path''.
  
-===xxx.tmLanguage.json===+====xxx.tmLanguage.json====
  
 Voir [[https://macromates.com/manual/en/language_grammars|Language Grammars]] pour la syntaxe. Voir [[https://macromates.com/manual/en/language_grammars|Language Grammars]] pour la syntaxe.
  
-L'arborescence de base est :+===Arborescence de base===
  
 <code javascript> <code javascript>
- 
 { {
- "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", +    "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 
- "name": "CELLman", +    "name": "CELLman", 
- "patterns":+    "patterns":
- +        
- "include": "#comments" +            "include": "#comments" 
- +        
- ], +    ], 
- "repository":+    "repository":
- "comments": ... +        "comments": ... 
- }, +    }, 
- "scopeName": "source.common"+    "scopeName": "source.common"
 } }
 </code> </code>
Ligne 163: Ligne 158:
 avec 3 champs à personnaliser : avec 3 champs à personnaliser :
  
-''patterns'' :+  * ''patterns''ce tableau ne contient que les noms qui font référence aux noms contenu dans le champ ''repository''. En pratique il peut contenir d'autres données mais l'utilisation exclusive du ''include'' est une bonne pratique du générateur automatique.
  
-''repository'' :+  * ''repository''contient la liste des patterns dont le nom est défini précédemment. 
 + 
 +  * ''scopeName'' : doit être le même que celui définit dans le fichier ''package.json'' à la rubrique ''grammars''
 + 
 +===pattern=== 
 + 
 +Chaque ''repository'' contient un dictionnaire de pattern. 
 + 
 +  * ''match'' 
 + 
 +Il est possible de ne définir que le pattern avec son type de contenu. Ici, on repère 4 mots clés (''if'', ''while'', ''for'', ''return''). 
 + 
 +Le nom doit impérativement respecter la [[https://macromates.com/manual/en/language_grammars|syntaxe]] de TextMate (§12.4 ''Naming Conventions''). Ici, ''keyword.control'' signifie « mainly related to flow control ». Ensuite, on rajoute un dernier ''.'' avec l'abréviation du langage. 
 + 
 +<code javascript> 
 +        "keywords":
 +            "patterns": [{ 
 +                "name": "keyword.control.cell", 
 +                "match": "\\b(if|while|for|return)\\b" 
 +            }] 
 +        }, 
 +</code> 
 + 
 +  * ''begin'' et ''end'' 
 + 
 +Dans l'autre exemple ci-dessous, le symbole ''%%"%%'' et ''%%"%%'' désigne le début (''begin'') et la fin (''end'') d'un texte. Là où l'utilisation de ''match'' se limite à une seule ligne, ''begin'' et ''end'' peut être sur une ou plusieurs lignes. Ce texte aura comme coloration syntaxique ''string.quoted.double''
 + 
 +Ensuite, à l'intérieur de ce texte délimité par ''begin'' et ''end'', on cherche tous les ''patterns'' ''\\.'' (tous les caractères d'échappement) et on lui applique la coloration syntaxique ''constant.character.escape''
 + 
 +Il y a aussi un double intérêt. Si le caractère d'échappement ''%%\"%%'' apparait, le guillement sera considéré comme un pattern et ne pourra pas être considéré par la contrainte ''end''. Textmate lisant de gauche à droite et le pattern commençant un caratère avant, ''patterns'' sera prioritaire au ''end''. Par contre, si ''patterns'' et ''end'' commencent au même caratère, c'est ''end'' qui sera prioritaire. 
 + 
 +<code javascript> 
 +        "strings":
 +            "name": "string.quoted.double.cell", 
 +            "begin": "\"", 
 +            "end": "\"", 
 +            "patterns":
 +                { 
 +                    "name": "constant.character.escape.cell", 
 +                    "match": "\\\\." 
 +                } 
 +            ] 
 +        } 
 +</code> 
 + 
 +Il est aussi possible de colorer en fonction des groupes de l'expression régulière via ''captures''
 + 
 +<code javascript> 
 +        "strings":
 +            "name": "string.quoted.double.cell", 
 +            "match": "\\\"(.*)\\\"", 
 +            "captures":
 +                "1":
 +                    "name": "string.inside.cell" 
 +                } 
 +            } 
 +        } 
 +</code> 
 + 
 +Il est aussi possible d'utiliser ''beginCaptures'' et ''endCaptures'' sur les textes correspondant aux expressions régulières de ''begin'' et ''end''
 + 
 +En plus de ''name'', il existe ''contentName''. Si on applique ''name'' à un pattern, il s'appliquera au texte désigné par ''begin'', ''end'' et le texte au milieu. Par contre, ''contentName'' ne va définir le nom que pour le texte entre les textes désignés par ''begin'' et ''end''. Si les deux sont appliqués, c'est le nom définit par ''contentName'' qui sera prioritaire. 
 + 
 +Il est aussi possible d'utiliser ''begin'' et ''end'' et de réutiliser leurs contenus dans ''pattern'' via les [[lang:regex#|Positive and Negative Lookahead]]. 
 + 
 +<WRAP center round important 60%> 
 +Le pattern de récursion ne fonctionnera plus. 
 +</WRAP> 
 + 
 + 
 +<code javascript> 
 +"identifiant":
 +    "begin": "(?=^\\s*(if)\\s)", 
 +    "end": "(?<=^\\s*end_if\\s*$)", 
 +    "name": "meta.if.definition.cellman.TRAN", 
 +    "pattern": [...] 
 +}, 
 +</code> 
 + 
 + 
 +====Bonnes pratiques==== 
 + 
 +===Principe=== 
 + 
 +L'idéal est d'avoir la sémantique du langage et de le convertir en expression régulière dans un format compatible. 
 + 
 +Il est donc préférable d'identifier chaque type de ligne et de construire les expressions régulières correspondantes. 
 + 
 +===Exemple=== 
 + 
 +  * ''package.json'' 
 + 
 +<file javascript package.json> 
 +
 +    "name": "cell-lang", 
 +    "displayName": "Cell Lang", 
 +    "description": "Syntax highlight for cell lang", 
 +    "version": "0.0.1", 
 +    "engines":
 +        "vscode": "^1.52.0" 
 +    }, 
 +    "categories":
 +        "Programming Languages" 
 +    ], 
 +    "contributes":
 +        "languages":
 +            { 
 +                "id": "lang", 
 +                "aliases":
 +                    "lang", 
 +                    "lang" 
 +                ], 
 +                "extensions":
 +                    "lang" 
 +                ], 
 +                "configuration": "./language-configuration.json" 
 +            } 
 +        ], 
 +        "grammars":
 +            { 
 +                "language": "lang", 
 +                "scopeName": "source.lang", 
 +                "path": "./syntaxes/lang.tmLanguage.json" 
 +            } 
 +        ] 
 +    } 
 +
 +</file> 
 + 
 +  * ''lang.tmLanguage.json'' 
 + 
 +Le schéma à respecter est [[https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json|tmlanguage.json]]. 
 + 
 +Il est important de comprendre le fichier ''.json'' en exemple, il contient des explications sur comment rédiger les règles en respectant la sémantique et aussi comment gérer les types if/while/... qui sont des blocs d'instructions qui peut être imbriqués. 
 + 
 +<file javascript lang.tmLanguage.json> 
 +
 +    "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 
 +    "name": "cell", 
 +    "// Le fichier contient soit des fonctions, soit du code source.": "", 
 +    "patterns":
 +        { 
 +            "include": "#function" 
 +        }, 
 +        { 
 +            "include": "#code" 
 +        }, 
 +        { 
 +            "match": ".*", 
 +            "name": "invalid.source.lang" 
 +        } 
 +    ], 
 +    "// Dans le champ repository, on stocke et nomme toutes les regex.": "", 
 +    "repository":
 +        "// Regex caractérisant un float.": {}, 
 +        "// Ici, l'ordre n'a pas d'important.": {}, 
 +        "// Un type peut faire référence (include) à un autre type qui sera défini plus tard.": {}, 
 +        "float":
 +            "name": "constant.numeric.float.lang", 
 +            "match": "\\b[0-9]+\\.[0-9]*(f|d)\\b" 
 +        }, 
 +        "hex":
 +            "name": "constant.numeric.hex.lang", 
 +            "match": "\\b0(x|X)[0-9a-fA-F]+\\b" 
 +        }, 
 +        "integer":
 +            "name": "constant.numeric.integer.lang", 
 +            "match": "\\b[0-9]+\\b" 
 +        }, 
 +        "number":
 +            "// Regex caractérisant un int.": "", 
 +            "// Il est important de mettre le cas le plus spécialisé en premier.": "", 
 +            "// Si on avait mis integer en premier, on n'aurait jamais réussi à faire matcher float": "", 
 +            "// car les parties entière et décimale seraient détectées comme int": "", 
 +            "patterns":
 +                { 
 +                    "include": "#float" 
 +                }, 
 +                { 
 +                    "include": "#hex" 
 +                }, 
 +                { 
 +                    "include": "#integer" 
 +                } 
 +            ] 
 +        }, 
 +        "expression":
 +            "// Pour faire simple, on dit qu'un expression, c'est soit :": "", 
 +            "//   - deux nombres avec un opérateur arithmétique au milieu,": "", 
 +            "//   - un nombre.": "", 
 +            "patterns":
 +                { 
 +                    "match": "(.*)\\b\\s*(\\+|\\/|\\*|-)\\b\\s*(.*)\\b", 
 +                    "name": "string.regexp.expr.lang", 
 +                    "captures":
 +                        "1":
 +                            "patterns":
 +                                { 
 +                                    "include": "#number" 
 +                                } 
 +                            ] 
 +                        }, 
 +                        "2":
 +                            "name": "keyword.operator.expr.lang" 
 +                        }, 
 +                        "3":
 +                            "patterns":
 +                                { 
 +                                    "include": "#number" 
 +                                } 
 +                            ] 
 +                        } 
 +                    } 
 +                }, 
 +                { 
 +                    "include": "#number" 
 +                } 
 +            ] 
 +        }, 
 +        "primary_type":
 +            "// On suppose que les types sont des nombres uniquement.": "", 
 +            "// Si le type est inconnu, on le déclare invalide.": "", 
 +            "// Attention à ne pas déclarer invalide comme fallthrough à chaque pattern.": "", 
 +            "// Sinon, les patterns qui include le type s'arrêteront toujours au fallthrough": "", 
 +            "// sans lire les étapes suivantes.": "", 
 +            "patterns":
 +                { 
 +                    "match": "(int|short|char|double|float)", 
 +                    "name": "storage.type.lang" 
 +                }, 
 +                { 
 +                    "match": ".*", 
 +                    "name": "invalid.primary_type.lang" 
 +                } 
 +            ] 
 +        }, 
 +        "init_variable":
 +            "// Une initialisation de variable se faire :": "", 
 +            "// int variable = expression;": "", 
 +            "patterns":
 +                { 
 +                    "name": "meta.expr.init_variable.lang", 
 +                    "match": "\\s*([^\\s]+)\\b\\s*(.+)\\b\\s*=\\s*(.+);", 
 +                    "captures":
 +                        "1":
 +                            "patterns":
 +                                { 
 +                                    "include": "#primary_type" 
 +                                } 
 +                            ] 
 +                        }, 
 +                        "2":
 +                            "name": "variable.name.lang" 
 +                        }, 
 +                        "3":
 +                            "patterns":
 +                                { 
 +                                    "include": "#expression" 
 +                                } 
 +                            ] 
 +                        } 
 +                    } 
 +                } 
 +            ] 
 +        }, 
 +        "if":
 +            "// Un if a la syntaxe suivante :": "", 
 +            "// if expression then": "", 
 +            "//   du code.": "", 
 +            "// fi": "", 
 +            "// La déclaration de la partie code est faite dans l'identifiant code ci-après": "", 
 +            "begin": "^(\\s*)(if)(.*)(then)\\s*", 
 +            "beginCaptures":
 +                "2":
 +                    "name": "keyword.control.lang" 
 +                }, 
 +                "3":
 +                    "patterns":
 +                        { 
 +                            "include": "#expression" 
 +                        } 
 +                    ] 
 +                }, 
 +                "4":
 +                    "name": "keyword.control.lang" 
 +                } 
 +            }, 
 +            "// Ici, on utilise le groupe \\1 qui fait référence au groupe 1 de begin": "", 
 +            "// Comme il n'est pas possible de détecter les fi correspondant aux if": "", 
 +            "// dans le cas de if imbriqués, on utilise l'indentation pour la correspondance.": "", 
 +            "end": "^(\\1)(fi)\\s*", 
 +            "endCaptures":
 +                "2":
 +                    "name": "keyword.control.lang" 
 +                } 
 +            }, 
 +            "name": "meta.body.if.definition.lang", 
 +            "patterns":
 +                { 
 +                    "include": "#code" 
 +                } 
 +            ] 
 +        }, 
 +        "code":
 +            "// Le code représente des instructions entières.": "", 
 +            "// Ici, on considère que des nombres et expression peuvent": "", 
 +            "// être des instructions mais cela aurait pu être interdit.": "", 
 +            "patterns":
 +                { 
 +                    "include": "#if" 
 +                }, 
 +                { 
 +                    "include": "#init_variable" 
 +                }, 
 +                { 
 +                    "include": "#expression" 
 +                }, 
 +                { 
 +                    "include": "#number" 
 +                } 
 +            ] 
 +        }, 
 +        "function":
 +            "begin": "^\\s*(function)\\s+([^\\s]*)\\s*$", 
 +            "beginCaptures":
 +                "1":
 +                    "name": "storage.type.function.lang" 
 +                }, 
 +                "2":
 +                    "name": "entity.name.method.lang" 
 +                } 
 +            }, 
 +            "end": "\\s*(end_function)\\s*", 
 +            "endCaptures":
 +                "1":
 +                    "name": "keyword.other.lang" 
 +                } 
 +            }, 
 +            "name": "meta.body.function.definition.lang", 
 +            "patterns":
 +                { 
 +                    "include": "#code" 
 +                } 
 +            ] 
 +        } 
 +    }, 
 +    "scopeName": "source.lang" 
 +
 +</file> 
 + 
 +|<file lang test.lang> 
 +text not supported 
 + 
 +156 
 +156.6f 
 +0x156aE 
 + 
 +123+456 
 + 
 +float eiuaf = 123; 
 + 
 +eiua eiua = 123; 
 + 
 +function xxx 
 +  double eiuad = 123.3d; 
 +  if 156 = 156 then 
 +    if 156 = 156 then 
 +      int eiua = 123.3d; 
 +    fi 
 +    int eiua = 123.3d; 
 +  fi 
 +end_function 
 + 
 +156 
 +</file>|Version colorée : \\ \\ {{:prog:vsc:module_highlight:hl_example_1.png?200|}}| 
 + 
 +====Utilisation d'un langage existant==== 
 +Une partie du code peut intégrer un langage existant. 
 + 
 +Il faut définir qu'on va s'injecter dans le ''scopeName'' dans ''package.json''
 + 
 +<code javascript> 
 +
 +  "language": "cellman_stave", 
 +  "scopeName": "source.cellman.stave", 
 +  "path": "./syntaxes/cellman.tmLanguage.stave.json" 
 +}, 
 +
 +  "scopeName": "shell.injection", 
 +  "path": "./syntaxes/shellscriptInjection.tmGrammar.json", 
 +  "injectTo":
 +    "source.cellman.stave" 
 +  ], 
 +  "embeddedLanguages":
 +    "meta.embedded.shellscript": "shellscript" 
 +  } 
 +
 +</code> 
 + 
 +<file javascript shellscriptInjection.tmGrammar.json> 
 +
 +  "scopeName": "shell.injection", 
 +  "injectionSelector": "R:meta.embedded.shellscript", 
 +  "patterns":
 +    { 
 +      "include": "source.shell" 
 +    } 
 +  ] 
 +
 +</file> 
 + 
 +Utilisation du langage importé. Il faut impérativement utiliser ''begin'', ''end'' et ''contentName'' qui sont multiligne. 
 + 
 +Pour être utiliser en single line, on peut tricher... 
 + 
 +<code javascript> 
 +
 +  "begin": ".", 
 +  "end": "$", 
 +  "contentName": "meta.embedded.shellscript" 
 +
 +</code> 
 + 
 +====Coloration personnalisée==== 
 + 
 +Afin d'avoir une coloration syntaxique sans devoir personnaliser les couleurs, il est impératif que les noms respectent les règles de la [[https://macromates.com/manual/en/language_grammars|syntaxe grammaticale]]. 
 + 
 +A défaut, il faudra définir les couleurs manuellement dans les préférences de l'utilisateur (fichier ''settings.json'') en plus de l'installation de l'extension. 
 + 
 +Gros inconvénient, cela devra être fait pour chaque thème existant. 
 + 
 +<code javascript> 
 +    "editor.tokenColorCustomizations":
 +        "[Default Dark+]":
 +            "textMateRules":
 +                { 
 +                    "scope":
 +                        "source.cellman.TRAN entity.name.extended.language", 
 +                        "Same color than entity.name.function" 
 +                    ], 
 +                    "settings":
 +                        "foreground": "#DCDCAA", 
 +                    } 
 +                } 
 +            ] 
 +        }, 
 +    } 
 +</code> 
 + 
 +====Générer le module==== 
 + 
 +===En local=== 
 + 
 +Il faut ajouter le champ ''publisher'' dans ''package.json'' et lancer la commande : 
 + 
 +<code bash> 
 +vsce package 
 +</code> 
 + 
 +Et pour installer le module dans Visual Studio Code : 
 + 
 +<code bash> 
 +code --install-extension myextension.vsix 
 +</code>
  
-''scopeName'' : doit être le même que celui définit dans le fichier ''package.json'' à la rubrique ''grammars''. 
prog/vsc/module_highlight.1612194608.txt.gz · Dernière modification : 2021/02/01 16:50 de root