Frage

Viele Editoren und IDEs haben Code-Vervollständigung. Einige von ihnen sind sehr „intelligent“ andere sind nicht wirklich. Ich bin in der intelligentere Art interessiert. Zum Beispiel habe ich IDEs gesehen, das nur eine Funktion anbieten, wenn sie a) in dem aktuellen Bereich b) der Rückgabewert ist gültig ist. (Zum Beispiel nach „5 + foo [Tab]“ es nur bietet Funktionen, die etwas zurückgeben, die auf eine ganze Zahl oder Variablennamen des richtigen Typs hinzugefügt werden können.) Ich habe auch, dass sie Platz gesehen vor der immer häufiger verwendet oder längste Option von der Liste.

Ich weiß, Sie müssen den Code analysieren. Aber in der Regel während der Bearbeitung den aktuellen Code ist ungültig sind Syntaxfehler drin. Wie analysieren Sie etwas, wenn sie unvollständig ist und enthält Fehler?

Es gibt auch eine Zeitbeschränkung. Die Fertigstellung ist nutzlos, wenn es Sekunden dauert, eine Liste zu kommen. Manchmal sind die Fertigstellung Algorithmus beschäftigt sich mit Tausenden von Klassen.

Was die guten Algorithmen und Datenstrukturen hierfür sind?

War es hilfreich?

Lösung

Der IntelliSense-Engine in meinem Unreal Sprache Serviceprodukt ist kompliziert, aber ich werde geben, wie am besten hier einen Überblick, wie ich kann. Die Sprache C # Dienst in VS2008 SP1 ist mein Leistungsziel (aus gutem Grund). Es ist noch nicht da, aber es ist schnell / präzise genug, dass ich sicher Vorschläge nach einem einzelnen Zeichen eingegeben wird anbieten können, ohne Abfrage zu warten + Raum oder den Benutzer der Eingabe ein . (dot). Je mehr Informationen Menschen [auf Sprachdienstleistungen arbeiten] erhalten zu diesem Thema, die besseren Endanwender-Erfahrung ich soll ich jemals ihre Produkte verwenden. Es gibt eine Reihe von Produkten ich damit hatte die unglückliche Erfahrung in der Arbeit nicht zahlen so viel Liebe zum Detail und als Folge ich mit den IDE mehr kämpft als ich Codierung.

In meiner Sprache Service, ist es angelegt wie folgt aus:

  1. Finden Sie den Ausdruck an der Cursorposition. Dies geht aus dem Anfang des Mitglied Zugriff Ausdrucks bis zum Ende des Identifiers der Cursor über sind. Der Member-Access-Ausdruck ist in der Regel in Form aa.bb.cc, sondern auch Methodenaufrufe wie in aa.bb(3+2).cc enthalten.
  2. Holen Sie sich den Kontext , um den Cursor umgibt. Das ist sehr schwierig, weil es nicht immer den gleichen Regeln wie der Compiler (lange Geschichte) folgen, aber für hier annehmen, dass es tut. Im Allgemeinen bedeutet dies, die im Cache gespeicherten Informationen über die Methode / Klasse erhalten Sie den Cursor innerhalb.
  3. Sprechen Sie das Kontext-Objekt implementiert IDeclarationProvider, wo Sie GetDeclarations() nennen kann eine IEnumerable<IDeclaration> aller Elemente sichtbar in den Rahmen zu bekommen. In meinem Fall enthält diese Liste die Einheimischen / Parameter (wenn in einem Verfahren), Elemente (Felder und Methoden, statisch nur, es sei denn in einer Instanzmethode und keine private Mitglieder von Basistypen), Globals (Typen und Konstanten für die Sprache I ‚arbeite an) und Schlüsselwörter. In dieser Liste wird ein Element mit dem Namen aa sein. Als ein erster Schritt in die Expression in # Auswertung 1, wählen wir das Element aus dem Kontext Aufzählung mit dem Namen aa, geben Sie uns eine IDeclaration für den nächsten Schritt.
  4. Als nächstes wende ich den Bediener auf die IDeclaration aa Vertretung eines anderen IEnumerable<IDeclaration> bekommen die „Mitglieder“ (in gewissem Sinne) von aa enthält. Da der . Operator unterscheidet sich von der -> Operator ist, nenne ich declaration.GetMembers(".") und erwarten, dass die IDeclaration Objekt korrekt aufgelistet Betreiber gelten.
  5. Dies setzt sich fort, bis ich cc getroffen, in dem die Erklärung Liste kann oder nicht enthalten ein Objekt mit dem Namen cc. Da ich sicher bin, sind Sie sich bewusst, wenn mehrere Artikel mit cc beginnen, sollten sie auch erscheinen. Ich löse dies durch die endgültige Aufzählung zu nehmen und es durch meinen dokumentiert Algorithmus vorbei bietet den Anwender mit den meisten hilfreichen Informationen möglich.

