Skip to content

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Code Editor (cel)

Une bibliothèque Java pour Android qui fournit un éditeur de code complet : moteur d'édition pur, coloration syntaxique incrémentale (voie intégrée et voie grammaires), couche de rendu Canvas et intégration Language Server Protocol.

Ce que c'est — et ce que ce n'est pas :

  • Java, pas de Kotlin ; Canvas, ni Compose ni TextView par ligne — une seule vue dessine tout.
  • Le moteur est écrit intégralement dans ce dépôt ; les dépendances réellement embarquées (LSP4J, Gson) et les grammaires tierces du module optionnel sont attribuées dans NOTICE.
  • Publication : coordonnées Maven jo.codeeditor:<module> — version courante : 4.14.1 (le rendu des inlay hints réparé : plus d'espaces fantômes — le hint vit à la taille du code sur la même baseline, le padding LSP devient exactement un espace net, et les ligatures cèdent au tissage au lieu d'écraser le code ; sur la 4.14.0 des quick fixes complémentaires — entourer / envelopper / transtyper la sélection —, les RÉFÉRENCES et le RENOMMAGE au niveau du fichier avec des portées honnêtes, et l'AUTO-IMPORT servi par le classpath : le nom du runtime devient import en un geste, le défilement compensé ; sur la 4.13.0 de la documentation du CLASSPATH : les vraies surcharges du runtime par réflexion (Integer.parseInt(…), la fiche du symbole sous le caret, et les INDICES LAMBDA — le type fantôme des paramètres de lambda, inféré de l'interface fonctionnelle ; sur la 4.12.0 de la complétion CONTEXTUELLE : constantes d'enum après « . », attributs d'annotation dans « @Anno( », arguments nommés à une position d'argument — la même résolution que l'aide de signature, paramètre actif compris — et le quickfix « Implémenter les membres manquants » : diagnostic unimplemented-members sur le nom de la classe incomplète, stubs @Override générés avant la fermante ; sur la 4.11.0 de l'aide de signature qui montre la VRAIE méthode, branche par branche : paramètres en trois paliers — déjà frappés en accent, actif en gras, à venir en atténué —, signature au-dessus de la ligne quand la complétion vit en dessous, paramètre actif par signature (LSP 3.16) ; sur la 4.10.0 de l'aide qui ne se tait plus : une surcharge à la fois — stepper ‹ i/n › tactile borné, fenêtrage ±1 des longues signatures, peek complet par appui long —, repli local sur réponse VIDE du résolveur, rangées de complétion à trois axes (libellé seul, container à gauche, détail à droite), coins arrondis 14 dp ; sur la 4.9.1 du popup deux lignes : l'identité qualifiée vit sous le nom, jars du moteur regex intégrés au dépôt ; sur la 4.9.0 du tir sémantique indépendant : les couleurs ne dépendent plus des publications de diagnostics — tir à la connexion, re-tir après chaque didChange, purge à la déconnexion, premier tir de plis à la connexion effectif ; sur la 4.8.0 du local qui ne se perd plus : plancher de complétion fusionné, aide de signature redessinée, session vierge neutre text, drapeau callSyntax des profils ; sur la 4.7.0 de la résolution à la demande : la documentation des candidats arrive au completionItem/resolve — dwell 200 ms du popup, ancre stable, cache client ; sur la 4.6.0 de la consolidation : l'apprentissage survit aux sessions — persistance snapshot()/restore() + télémétrie recorded/boosted —, bascule publique du renommage XML lié, zéro dépréciation restante ; sur la 4.5.1 des bancs démo postfix, XML lié, compensation viewport ; sur la 4.5.0 des paliers P2 et P3 du plan de rattrapage : gabarits riches — choix et macros —, apprentissage du classement, barre de touches de code, mode gros fichiers, renommage XML lié, postfix par profil, compensation du viewport ; sur la 4.3.0 du popup qui honore les listes tronquées, la 4.2.0 des yeux d'aperçu ancrés aux lignes des @Preview, la 4.1.0 du corps de carte tactile et la refonte 4.0.0 du moteur de regex de cel-textmate — voir CHANGELOG.md).

Modules

Cinq modules publiables et une application de démonstration — chacun a son README.md détaillé :

Module Rôle Dépend de Détail
:cel-core Moteur pur (sans Android UI) : rope, session, undo, coloration incrémentale, plis, wrap, complétion, snippets, recherche, registre de 29 langages — editor/cel-core
:cel-lsp-api SPI langage : Language, 17 interfaces de providers, 15 modèles de données, contrat hôte EditorHost :cel-core editor/cel-lsp-api
:cel-lsp Intégration LSP4J : projet multi-serveurs, synchronisation document, requêtes bornées :cel-core, :cel-lsp-api (compileOnly) editor/cel-lsp
:cel-textmate Optionnel — voie grammaires (ADR-002 C3) : moteur de grammaires, mapping portées → TokenType, grammaires embarquées :cel-core seul editor/cel-textmate
:cel-ui Vues Android : EditorView (Canvas), renderer, gestes, popups, gutter, recherche, fil d'Ariane, minimap :cel-core, :cel-lsp-api editor/cel-ui
:demo Terrain de jeu (bascule voie intégrée/grammaires) — non publiée :cel-ui, :cel-textmate demo

L'application hôte type ne dépend que de cel-ui (qui apporte transitivement le moteur et le SPI) ; cel-lsp et cel-textmate sont optionnels et s'ajoutent selon les besoins.

Documentation

Document Contenu
FEATURES.md Matrice factuelle des fonctionnalités (implémenté ✅ / piste 🧭), par module et fichier
USAGE.md Guide d'intégration : thèmes, diagnostics, résolveurs, raccourcis, aperçu XML, R8, dépannage
ARCHITECTURE.md Couches, cycle d'édition, organisation interne des packages, interface EditorPreviewHost
BUILD.md Toolchain, commandes, publication Maven (GitHub Packages, JitPack)
CHANGELOG.md Historique des versions (archives : docs/archive/CHANGELOG-archive.md)
CONTRIBUTING.md Mise en place de l'environnement, conventions, processus de PR
AGENT.md Guide des contributeurs agents : commandes vérifiées, pièges connus, règles du dépôt
SECURITY.md Versions prises en charge, signalement des vulnérabilités
editor/<module>/README.md, demo/README.md Par module : rôle, fonctionnalités, liaisons, limites
docs/adr/ Décisions d'architecture enregistrées (ADR-001 audit 3.69.0, ADR-002 multi-langages)
docs/plans/ Plans de travail en cours (décomposition, stabilité API, minification…)
docs/archive/ Rapports historiques figés (audits, livraisons passées) — index et politique

Installation

Via GitHub Packages (méthode principale)

Les artefacts sont publiés sur maven.pkg.github.com avec les coordonnées jo.codeeditor:<module>:<version> (version courante : 4.14.1).

  1. Créez un jeton d'accès personnel GitHub avec le droit read:packages.
  2. Renseignez vos identifiants dans ~/.gradle/gradle.properties :
gpr.user=<votre-utilisateur-github>
gpr.key=<votre-jeton-d-acces>
  1. Ajoutez le dépôt et les dépendances dans l'application consommatrice :
// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven {
            name = "GitHubPackages"
            url = uri("https://maven.pkg.github.com/jjoblab/code-editor")
            credentials {
                username = providers.gradleProperty("gpr.user").get()
                password = providers.gradleProperty("gpr.key").get()
            }
        }
    }
}
// app/build.gradle.kts
dependencies {
    // Le module UI embarque transitivement cel-core et cel-lsp-api.
    implementation("jo.codeeditor:cel-ui:4.8.0")

    // Optionnel — intégration Language Server Protocol (LSP4J).
    // Ses dépendances vers cel-core/cel-lsp-api sont compileOnly :
    // gardez la ligne cel-ui ci-dessus (l'adaptateur LspEditorViewHost
    // du pont LSP y vit — plan 03 : le client LSP ne dépend plus de la vue).
    implementation("jo.codeeditor:cel-lsp:4.8.0")

    // Optionnel — voie grammaires (ADR-002 C3) : coloration par
    // grammaires TextMate, grammaires embarquées + registration
    // hôte (supporter un langage de plus = un FICHIER, pas du Java).
    implementation("jo.codeeditor:cel-textmate:4.8.0")
    // … puis UNE fois au démarrage, avant toute vue éditeur :
    // SyntaxHighlighter.setTextMateTokenizer(TextMateTokenizerImpl.create());
}

