การเติมโค้ดทำงานอย่างไร?


84

บรรณาธิการและ IDE จำนวนมากมีการเติมโค้ด บางคนมีความ "ฉลาด" มากคนอื่น ๆ ไม่ได้จริงๆ ฉันสนใจประเภทที่ฉลาดกว่า ตัวอย่างเช่นฉันเคยเห็น IDE ที่เสนอฟังก์ชันถ้าเป็น a) พร้อมใช้งานในขอบเขตปัจจุบัน b) ค่าที่ส่งคืนถูกต้อง (ตัวอย่างเช่นหลังจาก "5 + foo [tab]" จะมีเฉพาะฟังก์ชันที่ส่งคืนสิ่งที่สามารถเพิ่มลงในชื่อจำนวนเต็มหรือตัวแปรของประเภทที่ถูกต้องได้) ฉันยังเห็นว่าพวกเขาวางตัวเลือกที่ใช้บ่อยกว่าหรือยาวที่สุดไว้ข้างหน้า ของรายการ

ฉันรู้ว่าคุณต้องแยกวิเคราะห์รหัส แต่โดยปกติแล้วในขณะที่แก้ไขโค้ดปัจจุบันไม่ถูกต้องจะมีข้อผิดพลาดทางไวยากรณ์อยู่ คุณจะแยกวิเคราะห์บางสิ่งได้อย่างไรเมื่อมันไม่สมบูรณ์และมีข้อผิดพลาด

นอกจากนี้ยังมีข้อ จำกัด ด้านเวลา การทำให้เสร็จสิ้นจะไร้ประโยชน์หากใช้เวลาไม่กี่วินาทีในการสร้างรายการ บางครั้งอัลกอริทึมการทำให้สมบูรณ์จะเกี่ยวข้องกับคลาสหลายพันคลาส

อัลกอริทึมและโครงสร้างข้อมูลที่ดีสำหรับสิ่งนี้คืออะไร?


1
เป็นคำถามที่ดี คุณอาจต้องการที่จะดูที่โค้ดสำหรับบางส่วนของ IDEs โอเพ่นซอร์สที่ใช้นี้เช่นรหัส :: บล็อกที่ codeblocks.org

1
นี่คือบทความการสร้าง Code Completion ใน C # การสร้าง Code Completion ใน C #
Pritam Zope

คำตอบ:


65

กลไก IntelliSense ในผลิตภัณฑ์บริการภาษา UnrealScript ของฉันมีความซับซ้อน แต่ฉันจะให้ภาพรวมที่ดีที่สุดเท่าที่จะทำได้ บริการภาษา C # ใน VS2008 SP1 คือเป้าหมายด้านประสิทธิภาพของฉัน (ด้วยเหตุผลที่ดี) ยังไม่มี แต่มันเร็ว / แม่นยำพอที่จะให้คำแนะนำได้อย่างปลอดภัยหลังจากพิมพ์อักขระตัวเดียวโดยไม่ต้องรอเว้นวรรค ctrl + หรือผู้ใช้พิมพ์ a .(dot) ยิ่งผู้คน [ทำงานเกี่ยวกับบริการภาษา] ได้รับข้อมูลเกี่ยวกับเรื่องนี้มากเท่าไหร่ฉันก็จะได้รับประสบการณ์ของผู้ใช้ปลายทางที่ดีกว่าที่ฉันจะได้ใช้ผลิตภัณฑ์ของพวกเขา มีผลิตภัณฑ์จำนวนหนึ่งที่ฉันมีประสบการณ์ที่โชคร้ายในการทำงานที่ไม่ได้ใส่ใจในรายละเอียดมากนักและด้วยเหตุนี้ฉันจึงต่อสู้กับ IDE มากกว่าที่ฉันเขียนโค้ด

