De nombreux éditeurs et IDE ont une complétion de code. Certains d'entre eux sont très "intelligents", d'autres ne le sont pas vraiment. Je suis intéressé par le type le plus intelligent. Par exemple, j'ai vu des IDE qui n'offrent une fonction que si elle est a) disponible dans la portée actuelle b) sa valeur de retour est valide. (Par exemple, après "5 + foo [tab]", il ne propose que des fonctions qui renvoient quelque chose qui peut être ajouté à un nom entier ou variable du type correct.) J'ai également vu qu'ils placent en avant l'option la plus souvent utilisée ou la plus longue de la liste.
Je réalise que vous devez analyser le code. Mais généralement, lors de l'édition du code actuel n'est pas valide, il y a des erreurs de syntaxe. Comment analysez-vous quelque chose lorsqu'il est incomplet et contient des erreurs?
Il y a aussi une contrainte de temps. La complétion est inutile si cela prend quelques secondes pour arriver à une liste. Parfois, l'algorithme d'achèvement traite des milliers de classes.
Quels sont les bons algorithmes et structures de données pour cela?
la source
Réponses:
Le moteur IntelliSense de mon produit de service de langage UnrealScript est compliqué, mais je vais vous donner le meilleur aperçu que je peux ici. Le service de langage C # dans VS2008 SP1 est mon objectif de performances (pour une bonne raison). Ce n'est pas encore là, mais il est suffisamment rapide / précis pour que je puisse proposer en toute sécurité des suggestions après la saisie d'un seul caractère, sans attendre que ctrl + espace ou que l'utilisateur tape un
.
(point). Plus les gens [travaillant sur les services linguistiques] obtiennent d'informations sur ce sujet, meilleure sera l'expérience de l'utilisateur final si j'utilise leurs produits. Il y a un certain nombre de produits avec lesquels j'ai eu la malheureuse expérience de travailler qui ne prêtaient pas autant d'attention aux détails, et par conséquent, je me battais plus avec l'IDE que je ne codais.Dans mon service linguistique, il est présenté comme suit:
aa.bb.cc
, mais peut également contenir des appels de méthode comme dansaa.bb(3+2).cc
.IDeclarationProvider
, où vous pouvez appelerGetDeclarations()
pour obtenir unIEnumerable<IDeclaration>
de tous les éléments visibles dans la portée. Dans mon cas, cette liste contient les locals / paramètres (si dans une méthode), les membres (champs et méthodes, statiques uniquement sauf dans une méthode d'instance, et aucun membre privé des types de base), globals (types et constantes pour la langue I je travaille sur) et des mots-clés. Dans cette liste sera un élément avec le nomaa
. Comme première étape dans l'évaluation de l'expression dans # 1, nous sélectionnons l'élément de l'énumération de contexte avec le nomaa
, nous donnant unIDeclaration
pour l'étape suivante.IDeclaration
représentantaa
pour en obtenir un autreIEnumerable<IDeclaration>
contenant les "membres" (dans un certain sens) deaa
. Puisque l'.
opérateur est différent de l'->
opérateur, j'appelledeclaration.GetMembers(".")
et j'attends que l'IDeclaration
objet applique correctement l'opérateur répertorié.cc
, où la liste de déclaration peut ou non contenir un objet avec le nomcc
. Comme je suis sûr que vous le savez, si plusieurs éléments commencent parcc
, ils devraient également apparaître. Je résous ce problème en prenant l'énumération finale et en la passant par mon algorithme documenté pour fournir à l'utilisateur les informations les plus utiles possible.Voici quelques remarques supplémentaires pour le backend IntelliSense:
GetMembers
. Chaque objet de mon cache est capable de fournir un foncteur qui évalue à ses membres, donc effectuer des actions compliquées avec l'arborescence est presque trivial.List<IDeclaration>
de ses membres, je garde aList<Name>
, oùName
est une structure contenant le hachage d'une chaîne spécialement formatée décrivant le membre. Il y a un énorme cache qui mappe les noms aux objets. De cette façon, lorsque je ré-analyse un fichier, je peux supprimer tous les éléments déclarés dans le fichier du cache et le remplir à nouveau avec les membres mis à jour. En raison de la façon dont les foncteurs sont configurés, toutes les expressions sont immédiatement évaluées par rapport aux nouveaux éléments."Interface" IntelliSense
Au fur et à mesure que l'utilisateur tape, le fichier est syntaxiquement incorrect plus souvent qu'il n'est correct. En tant que tel, je ne veux pas supprimer au hasard des sections du cache lorsque l'utilisateur tape. J'ai mis en place un grand nombre de règles de cas spéciaux pour gérer les mises à jour incrémentielles le plus rapidement possible. Le cache incrémentiel est uniquement conservé localement dans un fichier ouvert et permet de s'assurer que l'utilisateur ne se rend pas compte que sa saisie amène le cache principal à contenir des informations de ligne / colonne incorrectes pour des éléments comme chaque méthode du fichier.
Extrait de code de la section précédente:
J'ai pensé que j'ajouterais une liste des fonctionnalités IntelliSense que j'ai implémentées avec cette mise en page. Des photos de chacun se trouvent ici.
la source
Je ne peux pas dire exactement quels algorithmes sont utilisés par une implémentation particulière, mais je peux faire des suppositions éclairées. Un trie est une structure de données très utile pour ce problème: l'EDI peut conserver un grand trie en mémoire de tous les symboles de votre projet, avec quelques métadonnées supplémentaires à chaque nœud.
Lorsque vous tapez un caractère, il emprunte un chemin dans le trie. Tous les descendants d'un nœud de trie particulier sont des complétions possibles. L'EDI a alors juste besoin de filtrer ceux qui ont du sens dans le contexte actuel, mais il a seulement besoin d'en calculer autant que possible dans la fenêtre contextuelle de complétion des onglets.
La complétion par tabulation plus avancée nécessite un tri plus compliqué. Par exemple, Visual Assist X a une fonction dans laquelle vous n'avez qu'à taper les lettres majuscules des symboles CamelCase - par exemple, si vous tapez SFN, il vous montre le symbole
SomeFunctionName
dans sa fenêtre de complétion par tabulation.Le calcul du trie (ou d'autres structures de données) nécessite l'analyse de tout votre code pour obtenir une liste de tous les symboles de votre projet. Visual Studio le stocke dans sa base de données IntelliSense, un
.ncb
fichier stocké à côté de votre projet, afin qu'il n'ait pas à tout analyser à chaque fois que vous fermez et rouvrez votre projet. La première fois que vous ouvrez un grand projet (par exemple, celui que vous venez de synchroniser avec le contrôle de code source), VS prendra le temps de tout analyser et de générer la base de données.Je ne sais pas comment il gère les changements incrémentiels. Comme vous l'avez dit, lorsque vous écrivez du code, sa syntaxe est invalide 90% du temps, et tout réanalyser chaque fois que vous êtes inactif imposerait une énorme taxe sur votre processeur pour très peu d'avantages, surtout si vous modifiez un fichier d'en-tête inclus par un grand nombre de fichiers source.
Je soupçonne que soit (a) il ne répète que chaque fois que vous construisez votre projet (ou peut-être lorsque vous le fermez / ouvrez), ou (b) il effectue une sorte d'analyse locale où il analyse uniquement le code là où vous venez de édité de manière limitée, juste pour obtenir les noms des symboles pertinents. Étant donné que C ++ a une grammaire extrêmement compliquée, il peut se comporter de manière étrange dans les coins sombres si vous utilisez une métaprogrammation de modèles lourds, etc.
la source
Le lien suivant vous aidera davantage.
Mise en évidence de la syntaxe: zone de texte colorée rapide pour la mise en évidence de la syntaxe
la source