Via JitPack (alternative de secours)

Si GitHub Packages n'est pas accessible, JitPack construit la bibliothèque à la demande depuis un tag Git (le fichier jitpack.yml épingle le JDK 17) :

// settings.gradle.kts — ajouter le dépôt JitPack
maven { url = uri("https://jitpack.io") }
dependencies {
    implementation("com.github.jjoblab.code-editor:cel-ui:v4.8.0")
    implementation("com.github.jjoblab.code-editor:cel-lsp:v4.8.0") // optionnel
    implementation("com.github.jjoblab.code-editor:cel-textmate:v4.8.0") // optionnel (voie grammaires)
}

Via les modules locaux (inclusion des sources)

// settings.gradle.kts
include(":cel-core", ":cel-lsp-api", ":cel-lsp", ":cel-ui", ":cel-textmate")
project(":cel-core").projectDir = file("code-editor/editor/cel-core")
project(":cel-lsp-api").projectDir = file("code-editor/editor/cel-lsp-api")
project(":cel-lsp").projectDir = file("code-editor/editor/cel-lsp")
project(":cel-ui").projectDir = file("code-editor/editor/cel-ui")
project(":cel-textmate").projectDir = file("code-editor/editor/cel-textmate")

// app/build.gradle.kts
dependencies {
    implementation(project(":cel-ui"))
}