ในบริการภาษาของฉันมีการจัดวางดังต่อไปนี้:

  1. รับนิพจน์ที่เคอร์เซอร์ สิ่งนี้เดินจากจุดเริ่มต้นของนิพจน์การเข้าถึงของสมาชิกไปยังจุดสิ้นสุดของตัวระบุที่เคอร์เซอร์อยู่เหนือ โดยทั่วไปแล้วนิพจน์การเข้าถึงของสมาชิกจะอยู่ในรูปแบบaa.bb.ccแต่ยังสามารถมีการเรียกเมธอดได้เช่นaa.bb(3+2).ccกัน
  2. รับบริบทรอบเคอร์เซอร์ นี่เป็นเรื่องยุ่งยากมากเพราะมันไม่ได้เป็นไปตามกฎเดียวกันกับคอมไพเลอร์เสมอไป (เรื่องยาว) แต่สำหรับที่นี่ถือว่าเป็นเช่นนั้น โดยทั่วไปหมายถึงรับข้อมูลแคชเกี่ยวกับวิธีการ / คลาสที่เคอร์เซอร์อยู่ภายใน
  3. พูดว่าการใช้อ็อบเจ็กต์บริบทIDeclarationProviderซึ่งคุณสามารถเรียกGetDeclarations()เพื่อรับIEnumerable<IDeclaration>รายการทั้งหมดที่มองเห็นได้ในขอบเขต ในกรณีของฉันรายการนี้มีภาษาท้องถิ่น / พารามิเตอร์ (ถ้าเป็นวิธีการ) สมาชิก (เขตข้อมูลและวิธีการแบบคงที่เว้นแต่ในวิธีการอินสแตนซ์และไม่มีสมาชิกส่วนตัวของประเภทฐาน) โลก (ประเภทและค่าคงที่สำหรับภาษา I กำลังดำเนินการ) และคำหลัก aaในรายการนี้จะเป็นรายการที่มีชื่อที่ ในขั้นตอนแรกในการประเมินนิพจน์ใน # 1 เราจะเลือกรายการจากการแจงนับบริบทพร้อมชื่อaaเพื่อให้เรามีIDeclarationขั้นตอนต่อไป
  4. ต่อไปผมจะนำไปใช้ประกอบการที่จะIDeclarationเป็นตัวแทนaaที่จะได้รับอีกIEnumerable<IDeclaration>มี "สมาชิก" (ในความรู้สึกบาง) aaของ เนื่องจากตัว.ดำเนินการแตกต่างจากตัว->ดำเนินการฉันจึงเรียกใช้declaration.GetMembers(".")และคาดว่าIDeclarationวัตถุจะใช้ตัวดำเนินการที่ระบุไว้อย่างถูกต้อง
  5. นี้ต่อไปจนกว่าผมตีccที่รายการประกาศอาจหรือไม่อาจccมีวัตถุที่มีชื่อที่ อย่างที่ฉันแน่ใจว่าคุณทราบดีหากมีหลายรายการขึ้นต้นด้วยรายการccเหล่านั้นก็ควรปรากฏเช่นกัน ฉันแก้ปัญหานี้โดยการแจงนับขั้นสุดท้ายและส่งผ่านอัลกอริทึมเอกสารของฉันเพื่อให้ข้อมูลที่เป็นประโยชน์กับผู้ใช้มากที่สุดเท่าที่จะเป็นไปได้

หมายเหตุเพิ่มเติมบางส่วนสำหรับแบ็กเอนด์ IntelliSense มีดังนี้

  • ฉันใช้กลไกการประเมินที่ขี้เกียจของ LINQ ในการนำไปใช้GetMembersอย่างกว้างขวาง แต่ละออบเจ็กต์ในแคชของฉันสามารถจัดหา functor ที่ประเมินให้กับสมาชิกได้ดังนั้นการดำเนินการที่ซับซ้อนกับทรีจึงเป็นเรื่องเล็กน้อย
  • แทนที่จะเก็บแต่ละออบเจ็กต์ไว้เป็นList<IDeclaration>สมาชิกฉันเก็บ a ไว้List<Name>ซึ่งNameเป็นโครงสร้างที่มีแฮชของสตริงที่จัดรูปแบบพิเศษซึ่งอธิบายถึงสมาชิก มีแคชขนาดใหญ่ที่แมปชื่อกับวัตถุ ด้วยวิธีนี้เมื่อฉันแยกวิเคราะห์ไฟล์อีกครั้งฉันสามารถลบรายการทั้งหมดที่ประกาศในไฟล์ออกจากแคชและเติมข้อมูลใหม่ด้วยสมาชิกที่อัปเดต เนื่องจากวิธีการกำหนดค่า functors นิพจน์ทั้งหมดจะประเมินรายการใหม่ทันที

IntelliSense "ส่วนหน้า"

