Utiliser, créer et publier une extension VS Code en TypeScript : commandes, configuration, vues, webviews, diagnostics, tests, bundling et publication sur le Marketplace et Open VSX.
Une commande accessible uniquement par la palette, c'est un début. Une extension agréable s'intègre là où l'utilisateur travaille : un raccourci, un clic droit, un réglage qu'il ajuste. Tout cela se déclare dans contributes, presque sans code.
contributes est la partie déclarative du manifeste. VS Code la lit sans exécuter l'extension : les commandes apparaissent dans la palette, les réglages dans l'écran Settings, les menus sont en place avant même l'activation. Le code ne se charge qu'au premier usage.
Les plus utilisés :
| Clé | Ce qu'elle ajoute |
|---|---|
commands | Commandes de la palette, avec titre, catégorie et icône |
menus | Entrées dans les menus contextuels, barres de titre, palette |
keybindings | Raccourcis clavier |
configuration | Réglages visibles dans Settings |
views, viewsContainers | Vues latérales (chapitre 4) |
snippets, grammars, languages | Support d'un langage (chapitre 6) |
themes, iconThemes | Thèmes de couleurs et d'icônes |
TODO Radar doit laisser l'utilisateur choisir ses marqueurs et les dossiers à ignorer.
"contributes": {
"configuration": {
"title": "TODO Radar",
"properties": {
"todoRadar.tags": {
"type": "array",
"items": { "type": "string" },
"default": ["TODO", "FIXME", "HACK"],
"description": "Marqueurs à rechercher dans les commentaires."
},
"todoRadar.exclude": {
"type": "string",
"default": "**/{node_modules,dist,out,.git}/**",
"description": "Motif glob des fichiers à ignorer lors du scan du projet."
},
"todoRadar.requireTicket": {
"type": "boolean",
"default": false,
"markdownDescription": "Signaler les `TODO` qui ne référencent pas de ticket (`#123` ou `ABC-123`)."
}
}
}
}Le schéma suit JSON Schema. VS Code en tire l'interface de Settings : une case à cocher pour un booléen, une liste éditable pour un tableau, un menu déroulant si vous ajoutez enum. markdownDescription autorise le code inline et les liens.
Lire un réglage dans le code :
import * as vscode from 'vscode'
import { DEFAULT_TAGS } from './scanner'
export function getConfig() {
const config = vscode.workspace.getConfiguration('todoRadar')
return {
tags: config.get<string[]>('tags', DEFAULT_TAGS),
exclude: config.get<string>('exclude', '**/node_modules/**'),
requireTicket: config.get<boolean>('requireTicket', false),
}
}Un réglage peut changer pendant que l'extension tourne. On écoute l'événement et on filtre sur sa propre section :
context.subscriptions.push(
vscode.workspace.onDidChangeConfiguration((event) => {
if (event.affectsConfiguration('todoRadar')) {
refreshEverything()
}
})
)Écrire un réglage est possible, mais réservé aux cas où l'utilisateur l'a demandé, par exemple un bouton « Ajouter ce marqueur » :
await vscode.workspace
.getConfiguration('todoRadar')
.update('tags', [...tags, 'NOTE'], vscode.ConfigurationTarget.Workspace)ConfigurationTarget.Global écrit dans les réglages utilisateur, Workspace dans .vscode/settings.json du projet.
"keybindings": [
{
"command": "todo-radar.countInFile",
"key": "ctrl+alt+t",
"mac": "cmd+alt+t",
"when": "editorTextFocus"
}
]Choisissez un raccourci peu exposé : il entrera en concurrence avec ceux de VS Code et des autres extensions. L'utilisateur peut toujours le réassigner dans Keyboard Shortcuts.
whenwhen décide quand une contribution est visible ou active. C'est une expression évaluée sur des clés de contexte maintenues par VS Code :
| Clé | Vraie quand |
|---|---|
editorTextFocus | Le curseur est dans un éditeur de texte |
editorLangId == typescript | Le fichier actif est du TypeScript |
resourceExtname == .md | Le fichier ciblé a l'extension .md |
editorHasSelection | Du texte est sélectionné |
view == todoRadar.list | L'élément appartient à la vue todoRadar.list |
isWorkspaceTrusted | Le dossier est marqué de confiance |
Les opérateurs &&, ||, !, ==, !=, =~ (expression régulière) et in se combinent librement. La commande Developer: Inspect Context Keys affiche toutes les clés à l'instant T, indispensable quand un menu refuse d'apparaître.
Une extension peut créer ses propres clés :
await vscode.commands.executeCommand('setContext', 'todoRadar.hasTodos', matches.length > 0)La clé todoRadar.hasTodos devient utilisable dans n'importe quelle clause when du manifeste.
Ajoutons la commande au clic droit dans l'éditeur, et ne la proposons dans la palette que lorsqu'un fichier est ouvert :
"menus": {
"editor/context": [
{
"command": "todo-radar.countInFile",
"group": "navigation@10"
}
],
"commandPalette": [
{
"command": "todo-radar.countInFile",
"when": "editorIsOpen"
}
]
}Pour une commande purement interne, appelée seulement depuis un bouton ou par une autre commande, "when": "false" la retire complètement de la palette.
group place l'entrée dans une section du menu ; navigation est la section du haut, @10 règle l'ordre à l'intérieur. Les emplacements les plus courants sont editor/context, editor/title, explorer/context, view/title et view/item/context.
Depuis VS Code 1.74, les commandes, vues et langages déclarés dans contributes génèrent leurs propres événements d'activation. Pour une extension qui ne réagit qu'aux commandes, activationEvents reste vide.
TODO Radar doit pourtant compter les marqueurs dès qu'un fichier s'ouvre, sans attendre une commande. Plusieurs options :
| Événement | Quand |
|---|---|
onStartupFinished | Juste après le démarrage, sans le ralentir |
onLanguage:python | À l'ouverture d'un fichier Python |
workspaceContains:**/package.json | Si le projet contient ce fichier |
onView:todoRadar.list | À l'ouverture de la vue (implicite si la vue est déclarée) |
onUri | Quand une URL vscode://publisher.extension est ouverte |
* | Au démarrage. À éviter : ralentit l'ouverture de VS Code pour tout le monde |
On retient onStartupFinished :
"activationEvents": ["onStartupFinished"]ExtensionContext donne trois stockages persistants :
| Stockage | Portée | Usage |
|---|---|---|
context.globalState | Toutes les fenêtres | Date du dernier message « quoi de neuf », préférences internes |
context.workspaceState | Le projet ouvert | Dernier filtre appliqué, éléments masqués |
context.secrets | Trousseau du système | Jetons d'API, mots de passe |
// Afficher un message une seule fois après une mise à jour
const version = context.extension.packageJSON.version as string
if (context.globalState.get('lastVersion') !== version) {
vscode.window.showInformationMessage(`TODO Radar ${version} : nouvelle vue des statistiques.`)
await context.globalState.update('lastVersion', version)
}
// Stocker un jeton d'API (GitHub, Jira…) dans le trousseau
await context.secrets.store('todoRadar.jiraToken', token)
const saved = await context.secrets.get('todoRadar.jiraToken')Ne mettez jamais un secret dans globalState ni dans un réglage : les réglages sont synchronisés et souvent commités, globalState est stocké en clair. secrets passe par le trousseau de Windows, macOS ou Linux.
console.log n'est visible que pendant le développement. Pour les utilisateurs, on crée un canal dans le panneau Output :
const log = vscode.window.createOutputChannel('TODO Radar', { log: true })
context.subscriptions.push(log)
log.info('Scan démarré')
log.warn('Fichier trop gros ignoré : ' + uri.fsPath)
log.error(err as Error)Avec { log: true }, chaque ligne est horodatée et le niveau de détail suit le réglage Developer: Set Log Level. Quand un utilisateur ouvre une issue, vous lui demandez le contenu de ce canal.
TODO Radar ne fait que lire des fichiers ; elle peut tourner en mode restreint. On le déclare :
"capabilities": {
"untrustedWorkspaces": { "supported": true },
"virtualWorkspaces": true
}Une extension qui exécute des outils du projet (un linter installé dans node_modules, par exemple) déclare "supported": "limited" et vérifie vscode.workspace.isTrusted avant de lancer quoi que ce soit.
L'extension a des réglages, un raccourci, un menu et s'active au bon moment. Le chapitre suivant lui donne une vraie présence dans l'interface : barre d'état, vue latérale et surlignage.