Essayer avant d'intégrer

L'application de démonstration installe un terrain de jeu complet (35 échantillons, bancs des chantiers récents, bascules, menu) :

./gradlew :demo:assembleDebug
adb install demo/build/outputs/apk/debug/demo-debug.apk

Démarrage rapide

Éditeur basique (pur Java, testable)

import jo.codeeditor.document.EditorDocument;
import jo.codeeditor.session.EditorSession;

EditorDocument doc = EditorDocument.of("public class Hello {\n    \n}");
EditorSession session = new EditorSession(doc);
session.setLanguage("java");

session.setSelection(30);
session.typeChar('(');   // auto-close : Sys(|)
session.undo();          // annule

// Important : quand la session n'est plus utilisée (onDestroy de l'hôte),
// libérez son thread de restyle :
session.dispose();

EditorView (Android)

EditorView editorView = new EditorView(context);
editorView.setSession(session);
editorView.setTheme(EditorTheme.dark());

Enregistrer son propre langage (une entrée de table, zéro Java)

LanguageRegistry.register(LanguageProfile.builder("mylang")
        .family(SyntaxFamily.C_LIKE)
        .alias("ml").extension("ml")
        .keywords("if", "else", "repeat", "until")
        .commentSyntax(new CommentSyntax("#", null, null))
        .newlineTrigger(NewlineTrigger.onSuffix(":")) // +1 après « clé: »
        .build());
session.setLanguage("mylang");

LSP (Language Server Protocol)

LspProject project = new LspProject("/path/to/workspace");
project.addServerDefinition(myServerDefinition); // ex. un serveur Java, un serveur Lua…
LspEditor editor = project.createEditor("file:///path/to/Foo.java");
editor.setHost(new jo.codeeditor.view.LspEditorViewHost(editorView));
editor.connect();
// …
project.shutdown(); // borné à 2 s par serveur

Connecter l'application hôte — aperçu « goutte d'eau » (B27)

La bibliothèque ne sait PAS prévisualiser : c'est l'application hôte qui fournit le rendu via l'interface EditorPreviewHost. Trois familles d'aperçu sont câblées : la feuille flottante (Vue Android, typiquement un WebView — Markdown/HTML), le panneau scindé inline (canvas), et la carte « goutte d'eau » — un popup non modal ancré à l'œil de la gouttière, pensé pour le mobile (il esquive le bloc de frappe, se compacte si l'écran est petit, et suit l'édition en direct).

EditorView editorView = new EditorView(context);
editorView.setSession(session);

// 1) Poser le hôte (rendu + décision d'aperçu).
editorView.setPreviewHost(new MyPreviewHost());

// 2) Dire quel fichier est ouvert — le hôte décide s'il est
//    prévisualisable (l'œil apparaît alors dans la gouttière).
editorView.setFileName("activity_main.xml");

