3 điểm bởi GN⁺ 2024-03-22 | 1 bình luận | Chia sẻ qua WhatsApp
  • Difftastic là một công cụ diff CLI so sánh tệp dựa trên cấu trúc cú pháp của mã, thay vì theo từng dòng
  • Bên trong, công cụ dùng tree-sitter để phân tích mã, nhờ đó có thể phân biệt tốt hơn những biểu thức thực sự thay đổi ngay cả khi số dòng bị thay đổi do định dạng
  • Ngay cả khi trình định dạng mã tách các mục thành nhiều dòng hoặc khi có thêm wrapper, công cụ vẫn có thể tách riêng phần cấu trúc đã thay đổi và phần nội dung bên trong được giữ nguyên để hiển thị
  • Có tài liệu hướng dẫn cấu hình Git và cài đặt, đồng thời hỗ trợ nhiều ngôn ngữ lập trình cùng các định dạng văn bản có cấu trúc như JSON và YAML
  • Được phát hành theo giấy phép MIT, cho phép tải xuống, chỉnh sửa và chia sẻ, phù hợp để bổ sung khả năng so sánh có cấu trúc vào quy trình diff hiện có

So sánh cấu trúc cú pháp thay vì từng dòng

  • Difftastic là công cụ diff CLI so sánh tệp theo cú pháp
  • Tập trung vào việc hiển thị kết quả hiểu được cấu trúc mã tốt hơn so với diff theo dòng thông thường
  • Mã được phân tích bằng tree-sitter
    • Ngay cả những biểu thức nội bộ có thể trông như đã thay đổi trong diff theo dòng cũng có thể được phân biệt xem có thực sự thay đổi hay không
  • Ngay cả khi trình định dạng mã chia mã thành nhiều dòng, công cụ vẫn có thể hiển thị những thay đổi thực sự
  • Khi có thêm wrapper, công cụ sẽ khớp chính xác các dấu phân cách
    • Ngay cả khi nội dung bên trong cũng thay đổi, công cụ vẫn có thể hiển thị wrapper được thêm vào
  • Thay vì cú pháp diff hunk như @@ -5,6 +5,7 @@, công cụ hiển thị số dòng thực tế của tệp trước và sau khi thay đổi

Ngôn ngữ được hỗ trợ và tích hợp Git

  • Tài liệu dự án có tại GitHub, Manual, Install
  • Ví dụ về các ngôn ngữ lập trình được hỗ trợ gồm:
    • C++, C#, Clojure, Dart, Erlang, Go, Haskell, Java, JavaScript, Kotlin, Lisp, Lua, OCaml, PHP, Python, R, Ruby, Rust, Scala, TypeScript
    • Có thể xem toàn bộ danh sách trong danh sách ngôn ngữ được hỗ trợ của manual
  • Ví dụ về các định dạng tệp được hỗ trợ gồm:
  • Có thể dùng cùng Git, và manual có hướng dẫn cấu hình Git
  • Difftastic được phát hành theo giấy phép MIT, cho phép tải xuống, chỉnh sửa và chia sẻ

