Domanda

Molti editor e IDE hanno il completamento del codice. Alcuni di loro sono molto & Quot; intelligenti & Quot; gli altri non lo sono davvero. Sono interessato al tipo più intelligente. Ad esempio, ho visto gli IDE che offrono una funzione solo se è a) disponibile nell'ambito corrente b) il suo valore di ritorno è valido. (Ad esempio dopo & Quot; 5 + pippo [tab] & Quot; offre solo funzioni che restituiscono qualcosa che può essere aggiunto a numeri interi o variabili del tipo corretto.) Ho anche visto che posizionano l'opzione più utilizzata o più lunga prima dell'elenco.

Mi rendo conto che devi analizzare il codice. Ma di solito durante la modifica del codice corrente non sono presenti errori di sintassi. Come analizzi qualcosa quando è incompleto e contiene errori?

Esiste anche un vincolo temporale. Il completamento è inutile se bastano pochi secondi per creare un elenco. A volte l'algoritmo di completamento si occupa di migliaia di classi.

Quali sono i buoni algoritmi e strutture dati per questo?

È stato utile?

Soluzione

Il motore IntelliSense nel mio prodotto di servizio linguistico UnrealScript è complicato, ma fornirò una panoramica il migliore possibile. Il servizio di linguaggio C # in VS2008 SP1 è il mio obiettivo prestazionale (per una buona ragione). Non è ancora arrivato, ma è abbastanza veloce / accurato da poter offrire suggerimenti in modo sicuro dopo aver digitato un singolo carattere, senza attendere ctrl + spazio o l'utente digitando un . (punto). Più informazioni le persone [che lavorano sui servizi linguistici] ottengono su questo argomento, migliore è l'esperienza dell'utente finale che dovrei mai usare i loro prodotti. Ci sono un certo numero di prodotti con cui ho avuto la sfortunata esperienza di lavorare che non ha prestato così tanta attenzione ai dettagli, e di conseguenza stavo combattendo con l'IDE più di quanto non stessi programmando.

Nel mio servizio linguistico, è strutturato come il seguente:

  1. Ottieni l'espressione sul cursore. Passa dall'inizio dell'espressione accesso membro alla fine dell'identificatore su cui si trova il cursore. L'espressione di accesso del membro è generalmente nella forma aa.bb.cc, ma può contenere anche chiamate di metodo come in aa.bb(3+2).cc.
  2. Ottieni il contesto che circonda il cursore. Questo è molto complicato, perché non segue sempre le stesse regole del compilatore (lunga storia), ma supponiamo che lo faccia. Generalmente questo significa ottenere le informazioni memorizzate nella cache sul metodo / classe in cui si trova il cursore.
  3. Pronuncia l'oggetto contestuale implementa IDeclarationProvider, dove puoi chiamare GetDeclarations() per ottenere un IEnumerable<IDeclaration> di tutti gli elementi visibili nell'ambito. Nel mio caso, questo elenco contiene i locali / parametri (se in un metodo), i membri (campi e metodi, statici solo se in un metodo di istanza e nessun membro privato dei tipi di base), i globali (tipi e costanti per la lingua I ci sto lavorando) e parole chiave. In questo elenco sarà presente un elemento con il nome aa. Come primo passo nella valutazione dell'espressione in # 1, selezioniamo l'elemento dall'enumerazione del contesto con il nome IDeclaration, dandoci un -> per il passaggio successivo.
  4. Successivamente, applico l'operatore al declaration.GetMembers(".") che rappresenta cc per ottenere un altro GetMembers contenente il " membri " (in un certo senso) di List<IDeclaration>. Poiché l'operatore List<Name> è diverso dall'operatore Name, chiamo <=> e mi aspetto che l'oggetto <=> applichi correttamente l'operatore elencato.
  5. Continua finché non premo <=>, dove l'elenco delle dichiarazioni può o meno contenere un oggetto con il nome <=>. Come sono sicuro che sei a conoscenza, se più elementi iniziano con <=>, dovrebbero apparire anche loro. Risolvo questo prendendo l'enumerazione finale e passando attraverso il mio algoritmo documentato a fornire all'utente le informazioni più utili possibili.

Ecco alcune note aggiuntive per il back-end IntelliSense:

  • Faccio ampio uso dei meccanismi di valutazione pigri di LINQ nell'implementazione <=>. Ogni oggetto nella mia cache è in grado di fornire un functor che valuta i suoi membri, quindi eseguire azioni complicate con l'albero è quasi banale.
  • Invece che ogni oggetto mantenga un <=> dei suoi membri, tengo un <=>, dove <=> è una struttura che contiene l'hash di una stringa appositamente formattata che descrive il membro. C'è un'enorme cache che associa i nomi agli oggetti. In questo modo, quando analizzo nuovamente un file, posso rimuovere dalla cache tutti gli elementi dichiarati nel file e ripopolarlo con i membri aggiornati. A causa del modo in cui i funzione sono configurati, tutte le espressioni valutano immediatamente i nuovi elementi.

IntelliSense "frontend"