// 3) Rien d'autre : le tap sur l'œil de gouttière ouvre/ferme la carte.
//    (L'API programme : openPreviewDropAt(ligne) / togglePreviewDrop().)

Le hôte minimal pour la carte « goutte d'eau » :

public class MyPreviewHost implements EditorPreviewHost {
    @Override public boolean canPreview(String fileName) {
        return fileName != null && fileName.endsWith(".xml");
    }
    // Les hôtes CANVAS préfèrent la carte ; les hôtes VUE (WebView)
    // laissent ce défaut à faux et gardent la feuille flottante.
    @Override public boolean prefersDropPreview() { return true; }
    @Override public List<String> previewDropTabs() {
        return List.of("XML", "Compose");          // pilules d'en-tête
    }
    @Override public void drawDropPreview(Canvas c, float x, float y,
            float w, float h, int tab) {
        // Dessiner l'écran du mini-téléphone (déjà translaté + clippé).
    }
    @Override public String previewDropBadge(int tab) {
        return "activity_main.xml · gonflé";       // badge d'exécution
    }
    @Override public void onPreviewDropTabSelected(int index) { /* re-rendu */ }
}

Correspondance maquette → implémentation. La carte a été conçue sur une maquette HTML interactive (anti-recouvrement prouvé par QA automatisée sur 18 positions de caret), archivée dans docs/maquettes/preview-drop-gouttiere.html :

