Difftastic, công cụ phân tích khác biệt có cấu trúc hiểu cú pháp
(difftastic.wilfred.me.uk)- 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:
- HCL, HTML, JSON, YAML
- Có thể xem toàn bộ danh sách trong danh sách định dạng văn bản có cấu trúc của manual
- 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
Ý 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
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
asttí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 3Khi 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 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
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
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
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-updatecargo install-update --listcargo install-update --allNgoà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ế
cdrấ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 đĩalsđẹp mắt hơn, https://github.com/ClementTsang/bottom làhtopcó đồ thị, và https://github.com/sharkdp/bat làcatcó tô sáng cú pháplsđẹp hơndumới, tôi thích dua-cli nhất. Đây là một UI terminal tương tác mô phỏngncduTôi tìm thấy nó sau khi nhận ra
ncdukhông có chế độ sángncduvẫn là công cụ thay thếdutốt nhất, bỏ xa phần còn lạiGầ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
diffkiểu cũ làm mặc định vẫn là đúngNó đã 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
Đâ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/
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 đổiTôi cài ngay và thử
git diff, nó hoạt động rất tốtViệ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
Thực tế là nó còn không phân biệt được
foo.cpp.invớifoo.mk.inBất kỳ
foo.tnà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ướcThự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...
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
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
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
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à 78MBTrên dataset ZFS bật nén LZ4,
duđược báo là 17MBMì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
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 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
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