Éditeur
L'éditeur de code est CodeMirror 6 : modulaire, léger (contrairement à Monaco, l'éditeur de VS Code), utilisable sur tablette, et dont l'état est une valeur immuable qu'on transforme par transactions. CapyCode l'entoure de deux volets d'onglets, de comparaisons, d'une recherche et d'une palette de commandes.
Les deux volets
L'éditeur peut être scindé en deux volets, left et right (Ctrl+\). Chaque volet a ses onglets ; un fichier ouvert dans les deux partage le même contenu.
Chaque module est une fonction qui reçoit ce dont elle dépend (createDocuments(contents, panes, onSynced)) ; seul index.ts appelle defineStore. Un onglet (EditorTab) est un fichier, une image (ImageViewer), une comparaison (diff://…, DiffEditor) ou le graphe Git (git://graph, GitGraphView).
Le document et la sauvegarde
fileContentsgarde le texte en cours d'édition de chaque fichier ouvert,savedContentscelui du disque : un onglet est modifié quand ils diffèrent.CodeEditortransmet chaque frappe au store (updateContent) ; quand le contenu change hors de la frappe (fichier rechargé, formatage), le composant l'applique à CodeMirror en marquant la mise à jour comme programmatique, pour ne pas la renvoyer au store.- Pas d'enregistrement automatique : Ctrl+S (
saveFile) écrit parvfs.writeTextFile. Le bouton Exécuter, les commandesgitetpythondu terminal enregistrent d'abord les fichiers concernés (saveEditorsdu contexte du terminal). - Suivi du workspace (
documents.ts, abonné au VFS) : un fichier renommé garde ses onglets (followRename), un fichier supprimé les ferme (closeDeleted), un fichier modifié ailleurs (terminal, checkout, import) est relu (reloadFromDisk) s'il n'a pas de modification en cours.
Le composant CodeEditor
src/components/editor/CodeEditor.vue crée la vue CodeMirror d'un volet. Les extensions qui dépendent du fichier ou des préférences sont dans des compartiments, reconfigurés sans recréer l'éditeur :
| Compartiment | Contenu |
|---|---|
languageCompartment | coloration et complétion du langage (languageForFile(path).syntax()) |
linterCompartment | analyse à la frappe, si le langage en fournit une et si l'extension Linter est activée (voir Analyse Python) |
foldingCompartment | repliage de blocs de code dans la gouttière, conditionné par l'extension Structure du document |
localThemeCompartment | thème sombre ou clair (vscode-theme.ts) |
labelCompartment | nom accessible de la zone de code (« Code de main.py ») |
conflictCompartment | blocs de conflit d'une fusion (conflictMarkers.ts), seulement si Git est activé par la page hôte ; suit la langue |
Le reste est fixe : numéros de ligne, historique, fermeture des parenthèses, complétion, recherche, mise en évidence des occurrences.
Les blocs de conflit
Après une fusion en conflit (voir Git), un fichier contient des blocs <<<<<<< / ======= / >>>>>>>. src/components/editor/conflictMarkers.ts les relit à chaque modification (findConflicts, src/utils/conflicts.ts), surligne la version actuelle (vert) et la version entrante (bleu), et place au-dessus de chaque bloc les boutons « Garder la version actuelle », « Garder la version entrante », « Garder les deux », comme les CodeLens de VS Code. Une résolution est une transaction ordinaire : elle s'annule par Ctrl+Z.
Ces boutons sont dans la zone de saisie : pour ne pas imbriquer des contrôles dans un champ, ils sont aria-hidden et réservés à la souris. Au clavier et pour les lecteurs d'écran, les mêmes actions sont des commandes (palette : « Conflit : garder la version actuelle »…, « Conflit : aller au suivant / au précédent »), qui passent par l'EditorController du volet (resolveConflict, gotoConflict) et agissent sur le conflit sous le curseur, ou le suivant.
Un état par onglet
Un volet n'a qu'une vue CodeMirror, mais chacun de ses onglets garde son EditorState (texte, curseur, historique d'annulation) tant qu'il est ouvert, comme dans VS Code : Ctrl+Z annule encore après un aller-retour entre onglets. CodeEditor range l'état de l'onglet qu'il quitte dans savedStates et restaure celui de l'onglet affiché (view.setState), puis reconfigure ses compartiments (langue, thème ou nom ont pu changer entre-temps).
- La clé est l'onglet (
EditorTab) lui-même, pas son chemin : un fichier renommé garde le même onglet, dont le store change le chemin, et donc son historique. - Un onglet fermé libère son état ; rouvert, il repart d'un historique vide. Un onglet déplacé dans l'autre volet aussi.
- Si le fichier a changé pendant que l'onglet était caché (autre volet, terminal, Git), le texte restauré est remplacé par le contenu du store, par une transaction marquée
isProgrammaticUpdatequi ne revient pas vers le store.
Les actions venues de l'extérieur
Les commandes de l'IDE (annuler, copier, rechercher, commenter…) ne passent pas par un état réactif que l'éditeur surveillerait : à son montage, chaque CodeEditor enregistre un EditorController pour son volet (registerEditor), et editorStore.undo(), openSearch()… l'appellent directement. Seul le saut à une ligne (jumpToLocation, depuis la recherche, le plan ou un problème) reste un état (pendingJump), car il peut viser un fichier pas encore affiché.
Le clavier
Les raccourcis de l'IDE sont des commandes globales, écoutées avant CodeMirror. Le keymap de CodeMirror ne contient que l'édition elle-même : indentation (Tab), historique, commentaire, et Échap, qui ferme la recherche ou, sinon, active le mode « Tab sort de l'éditeur » (temporarilySetTabFocusMode) pour qu'on ne soit jamais piégé au clavier (RGAA 12.9).
La recherche
- Dans le fichier :
FindReplaceWidget.vue, au-dessus de l'éditeur (casse, mot entier, expression régulière, remplacement), branché sur le module de recherche de CodeMirror. - Dans les fichiers (Ctrl+Maj+F) :
SearchPanel.vuedans le volet latéral,src/services/search.tspour la recherche (elle lit les contenus en cours d'édition des fichiers ouverts, pas seulement le disque), et le storeeditorpour « remplacer dans les fichiers », écrit par le VFS en un lot.
Le formatage
Maj+Alt+F formate le fichier si son langage fournit format (Python : Ruff) et si l'extension Formateur de code est activée. formatting.ts garde le texte d'avant et un résumé des différences (src/services/formatDiff.ts) : un toast annonce le nombre de modifications, permet d'annuler le formatage ou d'ouvrir la comparaison (FormatDiffModal). Pour un débutant, voir ce que le formateur a changé fait partie de l'apprentissage.
Le plan
Le volet « Structure » (OutlinePanel) liste classes, fonctions et méthodes du fichier si le langage fournit outline (Python : lecture de l'arbre syntaxique Lezer, src/services/outline.ts) et si l'extension Structure du document est activée. C'est un arbre navigable au clavier (useTreeNavigation) ; choisir un élément y fait sauter l'éditeur.
Les comparaisons
DiffEditor.vue s'appuie sur @codemirror/merge pour comparer deux versions d'un fichier : copie de travail et index, index et HEAD, ou un commit et son parent. Ces onglets sont rafraîchis par les notifications du service Git (voir Git).