Hier sind einige zusätzliche Hinweise für die IntelliSense-Backend:

  • Ich mache umfangreichen Gebrauch von lazy evaluation Mechanismen LINQ GetMembers bei der Umsetzung. Jedes Objekt in meinem Cache ist in der Lage einen Funktor zu schaffen, die ihre Mitglieder bewertet, so die Durchführung komplizierte Aktionen mit dem Baum in der Nähe trivial ist.
  • Anstatt jedes Objekt eine List<IDeclaration> seiner Mitglieder zu halten, habe ich eine List<Name> halten, wo Name ist eine Struktur, die Hash einer speziell formatierten Zeichenfolge zur Beschreibung des Elements enthält. Es gibt einen riesigen Cache, Namen zu Objekten zuordnet. Auf diese Weise, wenn ich wieder analysieren, eine Datei, kann ich alle Elemente in der Datei aus dem Cache erklärt entfernen und mit den aktualisierten Mitgliedern neu zu besiedeln. Aufgrund der Art und Weise des functors konfiguriert ist, sofort alle Ausdrücke auf die neuen Artikel bewerten.

IntelliSense "Frontend"

Wie die Benutzertypen, die Datei syntaktisch falsch häufiger als es korrekt ist. Als solche will ich nicht aufs Geratewohl Abschnitte des Cache entfernen, wenn der Benutzer. Ich habe eine große Anzahl von speziellen Fall geltenden Vorschriften zu so schnell wie möglich inkrementelle Updates zu behandeln. Der inkrementelle Cache wird nur lokal auf eine geöffnete Datei gehalten und hilft nicht den Benutzer machen sicherzustellen, nicht erkennen, dass ihre Eingabe der Back-End-Cache verursacht halten falsche Zeile / Spalte Informationen für Dinge wie jede Methode in der Datei.

  • Ein erlös Faktor ist mein Parser ist schnell . Es kann eine vollständige Cache-Aktualisierung einer 20000 Linie Quelldatei in 150ms handhaben, während sich geschlossene auf einem niedrigen Priorität Hintergrundthread arbeitet. Jedes Mal, wenn diese Parser einen Pass auf einer geöffnete Datei erfolgreich abgeschlossen wird (syntaktisch), der aktuelle Status der Datei in den globalen Cache bewegt wird.
  • Wenn die Datei nicht syntaktisch korrekt ist, verwende ich eine ANTLR Filter-Parser (sorry über den Link - die meisten Informationen sind auf der Mailingliste oder gesammelt aus der Quelle zu lesen) die Datei zu Analysepunkten gesucht:
    • Variable / Feld-Deklarationen.
    • Die Signatur für Klasse / Struktur-Definitionen.
    • Die Signatur für Methodendefinitionen.
  • Im lokalen Cache, Klasse / Struktur / Methodendefinitionen beginnen bei der Unterzeichnung und Ende, wenn die Klammer Verschachtelungsebene sogar zurückgeht. Methoden können auch enden, wenn eine andere Methode Deklaration (keine Verschachtelung Methoden) erreicht ist.
  • Im lokalen Cache, Variablen / Felder werden zu dem unmittelbar vorhergehenden verknüpft unclosed -Element. Sehen Sie sich den kurzen Codeausschnitt unten für ein Beispiel dafür, warum dies wichtig ist.
  • Auch während der Benutzer tippt, halte ich eine Remap-Tabelle, die hinzugefügt / entfernt Zeichenbereiche markiert. Dies wird verwendet für:
    • Sicherstellen, dass ich den richtigen Kontext des Cursors identifizieren, da ein Verfahren kann / wird in der Datei zwischen Voll Parsen bewegen.
    • Sicherstellen, dass auf die Erklärung Go / Definition / ortet Referenz Artikel korrekt in geöffneten Dateien.

