Gå til innholdet

Kodeintelligens

Control Center gir en agent fire måter å forstå kode på utover å lese den. De svarer på genuint forskjellige spørsmål, og grunnen til at det er fire og ikke én, er at ingen enkelt mekanisme svarer på alle.

Mekanisme Svarer på Koster
Kodegraf Hva som finnes her, og hva som avhenger av det En indekseringsrunde; billig å spørre mot
Språkserver Kompilerer dette faktisk, og hva er denne typen Én serverprosess per prosjekt
Strukturelt søk Hvor dukker denne FORMEN opp Én parsing per fil
Feilsøker Hva som var sant i dette øyeblikket, i denne rammen Én stoppet prosess

Kodegrafen bygges fra tree-sitter: symboler, kallkanter, en påvirkningsradius. Den er rask, den er billig, og den vet ingenting om typer. Den kan ikke fortelle deg om kallet du nettopp skrev kompilerer, om det feltet finnes på objektet, eller om omdøpingen din mistet en re-eksport.

Uten en språkserver oppdager en agent det først ved testtid — eller aldri. Den skriver noe som virker rimelig, går videre, og feilen dukker opp tre runder senere, festet til en annen endring.

Så gevinsten er egentlig ikke lsp-verktøyet. Det er at write, edit og apply_patch er pakket inn: etter hver skriving går filen gjennom språkserveren, og nyinnført diagnostikk flettes inn i verktøyresultatet. Agenten får vite at den ødela bygget i det øyeblikket den ødela det, uansett om den kom på å spørre.

Å gjenta det agenten allerede har fått vite, er verre enn å si ingenting: det fyller resultatet med støy som agenten lærer seg å skumme forbi, og den ene nye feilen forsvinner sammen med den. En journal per kjøring holder rede på hva som er rapportert, nøklet på alvorlighetsgrad, kode og melding — ikke posisjon, slik at en diagnostisk melding som bare har flyttet seg fire linjer ned, ikke rapporteres på nytt som ny.

Ingen server starter ved oppstart. Én starter ved den første forespørselen som trenger den, noe som nesten alltid er den første redigeringen av en fil den gjør krav på, og ledige servere ryddes bort. En vert som aldri rører Dart, starter aldri en analysator.

Gjenkjenning er et snitt: en rotmarkør må finnes i utsjekkingen og binærfila må kunne løses opp. En markør alene sier «dette prosjektet er Python» og ingenting om hvorvidt serveren er installert; en oppløsbar binærfil alene sier at verten har en verktøykjede, og ingenting om denne utsjekkingen. Bare begge sammen betyr at en spørring kan lykkes, og et verktøy som tilbys og alltid feiler, er verre enn ett som ikke finnes.

grep matcher tegn. Det er det rette verktøyet for et navn og det gale for en form.

Et strukturelt mønster parses av samme grammatikk som filen, så dispose($X) finner kallet uansett hvordan det er delt opp med mellomrom, pakket inn eller brutt over linjer, og matcher aldri de samme tegnene inne i en strengliteral eller en kommentar.

Egenskapen som gjør det mer enn et regex med ekstra trinn, er at en metavariabel som gjentas i ett mønster, må fange den samme teksten:

if ($X != null) $X.dispose()

matcher en vakt som frigjør det den testet, og matcher ikke en som frigjør noe annet. Det er en feilklasse du ikke kan uttrykke tekstlig i det hele tatt.

Den kjører på tree-sitteren som allerede er lenket inn

Seksjon med tittelen «Den kjører på tree-sitteren som allerede er lenket inn»

Det finnes ingen motor nummer to. cc_natives laster allerede tree-sitter og grammatikkene for kodegrafen, med en hurtigbuffer for kompilerte spørringer og en språktabell. Matcheren er ren Dart over trær som ble parset uansett. En annen strukturell motor ville bety en annen parser, et annet grammatikksett som må bygges og klargjøres på hver plattform, og to svar på «hva er et kalluttrykk».

En grammatikk definerer bare hva en hel fil er. Hvis dispose(x) parses bart på Dart sitt toppnivå, er det en fullt gyldig funksjonssignatur — en deklarasjon med navnet dispose som tar en parameter x — uten noen feil noe sted som røper det. En matcher bygget på den parsingen leter etter deklarasjoner, finner ingen kall og rapporterer «ingen treff» for et mønster som aldri ble lest slik det ser ut.

Så et mønster prøves i en stige av stillaser — setning, uttrykk, klassemedlem, toppnivå — og det første som godtar det, vinner. Et fragment som ingen kontekst godtar, rapporteres som uparsbart i stedet for å bli søkt etter og stille ikke funnet.

Hvorfor en omskriving mellomlagres i stedet for å skrives

Seksjon med tittelen «Hvorfor en omskriving mellomlagres i stedet for å skrives»

En strukturell omskriving er den videste redigeringen en agent kan gjøre: ett mønster, førti filer, og ingen leser resultatet linje for linje — ikke modellen, og ikke personen som ba om det. Å skrive den og rapportere «ferdig» betyr at det første ærlige blikket på endringen er en git diff i etterkant.

Så ast_edit rapporterer hva den ville gjort — antallet, filene, de matchede stedene — og endringen lander først når resolve sender den inn. Det er også det som lar en diff og en Godta/Forkast ligge foran et menneske før noe som helst når disken.

Innsendingen er alt eller ingenting, og hver fil sjekkes mot innholdet som ble fanget opp ved mellomlagringen. Mellom mellomlagring og innsending kan agenten ha redigert en av filene for hånd, en diagnostikkrunde kan ha skrevet den om, eller en formatter kan ha rørt den — å sende inn likevel ville kaste bort det arbeidet, og diffen ville se tilsiktet ut. Ethvert avvik nekter hele endringen, for en delvis anvendt strukturell omskriving etterlater et tre som ikke kompilerer i noen av formene.

Hva en feilsøker svarer på som en print-setning ikke kan

Seksjon med tittelen «Hva en feilsøker svarer på som en print-setning ikke kan»

Løkka med print-setninger er: legg til en print, kjør hele greia på nytt, les utskriften, fjern printen. Den koster en full testkjøring per spørsmål, og den svarer bare på spørsmålet du kom på å stille før du kjørte.

En stoppet ramme svarer på alle spørsmål om det øyeblikket på én gang — hver lokal variabel, hver kallende ramme, og et uttrykk evaluert i rammens eget omfang. For en prosess som har låst seg, eller en feil som reproduseres én gang av ti kjøringer, er det forskjellen mellom en hel ettermiddag og ett minutt.

En feilsøkingsadapter eier en stoppet prosess som holder på hva nå enn den prosessen holder — en port, en lås, en databasetilkobling. En økt som ingen styrer, er en lekkasje som overlever runden som startet den. Derfor finnes det en hard TTL, nøyaktig én økt per samtale, og en andre launch nektes i stedet for å i det stille erstatte den første: en agent som starter en andre uten å avslutte den første, har nesten helt sikkert mistet oversikten over den første.

Debug Adapter Protocol deler Language Server Protocol sin Content-Length-innramming og ingenting over den — forskjellige korrelasjonsnøkler, en annen feilform, og en tredje meldingstype (hendelser) uten noen LSP-motpart. Innrammingen bor på ett sted; protokollene ligger på toppen av den, hver for seg.

Å skrive innrammingen to ganger ville bety to svar på «en melding strekker seg over to biter», «en bit inneholder tre meldinger» og «serveren skrev et banner til stdout før sin første ramme», og bare ett av dem ville få rettelsen.