Viele Editoren und IDEs haben Code-Vervollständigung. Einige von ihnen sind sehr "intelligent", andere nicht wirklich. Ich interessiere mich für den intelligenteren Typ. Ich habe zum Beispiel IDEs gesehen, die eine Funktion nur anbieten, wenn sie a) im aktuellen Bereich verfügbar ist, b) ihren Rückgabewert gültig ist. (Zum Beispiel bietet es nach "5 + foo [tab]" nur Funktionen, die etwas zurückgeben, das zu einer Ganzzahl oder zu Variablennamen des richtigen Typs hinzugefügt werden kann.) Ich habe auch gesehen, dass sie die am häufigsten verwendete oder längste Option vor sich haben der Liste.
Mir ist klar, dass Sie den Code analysieren müssen. Während der Bearbeitung des aktuellen Codes ist jedoch normalerweise eine Syntax fehlerhaft. Wie analysiert man etwas, wenn es unvollständig ist und Fehler enthält?
Es gibt auch eine zeitliche Beschränkung. Die Fertigstellung ist nutzlos, wenn es Sekunden dauert, eine Liste zu erstellen. Manchmal behandelt der Vervollständigungsalgorithmus Tausende von Klassen.
Was sind die guten Algorithmen und Datenstrukturen dafür?
quelle
Antworten:
Die IntelliSense-Engine in meinem UnrealScript-Sprachdienstprodukt ist kompliziert, aber ich werde hier so gut wie möglich einen Überblick geben. Der C # -Sprachendienst in VS2008 SP1 ist mein Leistungsziel (aus gutem Grund). Es ist noch nicht da, aber es ist schnell / genau genug, dass ich sicher Vorschläge machen kann, nachdem ein einzelnes Zeichen eingegeben wurde, ohne auf Strg + Leerzeichen zu warten oder der Benutzer einen
.
(Punkt) einzugeben . Je mehr Informationen Menschen [die an Sprachdiensten arbeiten] zu diesem Thema erhalten, desto besser ist die Erfahrung für Endbenutzer, sollte ich jemals ihre Produkte verwenden. Es gibt eine Reihe von Produkten, mit denen ich die unglückliche Erfahrung gemacht habe, dass ich nicht so genau auf Details geachtet habe. Infolgedessen habe ich mehr mit der IDE gekämpft als mit dem Codieren.In meinem Sprachendienst ist es wie folgt aufgebaut:
aa.bb.cc
, kann jedoch auch Methodenaufrufe wie in enthaltenaa.bb(3+2).cc
.IDeclarationProvider
, wo Sie aufrufen könnenGetDeclarations()
, um einesIEnumerable<IDeclaration>
der im Bereich sichtbaren Elemente anzuzeigen. In meinem Fall enthält diese Liste die lokalen / Parameter (wenn in einer Methode), Mitglieder (Felder und Methoden, nur statisch, außer in einer Instanzmethode, und keine privaten Mitglieder von Basistypen), Globale (Typen und Konstanten für die Sprache I. arbeite an) und Schlüsselwörtern. In dieser Liste befindet sich ein Element mit dem Namenaa
. Als ersten Schritt bei der Bewertung des Ausdrucks in # 1 wählen wir das Element aus der Kontextaufzählung mit dem Namen ausaa
und geben uns eineIDeclaration
für den nächsten Schritt.IDeclaration
Darstellungaa
an, um eine andere zu erhalten,IEnumerable<IDeclaration>
die die "Mitglieder" (in gewissem Sinne) von enthältaa
. Da sich der.
Operator vom->
Operator unterscheidet, rufe ich aufdeclaration.GetMembers(".")
und erwarte, dass dasIDeclaration
Objekt den aufgelisteten Operator korrekt anwendet.cc
, wo die Deklarationsliste ein Objekt mit dem Namen enthalten kann oder nichtcc
. Wie Sie sicher wissen, sollten mehrere Elemente, die mit beginnencc
, ebenfalls angezeigt werden. Ich löse dieses Problem, indem ich die endgültige Aufzählung durch meinen dokumentierten Algorithmus leite , um dem Benutzer die hilfreichsten Informationen zu liefern.Hier einige zusätzliche Hinweise für das IntelliSense-Backend:
GetMembers
. Jedes Objekt in meinem Cache kann einen Funktor bereitstellen, der für seine Mitglieder ausgewertet wird. Daher ist das Ausführen komplizierter Aktionen mit dem Baum nahezu trivial.List<IDeclaration>
seiner Mitglieder behält , behalte ich einList<Name>
, wobeiName
eine Struktur den Hash einer speziell formatierten Zeichenfolge enthält, die das Mitglied beschreibt. Es gibt einen riesigen Cache, der Objekten Namen zuordnet. Auf diese Weise kann ich beim erneuten Analysieren einer Datei alle in der Datei deklarierten Elemente aus dem Cache entfernen und mit den aktualisierten Mitgliedern neu füllen. Aufgrund der Konfiguration der Funktoren werden alle Ausdrücke sofort auf die neuen Elemente ausgewertet.IntelliSense "Frontend"
Während der Benutzertyp ist die Datei häufiger syntaktisch falsch als korrekt. Daher möchte ich nicht willkürlich Teile des Caches entfernen, wenn der Benutzer tippt. Ich habe eine große Anzahl von Sonderfallregeln eingerichtet, um inkrementelle Aktualisierungen so schnell wie möglich durchzuführen. Der inkrementelle Cache wird nur lokal in einer geöffneten Datei gespeichert und stellt sicher, dass der Benutzer nicht merkt, dass der Backend-Cache durch seine Eingabe falsche Zeilen- / Spalteninformationen für Dinge wie die einzelnen Methoden in der Datei enthält.
Code-Snippet für den vorherigen Abschnitt:
Ich dachte, ich würde eine Liste der IntelliSense-Funktionen hinzufügen, die ich mit diesem Layout implementiert habe. Bilder von jedem finden Sie hier.
quelle
Ich kann nicht genau sagen, welche Algorithmen von einer bestimmten Implementierung verwendet werden, aber ich kann einige fundierte Vermutungen anstellen. Ein Trie ist eine sehr nützliche Datenstruktur für dieses Problem: Die IDE kann einen großen Trie im Speicher aller Symbole in Ihrem Projekt mit einigen zusätzlichen Metadaten an jedem Knoten verwalten.
Wenn Sie ein Zeichen eingeben, geht es einen Pfad in der Trie entlang. Alle Nachkommen eines bestimmten Trie-Knotens sind mögliche Vervollständigungen. Die IDE muss dann nur diejenigen herausfiltern, die im aktuellen Kontext sinnvoll sind, aber sie muss nur so viele berechnen, wie im Popup-Fenster zum Ausfüllen der Registerkarten angezeigt werden können.
Eine erweiterte Tab-Vervollständigung erfordert einen komplizierteren Versuch. Beispielsweise verfügt Visual Assist X über eine Funktion, mit der Sie nur die Großbuchstaben von CamelCase-Symbolen eingeben müssen. Wenn Sie beispielsweise SFN eingeben, wird das Symbol
SomeFunctionName
in seinem Fenster zum Ausfüllen der Registerkarten angezeigt.Für die Berechnung des Versuchs (oder anderer Datenstrukturen) muss der gesamte Code analysiert werden, um eine Liste aller Symbole in Ihrem Projekt zu erhalten. Visual Studio speichert dies in seiner IntelliSense-Datenbank, einer
.ncb
Datei, die neben Ihrem Projekt gespeichert ist, sodass nicht jedes Mal, wenn Sie Ihr Projekt schließen und erneut öffnen, alles neu analysiert werden muss. Wenn Sie zum ersten Mal ein großes Projekt öffnen (z. B. eines, das Sie gerade mit der Quellcodeverwaltung synchronisiert haben), nimmt sich VS die Zeit, um alles zu analysieren und die Datenbank zu generieren.Ich weiß nicht, wie es mit inkrementellen Änderungen umgeht. Wie Sie sagten, wenn Sie Code schreiben, ist die Syntax in 90% der Fälle ungültig. Wenn Sie alles im Leerlauf reparieren, wird Ihre CPU mit einem sehr geringen Nutzen belastet, insbesondere wenn Sie eine von enthaltene Header-Datei ändern eine große Anzahl von Quelldateien.
Ich vermute, dass es entweder (a) nur dann repariert wird, wenn Sie Ihr Projekt tatsächlich erstellen (oder möglicherweise, wenn Sie es schließen / öffnen), oder (b) es eine Art lokales Parsen durchführt, bei dem der Code nur dort analysiert wird, wo Sie gerade sind in begrenzter Weise bearbeitet, nur um die Namen der relevanten Symbole zu erhalten. Da C ++ eine so außergewöhnlich komplizierte Grammatik hat, kann es sich in den dunklen Ecken merkwürdig verhalten, wenn Sie eine starke Metaprogrammierung für Vorlagen und dergleichen verwenden.
quelle
Der folgende Link hilft Ihnen weiter ..
Syntaxhervorhebung: Schnelle farbige Textbox für die Syntaxhervorhebung
quelle