Code-Snippet für den vorherigen Abschnitt:

class A
{
    int x; // linked to A

    void foo() // linked to A
    {
        int local; // linked to foo()

    // foo() ends here because bar() is starting
    void bar() // linked to A
    {
        int local2; // linked to bar()
    }

    int y; // linked again to A

Ich dachte, ich eine Liste der IntelliSense-Features hinzufügen würde ich mit diesem Layout implementiert haben. Bilder von jedem befinden sich hier.

  • Auto-complete
  • Tooltips
  • Method Tipps
  • Klassenansicht
  • Code-Definition-Fenster
  • Anruf Browser (VS 2010 schließlich fügt diese zu C #)
  • semantisch korrekt Alle Referenzen finden

Andere Tipps

Ich kann nicht genau sagen, was Algorithmen, die von einer bestimmten Implementierung verwendet werden, aber ich kann einige Vermutungen machen. Eine trie ist eine sehr nützliche Datenstruktur für dieses Problem: die IDE kann eine große trie im Speicher halten der alle Symbole in Ihrem Projekt, mit einigen zusätzlichen Metadaten an jedem Knoten.

Wenn Sie ein Zeichen eingeben, geht es einen Pfad in der Trie nach unten. Alle von den Nachkommen eines bestimmten Trie-Knoten sind mögliche Ergänzungen. Die IDE braucht dann nur die durch die diejenigen herauszufiltern, die Sinn im aktuellen Kontext zu machen, aber es muss nur so viele berechnen, wie in der Tab-Vervollständigung Popup-Fenster angezeigt werden.

Weitere erweiterte Tab-Vervollständigung erfordert eine kompliziertere trie. Zum Beispiel Visuelle X Assist verfügt über eine Funktion, bei der Sie brauchen nur die Großbuchstaben von Camelcase Symbole eingeben - zB wenn Sie, SFN geben, es zeigt Ihnen das Symbol SomeFunctionName in seiner Tab-Vervollständigung Fenster.

Die Berechnung der Trie (oder andere Datenstrukturen) erfordern alle Ihre Codes Parsen eine Liste aller der Symbole in Ihrem Projekt zu erhalten. Visual Studio speichert diese in seiner IntelliSense-Datenbank, eine Datei .ncb neben Ihrem Projekt gespeichert, so dass es nicht alles zu Analysepunkten jedes Mal, wenn Sie in der Nähe ist und der das Projekt wieder öffnen. Das erste Mal, öffnen Sie ein großes Projekt (sagen wir, Sie gerade Form Quellensteuerung synchronisiert), VS die Zeit nehmen, alles zu analysieren und die Datenbank zu erzeugen.

Ich weiß nicht, wie es inkrementelle Änderungen behandelt. Wie Sie sagten, wenn Sie Code schreiben, dann ist es ungültige Syntax 90% der Zeit, und neuparsen alles, wenn Sie eine große Steuer auf Ihrer CPU für sehr wenig Nutzen idled setzen würde, vor allem, wenn Sie eine Header-Datei enthalten sind Modifizieren von eine große Anzahl von Quelldateien.

Ich vermute, dass es entweder (a) nur reparses, wenn Sie Ihr Projekt tatsächlich bauen (oder möglicherweise, wenn Sie schließen / öffnen) oder (b) es tut eine Art von lokalem Parsing, wo sie analysiert nur den Code um, wo Sie habe gerade in einer begrenzten Art und Weise bearbeitet, nur die Namen der entsprechenden Symbole zu erhalten. Da C ++ so eine außerordentlich komplizierte Grammatik hat, kann es seltsam in den dunklen Ecken verhalten, wenn Sie schwere Metaprogrammierung und dergleichen verwenden.

Unter dem folgenden Link finden Sie weitere Hilfe ..

Syntax-Hervorhebungen: Schnell Farbige TextBox für Syntax-Hervorhebungen

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top