Élément de maquette Implémentation
Œil en gouttière (rangée du caret) GutterView pill B9c ; gelé sur la rangée d'ancre quand la carte vit
Tap œil → ouverture / re-tap → fermeture EditorTapResolver → EditorPreviewController.togglePreviewDrop()
Popup « goutte d'eau » (coin asymétrique + goutte pivotée) EditorPreviewDropPainter (Path à coins 18/18/18/4 + losange pivoté 45°)
Placement anti-recouvrement 3 niveaux (ancré → esquive entière → compactage 0,58) EditorPreviewDropGeometry.place() — testé par balayage de 18 positions
Tige de liaison œil↔goutte Placement.stemVisible (au-delà de 8 dp d'écart)
Onglets XML / Compose + ✕ EditorPreviewHost.previewDropTabs() + hit-test partagé dessin/tap
Badge « activity_main.xml · gonflé » EditorPreviewHost.previewDropBadge(tab)
Chip « rendu vivant » + « fit · NN % » poussée de contenu débouncée 200 ms + échelle de compactage réelle
Frappe : la carte esquive, jamais sur la ligne du caret zone de garde caret ± 1 ligne, re-résolue à chaque frame

La maquette complète (contrat des gestes, invariants, algorithme) et le guide pas-à-pas figurent dans USAGE.md (section « Aperçu — feuille, panneau, carte goutte d'eau »).

Le guide complet (thèmes, diagnostics, résolveurs, raccourcis, chords, aperçu XML, grammaires opt-in) figure dans USAGE.md.

Architecture

L'éditeur repose sur une architecture en couches séparées, où chaque composant est pur (sans dépendance UI) et testable unitairement :

┌─────────────────────────────────────────────────┐
│                  EditorView                      │  ← Rendu Android Canvas
│  (gutter, caret, sélection, squiggles, touch)    │
├─────────────────────────────────────────────────┤
│               EditorSession                      │  ← Moteur d'édition (undo/redo, IME)
│  ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│  │ EditOps  │ │ FindRepl │ │ DiagnosticShift  │ │
│  │ (smart)  │ │ (search) │ │ (offset mapping) │ │
│  └──────────┘ └──────────┘ └──────────────────┘ │
├─────────────────────────────────────────────────┤
│ SyntaxHighlighter │ FoldModel │ WrapModel       │  ← Analyse par ligne
│ LineRenderCache   │ SnippetSession              │
├─────────────────────────────────────────────────┤
│            EditorDocument                        │  ← Modèle de texte indexé par ligne
│  ┌──────────────────────────────────────────┐   │
│  │              Rope (arbre équilibré)       │   │  ← O(log N) par édition
│  └──────────────────────────────────────────┘   │
└─────────────────────────────────────────────────┘

Le détail (diagramme des dépendances entre modules, cycle d'édition, interface EditorPreviewHost) figure dans ARCHITECTURE.md.

Tests

./gradlew test
# 3 185 tests, 0 failure :
#   cel-core 1 496 · cel-lsp-api 32 · cel-lsp 238 · cel-ui 1 066 · cel-textmate 96 · demo 257
# (contrôle d'écart : scripts/check-doc-figures.sh, exécuté en CI)

Performances

Opération Complexité Détail
Insertion 1 char O(log N + leafSize) Rope replace, feuille max 512 chars
lineForOffset O(log L) Recherche binaire sur lineStarts
Undo O(texte édité) Coalescence, pas de copie complète
Coloration syntaxique O(ligne éditée) Incrémentale, état de sortie + cascade stop-rule
Cache rendu par ligne O(1) Triple-stamp (texte + inlay + sem), LRU 512
Filtre inlays/sem par ligne O(bucket) Index par ligne mémoïsé
Splice des stamps de révision O(région) Tableaux int[] + System.arraycopy
Prefetch idle — ±1 viewport après 150 ms de calme, chunks de 8
Folds cachés au-dessus d'une ligne O(log folds) Index mémoïsé fusionné + prefix-sums
Ligne pour un Y écran O(log n × log folds) Recherche binaire sur visibleIndex = l − hiddenAbove(l)
maxH (scroll horizontal) O(1) par appel maxCols mémoïsé, scan une fois par édition
Layouts ligatures O(1) sur hit Cache contenu-adressé LRU 64, clé = texte de ligne
Diagnostics par ligne de début O(bucket) Buckets mémoïsés, tri sévérité-desc
Chips diagnostics O(lignes visibles) Itération des buckets groupés, badge de compte
Grands fichiers gating > 2,5 M chars ou > 50 k lignes : analyse/folding/inlays coupés, édition conservée

Limites connues

Écarts assumés et documentés (l'inventaire exhaustif vit dans FEATURES.md § Perspectives) :

  • Édition multi-curseur : non implémentée — une seule sélection par session (plan de conception conservé comme piste).
  • Droite-à-gauche : non supporté — décision explicite de l'ADR-001 (n°7), avant-projet dans docs/plans/05-accessibilite-i18n-rtl.md.
  • Grands fichiers — coloration, plis et inlays coupés au-delà de 2,5 M chars / 50 k lignes (l'édition reste fonctionnelle).
  • Voie grammaires : portes de 800 lignes et de 5 000 caractères — au-delà (en nombre de lignes du document, ou en longueur d'UNE ligne : fichiers minifiés), repli sur le tokéniseur intégré (coût de restyle mesuré supérieur — ADR-002).
  • Chantier multi-langages (ADR-002) : C1/C2/C3/C4 livrés en 3.72.0 — registre de langages, tokéniseur générique piloté par la table, voie grammaires avec bascule en démo, calibrage des portées (voir CHANGELOG.md).

Build

# Prérequis : JDK 17+, Android SDK (platform 34)
./gradlew assembleDebug          # AARs debug des 5 modules + APK démo
./gradlew assembleRelease        # AARs release
./gradlew test                   # tests (AGP 9 : la variante debug porte les tests unitaires)
./gradlew lint                   # lint des 5 modules
./gradlew publishToMavenLocal    # publication Maven locale (jo.codeeditor:*)

Gradle wrapper 9.5.1 · AGP 9.0.0 · compileSdk 34 · minSdk 24 · Java 17 minimum (21 validé) · encodage source UTF-8.

Les instructions complètes (installation de la toolchain, publication Maven) figurent dans BUILD.md.

CI

Le workflow GitHub Actions (.github/workflows/ci.yml) exécute sur chaque push (main) et chaque PR : assembleDebug + testDebugUnitTest + lint sur JDK 17 Temurin, avec cache Gradle et validation des wrapper JARs (gradle/actions/setup-gradle). Sur chaque tag v*, un job release ajoute assembleRelease + publishToMavenLocal (répétition générale de la publication) et uploade les AAR/POM en artefacts. La publication sur GitHub Packages est automatique : pousser un tag de version strict vX.Y.Z déclenche .github/workflows/publish.yml (garde-fou tag = version du build, tests avant publication — voir BUILD.md).

Licence

Apache License 2.0 — texte complet dans LICENSE, attributions embarquées dans NOTICE.

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages