Difftastic เครื่องมือวิเคราะห์ diff เชิงโครงสร้างที่เข้าใจไวยากรณ์
(difftastic.wilfred.me.uk)- Difftastic คือเครื่องมือ CLI สำหรับ diff ที่เปรียบเทียบไฟล์โดยอิงจาก โครงสร้างไวยากรณ์ ของโค้ด ไม่ใช่ทีละบรรทัด
- ภายในใช้ tree-sitter ในการ parse โค้ด จึงแยกแยะ expression ที่เปลี่ยนจริงได้ดีกว่า แม้บรรทัดจะเปลี่ยนไปเพราะการจัดรูปแบบ
- แม้ code formatter จะแยกรายการออกเป็นหลายบรรทัด หรือมีการเพิ่ม wrapper ก็สามารถแสดงโครงสร้างที่เปลี่ยนไปแยกจากเนื้อหาภายในที่ยังคงเดิมได้
- มีเอกสารแนะนำการตั้งค่า Git และการติดตั้ง รองรับภาษาโปรแกรมหลายภาษา รวมถึงรูปแบบข้อความเชิงโครงสร้างอย่าง JSON และ YAML
- เผยแพร่ภายใต้ MIT License จึงสามารถดาวน์โหลด แก้ไข และแชร์ได้ เหมาะสำหรับเพิ่มการเปรียบเทียบเชิงโครงสร้างเข้าไปใน workflow diff ที่ใช้อยู่เดิม
เปรียบเทียบโครงสร้างไวยากรณ์ ไม่ใช่บรรทัด
- Difftastic คือเครื่องมือ CLI สำหรับ diff ที่เปรียบเทียบไฟล์โดยอิงจากไวยากรณ์
- มุ่งเน้นการแสดงผลที่เข้าใจโครงสร้างโค้ดได้ดีกว่า diff แบบทีละบรรทัดทั่วไป
- โค้ดถูก parse ด้วย tree-sitter
- สามารถแยกแยะได้ว่า expression ภายในที่ใน diff แบบเน้นบรรทัดอาจดูเหมือนเปลี่ยนไปนั้น จริง ๆ แล้วเปลี่ยนหรือไม่
- แม้ code formatter จะแบ่งโค้ดออกเป็นหลายบรรทัด ก็ยังสามารถแสดง การเปลี่ยนแปลงจริง ได้
- เมื่อมีการเพิ่ม wrapper จะจับคู่ตัวคั่นได้อย่างแม่นยำ
- แม้เนื้อหาภายในจะเปลี่ยนไปด้วย ก็สามารถแสดง wrapper ที่ถูกเพิ่ม ได้
- แทนที่จะใช้ไวยากรณ์ diff hunk เช่น
@@ -5,6 +5,7 @@จะแสดง หมายเลขบรรทัดจริง ของไฟล์ก่อนและหลังการเปลี่ยนแปลง
ภาษาที่รองรับและการเชื่อมต่อกับ Git
- ข้อมูลโครงการมีให้ที่ GitHub, Manual, Install
- ตัวอย่างภาษาโปรแกรมที่รองรับมีดังนี้
- C++, C#, Clojure, Dart, Erlang, Go, Haskell, Java, JavaScript, Kotlin, Lisp, Lua, OCaml, PHP, Python, R, Ruby, Rust, Scala, TypeScript
- ดูรายการทั้งหมดได้ที่ รายชื่อภาษาที่รองรับ ในคู่มือ
- ตัวอย่างรูปแบบไฟล์ที่รองรับมีดังนี้
- HCL, HTML, JSON, YAML
- ดูรายการทั้งหมดได้ที่ รายชื่อรูปแบบข้อความเชิงโครงสร้าง ในคู่มือ
- สามารถใช้ร่วมกับ Git ได้ และในคู่มือมีคำแนะนำ การตั้งค่า Git
- Difftastic เผยแพร่ภายใต้ MIT License จึงสามารถดาวน์โหลด แก้ไข และแชร์ได้
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
บางคนอาจรู้อยู่แล้ว แต่เครื่องมือนี้สร้างบน tree-sitter(https://tree-sitter.github.io/tree-sitter/) และอาจพูดได้ว่ามันทำให้การพาร์สได้ในสิ่งที่ LSP ทำให้กับการวิเคราะห์
มันแปลงโค้ดเป็นต้นไม้ไวยากรณ์เชิงนามธรรม (AST) และให้ส่วนติดต่อมาตรฐานเพื่อให้ไคลเอนต์อย่างเอดิเตอร์หรือเครื่องมือ diff นำ AST นั้นไปใช้ได้
เครื่องมือแบบนี้ไม่จำเป็นต้องรองรับภาษาหลายสิบภาษาเองโดยตรง แค่รองรับ tree-sitter ก็สามารถทำงานกับทุกภาษาที่ tree-sitter รองรับได้โดยอัตโนมัติ
ถ้ากำลังพัฒนาภาษาใหม่ ก็สร้าง parser ของ tree-sitter ขึ้นมา แล้วเครื่องมือทั้งหมดที่จัดการกับ tree-sitter ก็จะรองรับภาษานั้นได้
ด้วยนวัตกรรมใหญ่สองอย่างนี้ การปรับปรุงเครื่องมือแบบนี้จึงเพิ่มขึ้นอย่างรวดเร็ว ตอนนี้เอดิเตอร์ เครื่องมือ diff ฯลฯ สามารถรองรับภาษาหลายสิบถึงหลายร้อยภาษาได้โดยไม่ต้องทำงานซ้ำ ๆ กันเอง น่าทึ่งจริง ๆ
อ้างอิงเพิ่มเติม ทีมของเราหลายคนดูแล OCaml tree-sitter bindings และมักมีส่วนร่วมกับ grammar ต่าง ๆ เป็นส่วนหนึ่งของงาน Semgrep ด้วย Semgrep ใช้ tree-sitter สำหรับการค้นหาโค้ด และสำหรับพาร์ส query ซึ่งเป็นชิ้นส่วนโค้ดเองให้เป็น AST matcher
เวลาสร้าง linter มักต้องพก runtime ของภาษาเป้าหมายมาด้วย ตัวอย่างเช่น ถ้าสร้าง parser ใน Python ด้วยโมดูล
astในตัว ก็ต้องให้ตรงกับเวอร์ชันภาษาและฟีเจอร์ ทำให้ Pylint ที่รันบน Python 2.7 ไม่สามารถพาร์สโค้ด Python 3 ได้ถ้าเป้าหมายมีหลายภาษา เรื่องจะยุ่งยากกว่าที่คิดไว้ตอนแรกมาก
ก่อนมี tree-sitter หลายครั้งทางเลือกที่ดีที่สุดคือใช้เครื่องมือ AST ในตัวของภาษา เพราะรับประกันได้ว่าจะตามไวยากรณ์ล่าสุดทัน
โดยส่วนตัว อัจฉริยภาพของ tree-sitter อยู่ตรงที่ทำให้การรักษา parser ของภาษาให้ทันสมัยง่ายกว่าวิธี grammar แบบดั้งเดิมมาก
ถ้าอยากรู้เพิ่มเติมเกี่ยวกับการตัดสินใจออกแบบของ tree-sitter ขอแนะนำอย่างยิ่งให้ดูทอล์ก Strange Loop ของ Max Brunsfield: https://www.youtube.com/watch?v=Jes3bD6P0To
ผลก็คือ นอกจาก difftastic แล้ว เครื่องมือใหม่หลายอย่างอย่าง neovim, Zed, Semgrep และ GitHub code search ก็ถูกสร้างบน tree-sitter
ผมเคยเขียน grammar ของ tree-sitter และชุด query สำหรับ highlighting ให้ VHDL แล้วพบว่ามีปัญหามากในการแสดงไวยากรณ์ของ VHDL ด้วย tree-sitter
LSP ใช้ interface มาตรฐานสำหรับ compiler แบบ query-oriented และแก้ปัญหา integration แบบ M×N ระหว่างเอดิเตอร์ M ตัวกับภาษา N ภาษาได้จริง
tree-sitter ไม่ได้แก้ปัญหานั้นเท่าไรนัก แต่ทำให้การเขียน integration จำนวน N รายการในเอดิเตอร์หรือเครื่องมือต่าง ๆ ง่ายขึ้นมาก
Common Lisp พาร์สไม่ได้ถ้าไม่มี implementation ของ Lisp ที่สมบูรณ์, grammar ของ Haskell ซับซ้อนพอที่จะทำให้ grammar ยังไม่สมบูรณ์, C/C++ แค่ดู preprocessor ก็พาร์สให้ถูกต้องได้ยากแล้ว และการพาร์ส Perl เป็น Turing complete
เหมือนข้อเสนอจากที่อื่น ๆ ผมคิดว่าควรกำหนดรูปแบบที่ parser เดิมสามารถสร้างเป็น output เสริมได้ แทนที่จะให้เขียน parser ใหม่ใน ecosystem ใหม่
ถ้าทำได้ difftastic ก็น่าจะแสดงการเปลี่ยนแปลงตามประโยคแทนที่จะเป็นบรรทัดตามภาพได้
ในเรียงความ diff ตามบรรทัดที่เห็นด้วยตาดูแปลก เพราะประโยคที่เปลี่ยนมักเริ่มตรงกลางของบรรทัดที่เห็น
ถ้าติดตั้งด้วย cargo วิธีอัปเดต difftastic และเครื่องมือที่เกี่ยวข้องมีดังนี้
cargo install cargo-updatecargo install-update --listcargo install-update --allยังมีโปรเจกต์ Rust น่าสนใจที่ติดตั้งด้วย cargo ได้ด้วย
https://mise.jdx.dev/ mise-en-place เป็นทางเลือกที่รวดเร็วและยืดหยุ่นแทน https://asdf-vm.com/
https://github.com/ajeetdsouza/zoxide เป็นเครื่องมือแทน
cdที่ยอดเยี่ยม โดยบันทึกตำแหน่งที่เคยไปไว้ แล้วสามารถไปยัง"~/projects/helloworld"ได้ด้วยการ match บางส่วนอย่าง"z hel"https://github.com/bootandy/dust เป็นเครื่องมือเสริม
duที่แสดงว่าไดเรกทอรีใดใช้พื้นที่ดิสก์มากlsที่ดูสวยขึ้น, https://github.com/ClementTsang/bottom คือhtopที่มีกราฟ, https://github.com/sharkdp/bat คือcatที่มี syntax highlightinglsที่สวยกว่าduรุ่นใหม่ ๆ ชอบ dua-cli มากที่สุด เป็น UI เทอร์มินัลแบบ interactive ที่เป็นโคลนของncduไปเจอมันระหว่างหาว่า
ncduมี light mode ไหม ซึ่งไม่มีncduเป็นเครื่องมือแทนduที่ดีที่สุดอย่างชัดเจนแทบไม่เข้าใจเลยว่าทำไมเครื่องมืออย่าง git ถึงไม่ให้สิ่งนี้มาเป็นค่าเริ่มต้น
ผมใช้ difft มาประมาณ 1 ปีแล้ว และข้อไม่พอใจใหญ่ที่สุดคือมันทำให้ยากที่จะกลับไปใช้เครื่องมือ diff อื่นในสภาพแวดล้อมที่ไม่มี difft
สงสัยว่ามีงานวิจัยเกี่ยวกับเครื่องมือ diff เชิงความหมาย บ้างไหม เช่น เครื่องมือที่จัดการกรณีที่ไวยากรณ์เปลี่ยนไปแต่ความหมายยังเหมือนเดิม
โดยทั่วไปอาจดูเป็นปัญหาที่ยาก แต่สำหรับ DSL ขนาดเล็กหรือบาง subset ของภาษา อาจเป็นไปได้หรือมีประโยชน์ก็ได้
diffแบบเก่าเป็นค่าเริ่มต้นน่าจะถูกแล้วมันมีอยู่แล้วในแทบทุกระบบที่พยายามจะรัน git, เร็ว, เล็ก และทุกคนรู้วิธีใช้พื้นฐาน
แต่ก็ดีที่เปลี่ยนค่าเริ่มต้นนั้นได้ง่าย
ไม่ใช่ยูทิลิตีบรรทัดคำสั่ง แต่เป็นส่วนขยาย VS Code กับ GitHub App
ถ้าสงสัยว่ามันทำงานอย่างไรและต่างจาก difftastic อย่างไร ดูได้ที่ https://semanticdiff.com/blog/semanticdiff-vs-difftastic/
แต่หนึ่งในเหตุผลที่คิดว่าไม่ควรเป็นค่าเริ่มต้นคือมันซ่อนความต่างของ whitespace ยังไม่ได้เช็กว่าตั้งค่าได้หรือเปล่า
เห็นด้วยสุด ๆ กับคำถามว่าอ่าน syntax
@@ -5,6 +5,7 @@เป็นไหม และคำอธิบายว่า Difftastic แสดงหมายเลขบรรทัดจริงของไฟล์ก่อนและหลังการเปลี่ยนแปลงผมติดตั้งทันทีแล้วลอง
git diffดู ทำงานได้ดีมากการที่ GitHub ยังไม่ออกอะไรแบบนี้มาดูเหมือนเป็น ช่องว่างใหญ่ด้านนวัตกรรมผลิตภัณฑ์
แค่เปลี่ยน indentation ก็ไม่มีฟีเจอร์ช่วย และมักแสดงเป็นการลบกับเพิ่มก้อนมหึมา
diff viewer ของ GitHub ฉลาดกว่านี้ได้ และควรจะเป็นแบบนั้น
จริง ๆ แล้วมันยังแยกไม่ได้ด้วยซ้ำว่า
foo.cpp.inกับfoo.mk.inต่างกันfoo.tอะไรก็ตามจะถูกประกาศเป็น Perl และไม่มีวิธีแก้ มี ticket ที่อายุ 10 ปีแล้วด้วยในความเป็นจริง นักพัฒนาหลักของ TS ก็แนะนำว่าอย่าไปกังวลกับความถูกต้องของ grammar มากเกินไป[1]
สำหรับ GitHub หรือบริการที่อยู่ในสถานการณ์คล้ายกัน ข้อจำกัดนี้ดูเหมือนอาจเป็นเรื่องร้ายแรง
[1] https://github.com/tree-sitter/tree-sitter/issues/130#issuec...
เคยพยายามเปลี่ยนมาใช้เครื่องมือนี้ แต่รู้สึกว่า รกเกินไป เพราะมันใช้รูปแบบแปลก ๆ แม้กับส่วนที่ไม่ได้เปลี่ยน
สุดท้ายเลยกลับไปใช้ icdiff[0]
[0]: https://github.com/jeffkaufman/icdiff
เคยพบ Semantic Merge[1] มาก่อน แต่ไม่ใช่โอเพนซอร์ส
ตัวนี้ไม่ได้ merge ทำแค่ diff แต่ zumindest ก็เป็นโอเพนซอร์ส และ diff ดูอ่านง่ายกว่ามาก เลยเปลี่ยนเป็นค่าเริ่มต้นไปแล้ว
สงสัยว่ามีแผนจะขยายไปทำ ฟีเจอร์ merge หรือไม่
[1] https://docs.plasticscm.com/semanticmerge
บอกว่า AST merge เป็นปัญหาที่ยาก และ difftastic ไม่ครอบคลุมเรื่องนี้
อีกอย่าง AST diff เป็นกระบวนการที่มีการสูญเสียข้อมูลเมื่อมองจากมุมของ text diff difftastic จะละเว้น whitespace ที่ไม่มีความหมายทางไวยากรณ์ แต่การ merge จำเป็นต้องติดตาม whitespace
ตอนทำ refactor เลอะ ๆ ของโปรเจกต์ C# ในทีมใหญ่ มันเป็นเครื่องมือช่วยชีวิตจริง ๆ
การพูดคุยก่อนหน้านี้:
https://news.ycombinator.com/item?id=27768861 (297 points | 3 years ago | 61 comments)
https://news.ycombinator.com/item?id=32746258 (698 points | 2 years ago | 90 comments)
https://news.ycombinator.com/item?id=30841244 (983 points | 2 years ago | 219 comments)
ดูเหมือนเป็นเครื่องมือที่ยอดเยี่ยม แต่พอลองกับไฟล์ XML ขนาดใหญ่สองสามไฟล์ บรรทัดที่แก้ไขแสดงเป็นสีเขียวธรรมดา ส่วน attribute ที่แก้ไขแสดงเป็นสีเขียวตัวหนา ทำให้ แยกด้วยสายตาได้ยาก
ในเอกสารไม่พบวิธีเปลี่ยนสไตล์ diff หรือกำหนดสีอื่นสำหรับกรณีตัวหนา
สงสัยว่ามีใครรู้วิธีไหม
ผมไปคอมเมนต์ใน issue นี้[1] ว่าสนับสนุนฟีเจอร์นี้
ถ้าฟีเจอร์ที่ต้องการอยู่ในหมวดเดียวกัน ก็ไปช่วยแสดงความเห็นที่นั่นได้ หรือถ้าคิดว่าเป็นฟีเจอร์ที่ต่างกันพอสมควร จะเปิด issue ใหม่ก็น่าจะได้
[1]: https://github.com/Wilfred/difftastic/issues/611
แพ็กเกจของ Arch Linux มีขนาด 7MB แต่หลังติดตั้งแล้วคลายออกมาเป็น 80MB และน่าสนใจตรงที่แค่ไบนารี
difftอย่างเดียวก็ 78MB แล้วบนชุดข้อมูล ZFS ที่เปิดการบีบอัด LZ4 ว่ากันว่า
duแสดงเป็น 17MBสงสัยว่าทำไมถึงไม่บีบอัดอะไรบางอย่างที่บีบอัดได้ดีมากขนาดนี้ไว้ในตัวไบนารีเลย ถ้าคลายออกใน RAM ก็ดูเหมือนว่าอาจโหลดได้เร็วขึ้น
Processed 1 file, 614 regular extents (614 refs), 0 inline.Type Perc Disk Usage Uncompressed ReferencedTOTAL 14% 10M 77M 77Mnone 100% 1.1M 1.1M 1.1Mzstd 12% 9.8M 76M 76MTree-sitter ดูเหมือนต้องใช้โค้ดจำนวนมหาศาล และส่วนใหญ่ในนั้นก็ดูซ้ำซ้อนหรือบีบอัดได้
แต่มันถูกทำเป็น memory mapping และจะโหลดเฉพาะส่วนที่ใช้งานจริงเมื่อจำเป็น
ดังนั้นถ้าบีบอัดไบนารีขนาดใหญ่ demand paging จะไม่ทำงาน และกลับทำให้ช้าลง
เคยทดสอบกับ Windows EXE ขนาด 280MB เมื่อหลายปีก่อน แม้จะบีบอัดได้เหลือประมาณ 70MB แต่ตอนเริ่มต้นใช้เวลานานกว่าต้นฉบับหลายวินาที
ในบางสถานการณ์อย่างการรันไบนารีผ่านเครือข่ายก็อาจสมเหตุสมผล แต่โดยส่วนใหญ่ไบนารีที่ไม่บีบอัดจะเริ่มทำงานได้เร็วกว่า