ตามประเภทของผู้ใช้ไฟล์จะผิดพลาดทางไวยากรณ์บ่อยกว่าที่ถูกต้อง ด้วยเหตุนี้ฉันจึงไม่ต้องการลบส่วนต่างๆของแคชโดยบังเอิญเมื่อผู้ใช้พิมพ์ ฉันมีกฎกรณีพิเศษจำนวนมากเพื่อจัดการการอัปเดตที่เพิ่มขึ้นโดยเร็วที่สุด แคชที่เพิ่มขึ้นจะถูกเก็บไว้ในไฟล์ที่เปิดอยู่เท่านั้นและช่วยให้แน่ใจว่าผู้ใช้ไม่ทราบว่าการพิมพ์ของพวกเขาทำให้แคชแบ็กเอนด์เก็บข้อมูลบรรทัด / คอลัมน์ที่ไม่ถูกต้องสำหรับสิ่งต่างๆเช่นแต่ละวิธีในไฟล์

  • ปัจจัยหนึ่งที่แลกเป็น parser ของฉันคืออย่างรวดเร็ว สามารถจัดการการอัปเดตแคชแบบเต็มของไฟล์ซอร์ส 20000 บรรทัดใน 150 มิลลิวินาทีในขณะที่ใช้งานในตัวบนเธรดพื้นหลังที่มีลำดับความสำคัญต่ำ เมื่อใดก็ตามที่โปรแกรมแยกวิเคราะห์นี้ส่งผ่านไฟล์ที่เปิดได้สำเร็จ (ในเชิงไวยากรณ์) สถานะปัจจุบันของไฟล์จะถูกย้ายไปยังแคชส่วนกลาง
  • หากไฟล์ไม่ถูกต้องตามหลักไวยากรณ์ฉันใช้ตัวแยกวิเคราะห์ตัวกรอง ANTLR (ขออภัยเกี่ยวกับลิงก์ - ข้อมูลส่วนใหญ่อยู่ในรายชื่ออีเมลหรือรวบรวมจากการอ่านแหล่งที่มา)เพื่อแยกวิเคราะห์ไฟล์ที่ค้นหา:
    • การประกาศตัวแปร / ฟิลด์
    • ลายเซ็นสำหรับนิยามคลาส / โครงสร้าง
    • ลายเซ็นสำหรับนิยามวิธีการ
  • ในโลคัลแคชนิยามคลาส / โครงสร้าง / วิธีการเริ่มต้นที่ลายเซ็นและสิ้นสุดเมื่อระดับการซ้อนรั้งกลับไปเป็นคู่ เมธอดยังสามารถสิ้นสุดได้หากมีการประกาศเมธอดอื่น (ไม่มีเมธอดซ้อน)
  • ในแคชท้องถิ่นตัวแปร / สาขาที่มีการเชื่อมโยงไปทันทีก่อนunclosedองค์ประกอบ ดูข้อมูลโค้ดโดยย่อด้านล่างสำหรับตัวอย่างสาเหตุที่สำคัญ
  • นอกจากนี้ในฐานะผู้ใช้ประเภทฉันเก็บตารางการรีแมปไว้เพื่อทำเครื่องหมายช่วงอักขระที่เพิ่ม / ลบ ใช้สำหรับ:
    • ตรวจสอบให้แน่ใจว่าฉันสามารถระบุบริบทที่ถูกต้องของเคอร์เซอร์ได้เนื่องจากเมธอดสามารถ / ไม่ย้ายในไฟล์ระหว่างการแยกวิเคราะห์แบบเต็ม
    • ตรวจสอบให้แน่ใจว่า Go To Declaration / Definition / Reference ระบุตำแหน่งรายการอย่างถูกต้องในไฟล์ที่เปิด

ข้อมูลโค้ดสำหรับส่วนก่อนหน้า:

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