1 bình luận

 
GN⁺ 2024-03-22
Ý kiến Hacker News
  • Có thể nhiều người đã biết, nhưng công cụ này được xây dựng trên tree-sitter(https://tree-sitter.github.io/tree-sitter/), và có thể xem như làm cho việc phân tích cú pháp những gì LSP đã làm cho việc phân tích
    Nó chuyển mã thành cây cú pháp trừu tượng (AST), rồi cung cấp một giao diện tiêu chuẩn để các client như trình soạn thảo hay công cụ diff có thể sử dụng AST đó
    Các công cụ kiểu này không cần tự hỗ trợ trực tiếp hàng chục ngôn ngữ; chỉ cần hỗ trợ tree-sitter là có thể tự động hoạt động với mọi ngôn ngữ mà tree-sitter hỗ trợ
    Nếu bạn phát triển một ngôn ngữ mới, chỉ cần tạo parser tree-sitter, khi đó mọi công cụ làm việc với tree-sitter đều có thể hỗ trợ ngôn ngữ đó
    Nhờ hai đổi mới lớn này, việc cải tiến các công cụ như vậy đang bùng nổ. Giờ đây trình soạn thảo, công cụ diff, v.v. có thể hỗ trợ từ hàng chục đến hàng trăm ngôn ngữ mà không phải lặp lại cùng một công việc, thật sự rất ấn tượng

    • Hoàn toàn đồng ý, và nếu nhắc lại bình luận tôi viết năm ngoái thì cũng đáng nhấn mạnh rằng tree-sitter có cấu trúc không phụ thuộc
      Tham khảo thêm, vài thành viên trong nhóm tôi đang bảo trì OCaml tree-sitter bindings và cũng thường xuyên đóng góp cho grammar như một phần công việc ở Semgrep. Semgrep dùng tree-sitter để tìm kiếm mã và để phân tích các truy vấn, vốn bản thân cũng là các đoạn mã, thành AST matcher
      Khi làm linter, bạn thường phải mang theo cả runtime của ngôn ngữ mục tiêu. Ví dụ, nếu trong Python bạn dùng module ast tích hợp để làm parser, thì phải khớp phiên bản ngôn ngữ và tính năng, nên Pylint chạy trên Python 2.7 không thể phân tích mã Python 3
      Khi nhắm đến nhiều ngôn ngữ, việc này trở nên phiền phức hơn rất nhiều so với tưởng tượng ban đầu
      Trước tree-sitter, nhiều khi lựa chọn tốt nhất là dùng công cụ AST tích hợp của ngôn ngữ vì có đảm bảo bám sát cú pháp mới nhất
      Cá nhân tôi thấy điểm thiên tài của tree-sitter là nó khiến việc giữ cho parser ngôn ngữ luôn cập nhật trở nên dễ hơn rất nhiều so với các phương pháp ngữ pháp truyền thống
      Nếu muốn hiểu thêm về các lựa chọn thiết kế của tree-sitter, tôi rất khuyến nghị bài nói chuyện của Max Brunsfield tại Strange Loop: https://www.youtube.com/watch?v=Jes3bD6P0To
      Nhờ đó, ngoài difftastic còn có nhiều công cụ mới khác được xây dựng trên tree-sitter như neovim, Zed, Semgrep và GitHub Code Search
    • Tôi đồng ý tree-sitter là một công cụ tuyệt vời, nhưng việc tự viết grammar có thể cực kỳ khó
      Tôi từng thử viết bộ grammar tree-sitter và tập truy vấn highlight cho VHDL, và gặp rất nhiều khó khăn khi biểu diễn ngữ pháp VHDL bằng tree-sitter
    • Tôi nghĩ mô tả này không chính xác. Không có giao diện chung kiểu nói được tree-sitter; mỗi parser tree-sitter xuất ra một cây cú pháp cụ thể khác nhau, chứ không phải một cây cú pháp trừu tượng tiêu chuẩn
      LSP thực sự giải quyết bài toán tích hợp M×N cần có giữa M trình soạn thảo và N ngôn ngữ bằng cách dùng một giao diện tiêu chuẩn cho các trình biên dịch hướng truy vấn
      tree-sitter không hẳn giải quyết bài toán đó, mà đúng hơn là giúp việc viết N tích hợp trong trình soạn thảo hay công cụ trở nên dễ hơn rất nhiều
    • Điều khiến tôi vướng nhất với tree-sitter là cách tiếp cận này không hiệu quả với nhiều ngôn ngữ tôi quan tâm
      Common Lisp không thể phân tích cú pháp nếu không có một triển khai Lisp đầy đủ, ngữ pháp Haskell đủ phức tạp để grammar bị không hoàn chỉnh, C/C++ thì chỉ riêng preprocessor cũng đã khiến việc phân tích chính xác trở nên khó khăn, còn phân tích Perl thì là bài toán Turing-complete
      Như những đề xuất từ nơi khác, có vẻ hợp lý hơn nếu định nghĩa một định dạng mà parser hiện có có thể sinh ra như đầu ra bổ sung, thay vì bắt một hệ sinh thái mới phải viết lại parser
    • Liệu có thể tạo grammar tree-sitter cho ngôn ngữ tự nhiên như tiếng Anh để gắn nhãn từng câu như một đơn vị câu không?
      Khi đó có lẽ difftastic có thể hiển thị thay đổi theo câu thay vì theo dòng hiển thị
      Với bài luận, diff theo dòng hiển thị khá kỳ lạ. Thường thì câu được thay đổi bắt đầu từ giữa một dòng hiển thị
  • Nếu cài bằng cargo, đây là cách cập nhật difftastic và các công cụ liên quan
    cargo install cargo-update
    cargo install-update --list
    cargo install-update --all
    Ngoài ra còn có vài dự án Rust thú vị có thể cài bằng cargo
    https://mise.jdx.dev/ mise-en-place là một lựa chọn thay thế nhanh và linh hoạt cho https://asdf-vm.com/
    https://github.com/ajeetdsouza/zoxide là một công cụ thay thế cd rất tuyệt, nó lưu lại những nơi bạn đã đến để chỉ cần khớp một phần như "z hel" là có thể chuyển tới "~/projects/helloworld"
    https://github.com/bootandy/dust là công cụ bổ sung cho du, cho bạn thấy thư mục nào đang chiếm nhiều dung lượng đĩa

    • Thêm ba công cụ gọn gàng nữa thì có https://github.com/eza-community/ezals đẹp mắt hơn, https://github.com/ClementTsang/bottomhtop có đồ thị, và https://github.com/sharkdp/batcat có tô sáng cú pháp
    • Tôi rất thích zoxide. Cũng đáng thêm lsd vào danh sách. Đây là một ls đẹp hơn
    • Tôi đã thử cài mise-en-place, và đúng là nó trông như thứ tôi luôn mong asdf sẽ trở thành
    • Trong các công cụ kiểu du mới, tôi thích dua-cli nhất. Đây là một UI terminal tương tác mô phỏng ncdu
      Tôi tìm thấy nó sau khi nhận ra ncdu không có chế độ sáng
    • ncdu vẫn là công cụ thay thế du tốt nhất, bỏ xa phần còn lại
  • Gần như tôi không hiểu nổi vì sao các công cụ như git lại không cung cấp thứ này làm mặc định
    Tôi đã dùng difft khoảng 1 năm, và điều phàn nàn lớn nhất là nó khiến việc quay lại các công cụ diff khác trong môi trường không có difft trở nên khó khăn
    Tôi cũng tò mò không biết đã có nghiên cứu nào về công cụ diff dựa trên ngữ nghĩa hay chưa. Ý tôi là những công cụ xử lý các trường hợp cú pháp thay đổi nhưng ý nghĩa vẫn như cũ
    Nghe có vẻ là một vấn đề khó nói chung, nhưng với các DSL nhỏ hoặc tập con của một số ngôn ngữ thì có thể khả thi hoặc hữu ích

    • Tôi nghĩ dùng diff kiểu cũ làm mặc định vẫn là đúng
      Nó đã có sẵn trên gần như mọi hệ thống có thể chạy git, nhanh, nhỏ gọn, và ai cũng biết cách dùng cơ bản
      Dù vậy, việc có thể dễ dàng đổi mặc định đó vẫn là điều tốt
    • Tôi đang làm https://semanticdiff.com/, nó phát hiện những thay đổi ngữ nghĩa cơ bản như đổi literal thập phân sang hệ thập lục phân hoặc đổi thứ tự khóa trong một đối tượng JSON
      Đây không phải tiện ích dòng lệnh mà là extension VS Code và GitHub App
      Nếu muốn biết cách nó hoạt động và khác gì với difftastic, có thể xem https://semanticdiff.com/blog/semanticdiff-vs-difftastic/
    • Difftastic là một công cụ hữu ích, nhưng theo kinh nghiệm của tôi thì nó quá chậm để trở thành lựa chọn mặc định trong một công cụ được dùng rộng rãi như git
    • Muốn làm được việc này thì công cụ diff về cơ bản sẽ trở thành một trình biên dịch
    • Tôi đang thử gắn difft vào git và khá thích
      Tuy vậy, một trong những lý do tôi nghĩ nó không nên là mặc định là vì nó ẩn khác biệt về khoảng trắng. Tôi vẫn chưa kiểm tra xem có cấu hình được không
  • Tôi hoàn toàn đồng cảm với việc hỏi liệu có đọc được cú pháp @@ -5,6 +5,7 @@ hay không, và với lời giải thích rằng Difftastic hiển thị số dòng thực tế của file trước và sau thay đổi
    Tôi cài ngay và thử git diff, nó hoạt động rất tốt

    • Không đọc được cú pháp đó sao?
  • Việc GitHub chưa đưa ra thứ như thế này trông giống một khoảng trống lớn về đổi mới sản phẩm
    Ngay cả khi chỉ đổi thụt lề thì cũng không có gì hỗ trợ, thường vẫn hiện thành một khối xóa lớn và thêm lớn
    Trình xem diff của GitHub có thể thông minh hơn và nên như vậy

    • GitHub không chỉ chưa làm được diff ngữ nghĩa mà còn chẳng xử lý tử tế cả nhận biết cú pháp
      Thực tế là nó còn không phân biệt được foo.cpp.in với foo.mk.in
      Bất kỳ foo.t nào cũng bị khai báo là Perl, và không có cách sửa. Còn có cả ticket từ 10 năm trước
    • tree-sitter được tối ưu cho hiệu năng khi dùng trong editor chứ không phải cho độ chính xác
      Thực tế, ngay cả các nhà phát triển cốt lõi của TS cũng khuyên đừng quá bận tâm đến độ chính xác của grammar[1]
      Với GitHub hay các dịch vụ ở vị thế tương tự, ràng buộc này có vẻ có thể là vấn đề chí mạng
      [1] https://github.com/tree-sitter/tree-sitter/issues/130#issuec...
    • GitHub có tùy chọn bỏ qua khoảng trắng trong diff
  • Tôi đã thử chuyển sang công cụ này, nhưng nó dùng định dạng lạ ngay cả ở những phần không thay đổi nên cảm giác quá ồn
    Cuối cùng tôi quay lại icdiff[0]
    [0]: https://github.com/jeffkaufman/icdiff

  • Trước đây tôi từng biết đến Semantic Merge[1], nhưng nó không phải mã nguồn mở
    Cái này không làm merge mà chỉ diff, nhưng ít ra là mã nguồn mở và phần diff nhìn đẹp hơn nhiều, nên tôi đã đổi nó thành mặc định rồi
    Tôi tò mò không biết có kế hoạch mở rộng sang tính năng merge hay không
    [1] https://docs.plasticscm.com/semanticmerge

    • Theo README trên GitHub thì difftastic không làm merge
      AST merge là một bài toán khó, và difftastic không xử lý phần đó
      Ngoài ra, AST diff là một quá trình có mất mát nếu nhìn từ góc độ text diff. difftastic bỏ qua khoảng trắng không có ý nghĩa về mặt cú pháp, nhưng merge thì cần theo dõi khoảng trắng
    • Tôi cũng định đề xuất Semantic Merge
      Hồi làm tái cấu trúc lộn xộn cho dự án C# trong một đội lớn, nó đúng là công cụ cứu mạng
  • Các thảo luận trước đây:
    https://news.ycombinator.com/item?id=27768861 (297 points | 3 năm trước | 61 bình luận)
    https://news.ycombinator.com/item?id=32746258 (698 points | 2 năm trước | 90 bình luận)
    https://news.ycombinator.com/item?id=30841244 (983 points | 2 năm trước | 219 bình luận)

  • Có vẻ là một công cụ tuyệt vời, nhưng khi tôi thử với vài file XML lớn thì các dòng được sửa hiện màu xanh lá bình thường, còn các thuộc tính được sửa lại hiện xanh lá đậm, nên rất khó phân biệt bằng mắt
    Tôi không tìm thấy trong tài liệu cách đổi kiểu diff hoặc đặt màu khác cho trường hợp in đậm
    Không biết có ai biết cách làm không

    • Tiếc là có vẻ hiện vẫn chưa hỗ trợ tùy biến màu sắc
      Tôi đã vào bình luận trong issue này[1] để ủng hộ tính năng đó
      Nếu điều bạn muốn nằm cùng nhóm tính năng thì có thể góp ý ở đó, còn nếu bạn thấy nó đủ khác thì chắc cũng nên mở issue mới
      [1]: https://github.com/Wilfred/difftastic/issues/611
  • Điều thú vị là gói Arch Linux có dung lượng 7MB, nhưng sau khi cài đặt thì bung ra thành 80MB, riêng binary difft đã là 78MB
    Trên dataset ZFS bật nén LZ4, du được báo là 17MB
    Mình thắc mắc vì sao thứ gì đó nén tốt đến vậy bên trong binary lại không được giữ ở trạng thái nén; có vẻ nếu giải nén trong RAM thì còn có thể tải nhanh hơn

    • Nhân nói về chuyện nén nên mình tò mò kiểm tra thử, trên btrfs zstd:1 thì khoảng 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
    • Có lẽ điều này liên quan tới bình luận nói rằng parser Nim là một file C 60MB không thể merge
      Tree-sitter có vẻ cần cực kỳ nhiều mã nguồn, và một phần đáng kể trong đó trông như bị trùng lặp hoặc có thể nén được
    • Tệp thực thi không phải được nạp toàn bộ vào RAM rồi mới bắt đầu chạy
      Nó được ánh xạ bộ nhớ và chỉ những phần thực sự được dùng mới được nạp khi cần
      Vì vậy nếu nén một binary lớn thì demand paging sẽ không hoạt động, thành ra còn chậm hơn
      Vài năm trước tôi đã thử với một file EXE Windows 280MB; tuy nén xuống được khoảng 70MB nhưng khởi động mất thêm vài giây so với bản gốc
      Trong một số kịch bản như chạy binary qua mạng thì điều này có thể hợp lý, nhưng nhìn chung binary không nén vẫn khởi động nhanh hơn nhiều