3 คะแนน โดย GN⁺ 2024-03-22 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • 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
  • ตัวอย่างภาษาโปรแกรมที่รองรับมีดังนี้
  • ตัวอย่างรูปแบบไฟล์ที่รองรับมีดังนี้
  • สามารถใช้ร่วมกับ Git ได้ และในคู่มือมีคำแนะนำ การตั้งค่า Git
  • Difftastic เผยแพร่ภายใต้ MIT License จึงสามารถดาวน์โหลด แก้ไข และแชร์ได้

1 ความคิดเห็น

 
GN⁺ 2024-03-22
ความคิดเห็นจาก 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 ฯลฯ สามารถรองรับภาษาหลายสิบถึงหลายร้อยภาษาได้โดยไม่ต้องทำงานซ้ำ ๆ กันเอง น่าทึ่งจริง ๆ

    • เห็นด้วยอย่างยิ่ง และถ้าจะยกคอมเมนต์ที่ผมเคยเขียนไว้ปีที่แล้วกลับมาอีกครั้ง ก็มีอีกจุดที่ควรเน้นคือ tree-sitter มีโครงสร้างที่ไม่มี dependency
      อ้างอิงเพิ่มเติม ทีมของเราหลายคนดูแล 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
    • เห็นด้วยว่า tree-sitter เป็นเครื่องมือที่ยอดเยี่ยม แต่การ เขียน grammar เอง อาจยากมาก
      ผมเคยเขียน grammar ของ tree-sitter และชุด query สำหรับ highlighting ให้ VHDL แล้วพบว่ามีปัญหามากในการแสดงไวยากรณ์ของ VHDL ด้วย tree-sitter
    • ผมคิดว่าคำอธิบายนี้ไม่ถูกต้อง ไม่มี interface กลางแบบที่เรียกว่า พูดภาษา tree-sitter และ parser ของ tree-sitter แต่ละตัวก็ปล่อย concrete syntax tree ที่แตกต่างกัน ไม่ใช่ abstract syntax tree มาตรฐาน
      LSP ใช้ interface มาตรฐานสำหรับ compiler แบบ query-oriented และแก้ปัญหา integration แบบ M×N ระหว่างเอดิเตอร์ M ตัวกับภาษา N ภาษาได้จริง
      tree-sitter ไม่ได้แก้ปัญหานั้นเท่าไรนัก แต่ทำให้การเขียน integration จำนวน N รายการในเอดิเตอร์หรือเครื่องมือต่าง ๆ ง่ายขึ้นมาก
    • สิ่งที่ติดใจที่สุดเกี่ยวกับ tree-sitter คือแนวทางนี้ใช้ไม่ได้กับหลายภาษาที่ผมสนใจ
      Common Lisp พาร์สไม่ได้ถ้าไม่มี implementation ของ Lisp ที่สมบูรณ์, grammar ของ Haskell ซับซ้อนพอที่จะทำให้ grammar ยังไม่สมบูรณ์, C/C++ แค่ดู preprocessor ก็พาร์สให้ถูกต้องได้ยากแล้ว และการพาร์ส Perl เป็น Turing complete
      เหมือนข้อเสนอจากที่อื่น ๆ ผมคิดว่าควรกำหนดรูปแบบที่ parser เดิมสามารถสร้างเป็น output เสริมได้ แทนที่จะให้เขียน parser ใหม่ใน ecosystem ใหม่
    • จะสร้าง grammar ของ tree-sitter สำหรับภาษาธรรมชาติ อย่างภาษาอังกฤษ เพื่อ label แต่ละประโยคเป็นหน่วยประโยคได้ไหม?
      ถ้าทำได้ difftastic ก็น่าจะแสดงการเปลี่ยนแปลงตามประโยคแทนที่จะเป็นบรรทัดตามภาพได้
      ในเรียงความ diff ตามบรรทัดที่เห็นด้วยตาดูแปลก เพราะประโยคที่เปลี่ยนมักเริ่มตรงกลางของบรรทัดที่เห็น
  • ถ้าติดตั้งด้วย cargo วิธีอัปเดต difftastic และเครื่องมือที่เกี่ยวข้องมีดังนี้
    cargo install cargo-update
    cargo install-update --list
    cargo 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 ที่แสดงว่าไดเรกทอรีใดใช้พื้นที่ดิสก์มาก

    • ถ้าจะยกเครื่องมือดี ๆ เพิ่มอีกสามตัว https://github.com/eza-community/eza คือ ls ที่ดูสวยขึ้น, https://github.com/ClementTsang/bottom คือ htop ที่มีกราฟ, https://github.com/sharkdp/bat คือ cat ที่มี syntax highlighting
    • ชอบ zoxide มาก ๆ และ lsd ก็ควรเพิ่มเข้าไปในรายการด้วย เป็น ls ที่สวยกว่า
    • ลองติดตั้ง mise-en-place แล้ว มันคือสิ่งที่อยากให้ asdf เป็นพอดี
    • ในบรรดาเครื่องมือกลุ่ม du รุ่นใหม่ ๆ ชอบ dua-cli มากที่สุด เป็น UI เทอร์มินัลแบบ interactive ที่เป็นโคลนของ ncdu
      ไปเจอมันระหว่างหาว่า ncdu มี light mode ไหม ซึ่งไม่มี
    • ncdu เป็นเครื่องมือแทน du ที่ดีที่สุดอย่างชัดเจน
  • แทบไม่เข้าใจเลยว่าทำไมเครื่องมืออย่าง git ถึงไม่ให้สิ่งนี้มาเป็นค่าเริ่มต้น
    ผมใช้ difft มาประมาณ 1 ปีแล้ว และข้อไม่พอใจใหญ่ที่สุดคือมันทำให้ยากที่จะกลับไปใช้เครื่องมือ diff อื่นในสภาพแวดล้อมที่ไม่มี difft
    สงสัยว่ามีงานวิจัยเกี่ยวกับเครื่องมือ diff เชิงความหมาย บ้างไหม เช่น เครื่องมือที่จัดการกรณีที่ไวยากรณ์เปลี่ยนไปแต่ความหมายยังเหมือนเดิม
    โดยทั่วไปอาจดูเป็นปัญหาที่ยาก แต่สำหรับ DSL ขนาดเล็กหรือบาง subset ของภาษา อาจเป็นไปได้หรือมีประโยชน์ก็ได้

    • ผมคิดว่าใช้ diff แบบเก่าเป็นค่าเริ่มต้นน่าจะถูกแล้ว
      มันมีอยู่แล้วในแทบทุกระบบที่พยายามจะรัน git, เร็ว, เล็ก และทุกคนรู้วิธีใช้พื้นฐาน
      แต่ก็ดีที่เปลี่ยนค่าเริ่มต้นนั้นได้ง่าย
    • กำลังทำ https://semanticdiff.com/ อยู่ ซึ่งตรวจจับ การเปลี่ยนแปลงเชิงความหมาย พื้นฐานอย่างการเปลี่ยน literal ฐานสิบเป็นเลขฐานสิบหก หรือการเปลี่ยนลำดับ key ใน JSON object
      ไม่ใช่ยูทิลิตีบรรทัดคำสั่ง แต่เป็นส่วนขยาย VS Code กับ GitHub App
      ถ้าสงสัยว่ามันทำงานอย่างไรและต่างจาก difftastic อย่างไร ดูได้ที่ https://semanticdiff.com/blog/semanticdiff-vs-difftastic/
    • Difftastic เป็นเครื่องมือที่มีประโยชน์ แต่จากประสบการณ์ของผม มัน ช้าเกินไป สำหรับการเป็นตัวเลือกเริ่มต้นของเครื่องมือที่ใช้กันแพร่หลายอย่าง git
    • ถ้าจะทำแบบนี้ เครื่องมือ diff ก็จะกลายเป็น คอมไพเลอร์ ไปโดยปริยาย
    • ตอนนี้กำลังลองใช้ difft กับ git อยู่ และค่อนข้างชอบ
      แต่หนึ่งในเหตุผลที่คิดว่าไม่ควรเป็นค่าเริ่มต้นคือมันซ่อนความต่างของ whitespace ยังไม่ได้เช็กว่าตั้งค่าได้หรือเปล่า
  • เห็นด้วยสุด ๆ กับคำถามว่าอ่าน syntax @@ -5,6 +5,7 @@ เป็นไหม และคำอธิบายว่า Difftastic แสดงหมายเลขบรรทัดจริงของไฟล์ก่อนและหลังการเปลี่ยนแปลง
    ผมติดตั้งทันทีแล้วลอง git diff ดู ทำงานได้ดีมาก

    • อ่าน syntax นั้นไม่ออกเหรอ?
  • การที่ GitHub ยังไม่ออกอะไรแบบนี้มาดูเหมือนเป็น ช่องว่างใหญ่ด้านนวัตกรรมผลิตภัณฑ์
    แค่เปลี่ยน indentation ก็ไม่มีฟีเจอร์ช่วย และมักแสดงเป็นการลบกับเพิ่มก้อนมหึมา
    diff viewer ของ GitHub ฉลาดกว่านี้ได้ และควรจะเป็นแบบนั้น

    • GitHub ยังทำ syntax awareness ให้ดีไม่ได้ด้วยซ้ำ ไม่ต้องพูดถึง diff เชิงความหมาย
      จริง ๆ แล้วมันยังแยกไม่ได้ด้วยซ้ำว่า foo.cpp.in กับ foo.mk.in ต่างกัน
      foo.t อะไรก็ตามจะถูกประกาศเป็น Perl และไม่มีวิธีแก้ มี ticket ที่อายุ 10 ปีแล้วด้วย
    • tree-sitter ถูกปรับให้เหมาะกับ ประสิทธิภาพ สำหรับใช้ใน editor ไม่ใช่ความถูกต้อง
      ในความเป็นจริง นักพัฒนาหลักของ TS ก็แนะนำว่าอย่าไปกังวลกับความถูกต้องของ grammar มากเกินไป[1]
      สำหรับ GitHub หรือบริการที่อยู่ในสถานการณ์คล้ายกัน ข้อจำกัดนี้ดูเหมือนอาจเป็นเรื่องร้ายแรง
      [1] https://github.com/tree-sitter/tree-sitter/issues/130#issuec...
    • GitHub มี option ให้ละเว้น whitespace ใน diff
  • เคยพยายามเปลี่ยนมาใช้เครื่องมือนี้ แต่รู้สึกว่า รกเกินไป เพราะมันใช้รูปแบบแปลก ๆ แม้กับส่วนที่ไม่ได้เปลี่ยน
    สุดท้ายเลยกลับไปใช้ icdiff[0]
    [0]: https://github.com/jeffkaufman/icdiff

  • เคยพบ Semantic Merge[1] มาก่อน แต่ไม่ใช่โอเพนซอร์ส
    ตัวนี้ไม่ได้ merge ทำแค่ diff แต่ zumindest ก็เป็นโอเพนซอร์ส และ diff ดูอ่านง่ายกว่ามาก เลยเปลี่ยนเป็นค่าเริ่มต้นไปแล้ว
    สงสัยว่ามีแผนจะขยายไปทำ ฟีเจอร์ merge หรือไม่
    [1] https://docs.plasticscm.com/semanticmerge

    • ตาม GitHub README difftastic ไม่ทำ merge
      บอกว่า AST merge เป็นปัญหาที่ยาก และ difftastic ไม่ครอบคลุมเรื่องนี้
      อีกอย่าง AST diff เป็นกระบวนการที่มีการสูญเสียข้อมูลเมื่อมองจากมุมของ text diff difftastic จะละเว้น whitespace ที่ไม่มีความหมายทางไวยากรณ์ แต่การ merge จำเป็นต้องติดตาม whitespace
    • ผมก็ว่าจะมาแนะนำ Semantic Merge เหมือนกัน
      ตอนทำ 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 ก็ดูเหมือนว่าอาจโหลดได้เร็วขึ้น

    • พอมีประเด็นเรื่องการบีบอัดเลยลองตรวจสอบดู และบน btrfs zstd:1 ได้ประมาณ 10MB
      Processed 1 file, 614 regular extents (614 refs), 0 inline.
      Type Perc Disk Usage Uncompressed Referenced
      TOTAL 14% 10M 77M 77M
      none 100% 1.1M 1.1M 1.1M
      zstd 12% 9.8M 76M 76M
    • น่าจะเกี่ยวข้องกับคอมเมนต์ที่บอกว่า parser ของ Nim เป็นไฟล์ C ขนาด 60MB ที่ merge ไม่ได้
      Tree-sitter ดูเหมือนต้องใช้โค้ดจำนวนมหาศาล และส่วนใหญ่ในนั้นก็ดูซ้ำซ้อนหรือบีบอัดได้
    • ไฟล์ปฏิบัติการไม่ได้ถูกโหลดเข้า RAM ทั้งก้อนแล้วค่อยเริ่มทำงาน
      แต่มันถูกทำเป็น memory mapping และจะโหลดเฉพาะส่วนที่ใช้งานจริงเมื่อจำเป็น
      ดังนั้นถ้าบีบอัดไบนารีขนาดใหญ่ demand paging จะไม่ทำงาน และกลับทำให้ช้าลง
      เคยทดสอบกับ Windows EXE ขนาด 280MB เมื่อหลายปีก่อน แม้จะบีบอัดได้เหลือประมาณ 70MB แต่ตอนเริ่มต้นใช้เวลานานกว่าต้นฉบับหลายวินาที
      ในบางสถานการณ์อย่างการรันไบนารีผ่านเครือข่ายก็อาจสมเหตุสมผล แต่โดยส่วนใหญ่ไบนารีที่ไม่บีบอัดจะเริ่มทำงานได้เร็วกว่า