ฉันคิดว่าฉันจะเพิ่มรายการคุณสมบัติ IntelliSense ที่ฉันใช้กับเค้าโครงนี้ รูปภาพของแต่ละภาพอยู่ที่นี่

  • เติมข้อความอัตโนมัติ
  • เคล็ดลับเครื่องมือ
  • เคล็ดลับวิธีการ
  • มุมมองคลาส
  • หน้าต่างนิยามรหัส
  • Call Browser (ในที่สุด VS 2010 ก็เพิ่มสิ่งนี้ใน C #)
  • แก้ไขความหมายค้นหาการอ้างอิงทั้งหมด

ขอบคุณมาก ฉันไม่เคยคิดถึงอคติที่ละเอียดอ่อนตัวพิมพ์เล็กและใหญ่เมื่อเรียง ฉันชอบเป็นพิเศษที่คุณสามารถจัดการกับเครื่องมือจัดฟันที่ไม่ตรงกันได้
stribika

16

ฉันไม่สามารถบอกได้ว่าอัลกอริทึมใดถูกใช้โดยการใช้งานใด ๆ แต่ฉันสามารถคาดเดาได้ Trieเป็นโครงสร้างข้อมูลที่เป็นประโยชน์มากสำหรับปัญหานี้: IDE สามารถรักษา Trie ขนาดใหญ่ในหน่วยความจำทั้งหมดของสัญลักษณ์ในโครงการของคุณมีบางข้อมูลเมตาพิเศษที่แต่ละโหนด

เมื่อคุณพิมพ์อักขระตัวอักษรจะเดินไปตามเส้นทางในสาม ลูกหลานทั้งหมดของโหนด trie เฉพาะคือความสำเร็จที่เป็นไปได้ จากนั้น IDE ก็ต้องกรองสิ่งที่เหมาะสมในบริบทปัจจุบัน แต่จะต้องคำนวณให้มากที่สุดเท่าที่จะทำได้ในหน้าต่างป๊อปอัปการเติมแท็บ

การเติมแท็บขั้นสูงเพิ่มเติมจำเป็นต้องมีสามแบบที่ซับซ้อนมากขึ้น ตัวอย่างเช่นVisual Assist Xมีคุณลักษณะที่คุณต้องพิมพ์ตัวพิมพ์ใหญ่ของสัญลักษณ์ CamelCase เท่านั้นเช่นหากคุณพิมพ์ SFN จะแสดงสัญลักษณ์ให้คุณเห็นSomeFunctionNameในหน้าต่างการเติมแท็บ

การคำนวณ trie (หรือโครงสร้างข้อมูลอื่น ๆ ) จำเป็นต้องมีการแยกวิเคราะห์รหัสทั้งหมดของคุณเพื่อรับรายการสัญลักษณ์ทั้งหมดในโครงการของคุณ Visual Studio จัดเก็บสิ่งนี้ไว้ในฐานข้อมูล IntelliSense ซึ่งเป็น.ncbไฟล์ที่จัดเก็บไว้ข้างโปรเจ็กต์ของคุณเพื่อที่จะไม่ต้องแยกวิเคราะห์ทุกอย่างทุกครั้งที่คุณปิดและเปิดโปรเจ็กต์ของคุณอีกครั้ง ในครั้งแรกที่คุณเปิดโปรเจ็กต์ขนาดใหญ่ (เช่นคุณเพิ่งซิงค์ตัวควบคุมแหล่งที่มาของฟอร์ม) VS จะใช้เวลาในการแยกวิเคราะห์ทุกอย่างและสร้างฐานข้อมูล

ฉันไม่รู้ว่ามันจัดการกับการเปลี่ยนแปลงที่เพิ่มขึ้นอย่างไร อย่างที่คุณบอกเมื่อคุณเขียนโค้ดมันเป็นไวยากรณ์ที่ไม่ถูกต้อง 90% ของเวลาและการแยกวิเคราะห์ทุกครั้งที่คุณไม่ได้ใช้งานจะทำให้ CPU ของคุณเสียภาษีเป็นจำนวนมากเพื่อผลประโยชน์เพียงเล็กน้อยโดยเฉพาะอย่างยิ่งหากคุณกำลังแก้ไขไฟล์ส่วนหัวที่รวมอยู่ใน ไฟล์ต้นฉบับจำนวนมาก

ฉันสงสัยว่า (ก) จะทำการแยกวิเคราะห์เฉพาะเมื่อคุณสร้างโปรเจ็กต์ของคุณจริง ๆ (หรืออาจจะเป็นเมื่อคุณปิด / เปิด) หรือ (ข) มันจะทำการแยกวิเคราะห์เฉพาะบางประเภทโดยที่มันจะแยกวิเคราะห์โค้ดในบริเวณที่คุณเพิ่ง แก้ไขในรูปแบบที่ จำกัด เพียงเพื่อให้ได้ชื่อของสัญลักษณ์ที่เกี่ยวข้อง เนื่องจาก C ++ มีไวยากรณ์ที่ซับซ้อนอย่างโดดเด่นจึงอาจทำงานผิดปกติในมุมมืดหากคุณใช้ metaprogramming เทมเพลตจำนวนมากและอื่น ๆ


ไตรเป็นความคิดที่ดีจริงๆ สำหรับการเปลี่ยนแปลงที่เพิ่มขึ้นอาจเป็นไปได้ที่จะพยายามแยกวิเคราะห์ไฟล์อีกครั้งก่อนเมื่อไม่ได้ผลโดยไม่สนใจบรรทัดปัจจุบันและเมื่อใดที่ไม่ได้ผลให้เพิกเฉยต่อบล็อก {... } ที่ล้อมรอบ หากทุกอย่างล้มเหลวให้ใช้ฐานข้อมูลสุดท้าย
stribika

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.