Man mano che l'utente digita, il file è sinteticamente errato più spesso di quanto sia corretto. Come tale, non voglio rimuovere casualmente sezioni della cache quando l'utente digita. Ho un gran numero di regole per casi speciali in atto per gestire gli aggiornamenti incrementali il più rapidamente possibile. La cache incrementale viene mantenuta locale solo in modo apertoe aiuta a garantire che l'utente non si renda conto che la sua digitazione sta causando alla cache back-end di contenere informazioni errate su riga / colonna per cose come ogni metodo nel file.

  • Un fattore di riscatto è che il mio parser è veloce . Può gestire un aggiornamento completo della cache di un file sorgente di linea 20000 in 150 ms mentre opera autonomamente su un thread in background a bassa priorità. Ogni volta che questo parser completa un passaggio su un file aperto correttamente (sintatticamente), lo stato corrente del file viene spostato nella cache globale.
  • Se il file non è sintatticamente corretto, utilizzo un ANTLR filtro parser (mi dispiace per il link - la maggior parte delle informazioni sono sulla mailing list o raccolte dalla lettura della fonte) per analizzare il file cercando:
    • Dichiarazioni di variabili / campi.
    • La firma per le definizioni di classe / struttura.
    • La firma per le definizioni dei metodi.
  • Nella cache locale, le definizioni di classe / struttura / metodo iniziano alla firma e terminano quando il livello di annidamento del controvento torna a pari. I metodi possono anche terminare se viene raggiunta un'altra dichiarazione del metodo (nessun metodo di nidificazione).
  • Nella cache locale, le variabili / i campi sono collegati all'elemento non chiuso immediatamente precedente. Vedi il breve frammento di codice qui sotto per un esempio del perché questo è importante.
  • Inoltre, man mano che l'utente digita, tengo una tabella di rimappatura che segna gli intervalli di caratteri aggiunti / rimossi. Questo è usato per:
    • Assicurandomi di poter identificare il contesto corretto del cursore, poiché un metodo può / si sposta nel file tra analisi complete.
    • Assicurarsi che Vai a Dichiarazione / Definizione / Riferimento individua correttamente gli elementi nei file aperti.

Frammento di codice per la sezione precedente:

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

Ho pensato di aggiungere un elenco delle funzionalità di IntelliSense che ho implementato con questo layout. Le foto di ciascuna si trovano qui.

  • completamento automatico
  • Suggerimenti per gli strumenti
  • Suggerimenti sui metodi
  • Visualizzazione classe
  • Finestra Definizione codice
  • Call Browser (VS 2010 alla fine aggiunge questo a C #)
  • Trova tutti i riferimenti semanticamente corretti

Altri suggerimenti

Non posso dire esattamente quali algoritmi sono utilizzati da una particolare implementazione, ma posso fare alcune ipotesi istruite. Una trie è una struttura di dati molto utile per questo problema: l'IDE può mantenere un grande trie in memoria di tutti i simboli nel tuo progetto, con alcuni metadati extra su ciascun nodo.

Quando si digita un personaggio, questo percorre un percorso nel trie. Tutti i discendenti di un particolare nodo trie sono possibili completamenti. L'IDE deve quindi solo filtrare quelli che hanno senso nel contesto attuale, ma deve solo calcolare quanti ne possono essere visualizzati nella finestra pop-up di completamento della scheda.

Un completamento della scheda più avanzato richiede un trie più complicato. Ad esempio, Visual Assist X ha una funzione in base alla quale è necessario digitare solo le lettere maiuscole dei simboli CamelCase - ad es. , se digiti SFN, ti mostra il simbolo SomeFunctionName nella sua finestra di completamento delle schede.

Il calcolo del trie (o altre strutture di dati) richiede l'analisi di tutto il codice per ottenere un elenco di tutti i simboli nel progetto. Visual Studio lo archivia nel suo database IntelliSense, un file .ncb archiviato insieme al progetto, in modo che non debba ripetere tutto ogni volta che chiudi e riapri il progetto. La prima volta che apri un progetto di grandi dimensioni (ad esempio, uno che ha appena sincronizzato il controllo del codice sorgente), VS impiegherà il tempo per analizzare tutto e generare il database.

Non so come gestisca i cambiamenti incrementali. Come hai detto, quando scrivi il codice, è una sintassi non valida il 90% delle volte e ripetere tutto ogni volta che sei inattivo comporterebbe una tassa enorme sulla tua CPU per un beneficio molto piccolo, specialmente se stai modificando un file di intestazione incluso da un gran numero di file sorgente.

Sospetto che (a) ripeti solo ogni volta che realizzi il tuo progetto (o eventualmente quando lo chiudi / lo apri), oppure (b) esegue una sorta di analisi locale in cui analizza solo il codice attorno a te ho appena modificato in modo limitato, solo per ottenere i nomi dei simboli pertinenti. Dato che il C ++ ha una grammatica così straordinariamente complicata, potrebbe comportarsi in modo strano negli angoli bui se stai usando metaprogrammi di template pesanti e simili.

Il seguente link ti aiuterà ulteriormente ..

Evidenziazione della sintassi: TextBox a colori veloce per l'evidenziazione della sintassi

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top