Skip to content
English

Stores et synchronisation ​

Le problème à éviter ​

Un IDE affiche plusieurs vues d'un même état : l'arborescence, les onglets ouverts, le statut Git, les diffs. Quand un fichier change (sauvegarde, commande du terminal, checkout Git, import d'un projet), toutes doivent suivre.

La première version de CapyCode laissait chaque appelant rafraîchir lui-même ce qu'il pensait concerné (refreshTree(), gitStore.refresh(), reloadOpenTabs(), notifyFileRenamed()…). Résultat : des dizaines d'appels dispersés, des oublis (le statut Git ne suivait pas une sauvegarde) et des doublons (l'arborescence relue à chaque Ctrl+S, l'historique Git recalculé pour indexer un fichier).

Le principe : les services notifient, les stores s'abonnent ​

Deux services sont la source de vérité :

  • vfs notifie toute modification du workspace : write, mkdir, delete, rename, ou reset (contenu changé de façon quelconque : checkout, import de projet). isomorphic-git écrivant directement dans LightningFS, le service Git appelle vfs.notifyChange() après un checkout ou une fusion.
  • gitService notifie ses opérations : index (indexer, désindexer) ou refs (commit, branches, tags, checkout).

Chaque store s'abonne à ce qui le concerne, et à rien d'autre :

StoreÉcouteRéaction
fileSystemvfsrelit l'arborescence, une fois par lot
gitvfs, gitServicefichier de travail modifié ou index → statut seul ; .git touché, reset ou refs → tout (branches, tags, statut, historique)
editorvfs, gitServicerenomme ou ferme les onglets, relit les fichiers modifiés hors de l'éditeur, met à jour les diffs

Les appelants n'ont plus rien à rafraîchir : ils écrivent, c'est tout.

Une écriture se termine quand l'IDE est à jour ​

Les abonnés sont appelés de façon asynchrone mais attendus : await vfs.writeTextFile(…) ne se résout qu'une fois l'arborescence, le statut Git et les onglets à jour. Le code reste séquentiel et prévisible, et les tests n'ont pas besoin d'attendre « un peu ».

Les lots ​

Une opération en masse (copie récursive, import ZIP, rejeu d'une activité partagée) ne doit pas déclencher un rafraîchissement par fichier :

  • vfs.batch(task) diffère les notifications du VFS jusqu'à la fin de task, puis les envoie en un seul lot ;
  • gitService.batch(task) fait de même pour Git (une seule notification refs à la fin) ;
  • chaque commande du terminal est exécutée dans un vfs.batch.

Enfin, les rafraîchissements concurrents sont regroupés (src/utils/coalesce.ts) : si dix notifications arrivent pendant qu'un statut Git est calculé, il n'y aura qu'un seul calcul de plus.

Les stores ​

Seules ces dépendances existent entre stores ; aucune ne sert à la synchronisation. fileSystem, git et editor ne dépendent d'aucun autre store.

StoreRôle
fileSystemArborescence du workspace, dossiers dépliés, fichiers cachés, CRUD de fichiers
editorOnglets (deux volets), contenus et état modifié, diffs, formatage, sauts de ligne
gitDépôt, branches, tags, statut, historique, opérations Git de l'interface
runtimeExécution : sessions des prompts et des terminaux, bouton Exécuter, évaluation, interruption (voir Basthon) ; ne décide pas de l'affichage
consoleConsoles du panneau inférieur : terminaux, prompts, sorties graphiques ; décide où s'affiche une exécution (prompt à ouvrir, console graphique, panneau déplié, mise en forme des résultats)
terminalUne session par terminal : lignes, répertoire courant, historique, invite, sortie au fil de l'eau, input(), programmes Python (voir Terminal)
diagnosticsDiagnostics Ruff par fichier, compteurs de la barre d'état
extensionsExtensions intégrées activées (linter, git, formatter, outline, sql, ocaml), disponibilité, persistance
embeddingOptions réglées par la page hôte (Git activé ou non), voir Intégration dans une page hôte
layout, theme, i18n, pwa, commandPalettePréférences et état d'interface

Le store editor ​

C'est le plus riche : il est composé de modules (src/stores/editor/), assemblés par index.ts.

ModuleRôle
panes.tsOnglets des deux volets, scission, volet actif, glisser-déposer d'onglets
documents.tsContenus ouverts, saisie, sauvegarde, suivi du workspace (fichier renommé, supprimé, modifié ailleurs)
specialTabs.tsOnglets de comparaison (index, copie de travail, commit) et graphe Git
formatting.tsFormatage Ruff, résumé et annulation
requests.tsContrôleurs des éditeurs CodeMirror (annuler, copier, rechercher…, enregistrés par chaque volet), saut à une ligne et position du curseur

Chaque module est une fonction qui reçoit ce dont elle dépend (par exemple createDocuments(contents, panes, onSynced)) ; seul index.ts appelle defineStore.

Quelques choix d'efficacité ​

  • Un seul parcours du disque par lot. L'arborescence complète est lue en une fois ; la liste des fichiers (palette, complétion) en est dérivée.
  • Déplier un dossier ne relit rien : l'état déplié est appliqué à l'arborescence en mémoire. Seul .git, qui contient des milliers d'objets, n'est lu qu'à son premier dépliage.
  • Renommer un dossier est instantané (vfs.rename, natif dans LightningFS) au lieu d'une copie fichier par fichier.
  • Le statut d'un fichier est indexé par chemin : l'explorateur l'interroge pour chaque nœud sans parcourir la liste.

Ajouter une fonctionnalité qui modifie le workspace ​

  1. Écrire par vfs (ou gitService pour une opération Git). Pour plusieurs écritures, les envelopper dans vfs.batch.
  2. Ne rien rafraîchir à la main.
  3. Si un nouvel état doit suivre le workspace, l'abonner dans son store avec onScopeDispose(vfs.onChange(…)).