Merge pull request #370 from Yeegsing/translation

manual's translation of zh-CN version
pull/392/head
Wilfred Hughes 2022-09-23 22:55:43 +07:00 committed by GitHub
commit 4fa09c4cdf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1157 additions and 1 deletions

@ -96,7 +96,7 @@ favourite tool, and I will link it in the README!
## Translation
+ [Chinese](./translation/README-zh-CN.md)
+ [Chinese](./translation/zh-CN/README-zh-CN.md)
## License

@ -0,0 +1,20 @@
[book]
authors = ["Wilfred Hughes"]
language = "en"
multilingual = false
src = "src"
title = "Difftastic Manual"
description = "The official manual for difftastic, the syntactic differ"
[output.html]
git-repository-url = "https://github.com/wilfred/difftastic"
[output.html.redirect]
"/getting_started.html" = "./installation.html"
"/upstream_parsers.html" = "/languages_supported.html"
[output.html.playground]
copyable = false
[preprocessor.replace-version-placeholder]
command = "./replace_version_placeholder.sh"

@ -0,0 +1,5 @@
#!/bin/bash
DFT_VERSION=$(cargo read-manifest | jq -r .version)
jq .[1] | jq '.sections[0].Chapter.content |= sub("DFT_VERSION_HERE"; "'$DFT_VERSION'")'

@ -0,0 +1,17 @@
# 总结
- [简介](./introduction.md)
- [安装](./installation.md)
- [使用](./usage.md)
- [Git](./git.md)
- [Mercurial](./mercurial.md)
- [支持语言](./languages_supported.md)
- [解析](./parsing.md)
- [差异分析](./diffing.md)
- [棘手的例子](./tricky_cases.md)
- [贡献](./contributing.md)
- [解析器依赖库](./parser_vendoring.md)
- [添加解析器](./adding_a_parser.md)
- [词典](./glossary.md)
- [其它项目](./alternative_projects.md)
- [树状差异分析](./tree_diffing.md)

@ -0,0 +1,111 @@
# 添加解析器
## 寻找解析器
Difftastic的新解析器必须完整且合理地维护。
有许多解析器可用,网站包括[一些著名的解析器列表](https://tree-sitter.github.io/tree-sitter/#available-parsers)。
## 添加源码
一旦你找到一个解析器需要将其作为git的subtree添加到`vendor/`中。我们会使用[tree-sitter-json](https://github.com/tree-sitter/tree-sitter-json)作为例子。
```
$ git subtree add --prefix=vendor/tree-sitter-json git@github.com:tree-sitter/tree-sitter-json.git master
```
## 配置编译过程
Cargo不允许软件包包含`Cargo.toml`。需要在`src/`解析器子目录中添加一个符号链接。
```
$ cd vendor
$ ln -s tree-sitter-json/src tree-sitter-json-src
```
现在你可以通过在`build.rs`中加入目录,并将解析器添加到构建中。
```
TreeSitterParser {
name: "tree-sitter-json",
src_dir: "vendor/tree-sitter-json-src",
extra_files: vec![],
},
```
如果你的解析器包括用于语法的自定义C或C++文件(例如,一个`scanner.cc`),请将它添加到`extra_files`中。
## 配置解析器
为你的语言在`tree_sitter_parser.rs`中增加一个条目。
```
Json => {
let language = unsafe { tree_sitter_json() };
TreeSitterConfig {
name: "JSON",
language,
atom_nodes: vec!["string"].into_iter().collect(),
delimiter_tokens: vec![("{", "}"), ("[", "]")],
highlight_query: ts::Query::new(
language,
include_str!("../vendor/highlights/json.scm"),
)
.unwrap(),
}
}
```
`name`是用户节目中显示的可读名称。
`atom_nodes`是一个树形节点名称的列表,这些节点应被视为原子。即使这些节点有子节点,也应被视为原子。这对于字符串表面之或插值字符串非常常见的,因为在这种情况下,节点可能有用来表示开头和结尾的引用号。
如果你没有设置`atom_nodes`,你可能会主要添加/删除的内容显示为白色。这通常表面了子节点的父节点应该被当作原子。
`delimiter_tokens`是Difftastic存储在闭包节点上的定界符。这使得Difftastic能够区分划线符号和语言中的其它标点符号。
如果你不设置`delimiter_tokens`Difftastic会单独考虑这些标记并会认为是添加了`(`,但是`)`没有发生变化。
你可以使用`difft --dump-ts foo.json`来查看树状解析器的结果,并使用`difft --dump-syntax foo.json`来确认你已经正确设置了原子和定界符。
## 配置滑块
请为你的语言在`sliders.rs`中添加入口。
## 配置语言检测
更新`guess_language.rs`中的`from_extension`以检测新的语言。
```
"json" => Some(Json),
```
也可能有与你的语言相关的文件名或shebangs。[GitHub的语言定义](https://github.com/github/linguist/blob/master/lib/linguist/languages.yml)是针对常见文件扩展名的一个有用来源。
## 语法高亮(可选)
要为你的语言添加语法高亮,如果有的话,你还需要在`queries/highlights.scm`文件一个符号链接。
```
$ cd vendor/highlights
$ ln -s ../tree-sitter-json/queries/highlights.scm json.scm
```
## 添加一个回归测试
最后,为你的语言添加一个回归测试,这样可以确保你的测试文件的输出不会意外改变。
回归测试文件存在于`sample_files/`中,其形式为
`foo_before.abc`和`foo_after.abc`。
```
$ nano simple_before.json
$ nano simple_after.json
```
运行回归测试脚本并更新`.expect`文件。
```
$ ./sample_files/compare_all.sh
$ cp sample_files/compare.result sample_files/compare.expected
```

@ -0,0 +1,3 @@
# 其它项目
有许多不同的工具可以比较文件。本说明书的这一个部分讨论了其他影响到Difftastic的工具。

@ -0,0 +1,102 @@
# 贡献
## 构建
用[rustup](https://rustup.rs/)安装Rust然后克隆代码。
```
$ git clone git@github.com:Wilfred/difftastic.git
$ cd difftastic
```
Difftastic使用[Cargo](https://doc.rust-lang.org/cargo/)进行构建。
```
$ cargo build
```
调试构建的速度明显比发布构建的速度慢。对于超过50行的文件通常建议使用一个优化的构建。
```
$ cargo build --release
```
## Manual说明书
这个网站是用[mdbook](https://github.com/rust-lang/mdBook/)。mdbook可以用Cargo安装。
```
$ cargo install mdbook
```
然后你可以使用`mdbook`二进制文件来建立和在本地运行网站。
```
$ cd manual
$ mdbook serve
```
## API文档
你可以浏览由rustdoc生成的内部API文档[在这里](https://difftastic.wilfred.me.uk/rustdoc/difft/)。
Difftastic的内部文档在docs.rs上没有提供因为它[不支持二进制工具箱](https://difftastic.wilfred.me.uk/rustdoc/difft/)。
## 测试
```
$ cargo test
```
在`sample_files/`中也有几个文件你可以使用。
测试difftastic的最好方法是在真实项目查看历史。设置`GIT_EXTERNAL_DIFF`指向你当前的构建。
例如你可以在自己的源代码上运行Difftastic。
```
$ GIT_EXTERNAL_DIFF=./target/release/difft git log -p --ext-diff -- src
```
## 记录
Difftastic使用`pretty_env_logger`库来记录一些额外的调试信息。
```
$ RUST_LOG=debug cargo run sample_files/old.jsx sample_files/new.jsx
```
请参阅[`env_logger`](https://docs.rs/env_logger/0.9.0/env_logger/)以获得完整的细节。
## 调试
如果你有一个特别慢的文件,你可以使用 [cargo-flamegraph](https://github.com/flamegraph-rs/flamegraph) 来查看是哪些函数慢的。
```
$ CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph --bin difft sample_files/slow_before.rs sample_files/slow_after.rs
```
内存的使用情况也是值得关注,因为图的遍历错误会导致巨大的内存消耗。
```
$ /usr/bin/time -v ./target/release/difft sample_files/slow_before.rs sample_files/slow_after.rs
```
如果定时测量有噪音Linux的`perf`工具将报告 执行的指令,这也是更加稳定的。
```
$ perf stat ./target/release/difft sample_files/slow_before.rs sample_files/slow_after.rs
$ perf stat ./target/release/difft sample_files/typing_old.ml sample_files/typing_new.ml
```
还有很多剖析技术在[The Rust性能手册](https://nnethercote.github.io/perf-book/)中讨论了。
## 发布
使用Cargo创建一个新的版本并在git中标记它。Difftastic有一个帮助脚本
```
$ ./scripts/release.sh
```
现在你可以增加Cargo.toml中的版本并在
CHANGELOG.md加一个新的条目。

@ -0,0 +1,86 @@
# 差异分析
Difftastic将diff计算视作为有向无环图上的寻路问题。
## 图表示
图中的一个顶点代表两个语法树中的一个位置。
开始顶点的两个位置都指向两个树的第一个语法节点。结束顶点的两个位置都正好在两棵语法树的最后一个节点之后。
以`A`和`X A`比较为例:
```
START
+---------------------+
| Left: A Right: X A |
| ^ ^ |
+---------------------+
END
+---------------------+
| Left: A Right: X A |
| ^ ^|
+---------------------+
```
从起始顶点开始,我们有两个选择:
* 我们可以将左边的第一个语法节点标记为注意项并推进到左边的下一个语法节点即上面的顶点1
* 我们可以将右边的第一个语法节点标记为注意项并推进到右边的下一个语法节点上即上面的顶点2
```
START
+---------------------+
| Left: A Right: X A |
| ^ ^ |
+---------------------+
/ \
Novel atom L / \ Novel atom R
1 v 2 v
+---------------------+ +---------------------+
| Left: A Right: X A | | Left: A Right: X A |
| ^ ^ | | ^ ^ |
+---------------------+ +---------------------+
```
选择"新原子R"到顶点2将是最佳选择。从顶点2我们可以看到有三条路线通往终点。
```
2
+---------------------+
| Left: A Right: X A |
| ^ ^ |
+---------------------+
/ | \
Novel atom L / | \ Novel atom R
v | v
+---------------------+ | +---------------------+
| Left: A Right: X A | | | Left: A Right: X A |
| ^ ^ | | | ^ ^|
+---------------------+ | +---------------------+
| | |
| Novel atom R | Nodes match | Novel atom L
| | |
| END v |
| +---------------------+ |
+-------->| Left: A Right: X A |<---------+
| ^ ^|
+---------------------+
```
## 比较路线
我们给每条边分配一个成本。将一个语法节点标记为新奇,比找到一个匹配的语法节点更糟糕,因此"新奇原子"边的成本比"语法节点匹配"边更高。
最佳路线是指从起始顶点到终端顶点成本最低的路线。
## 寻找最佳路线
Difftastic使用Dijkstra算法来寻找最佳或称最低成本的路线。
这种算法的一大优势是,我们不需要事先构建图。相对于语法节点的数量,构建整个图需要指数级的内存。相反顶点的邻居是在探索图的过程中构建的。
网上有很多解释Dijkstra的算法但我特别推荐[Red
Blod Games的图搜索部分](https://www.redblobgames.com/pathfinding/a-star/introduction.html#dijkstra)。

@ -0,0 +1,62 @@
# Git
Git[支持使用外部的diff工具](https://git-scm.com/docs/diff-config#Documentation/diff-config.txt-diffexternal)。你可以使用`GIT_EXTERNEL_DIFF`来进行一键git命令。
```
$ GIT_EXTERNAL_DIFF=difft git diff
$ GIT_EXTERNAL_DIFF=difft git log -p --ext-diff
$ GIT_EXTERNAL_DIFF=difft git show e96a7241760319 --ext-diff
```
如果你想要默认使用Difftastic可以使用`git config`。
```
# Set git configuration for the current repository.
$ git config diff.external difft
# Set git configuration for all repositories.
$ git config --global diff.external difft
```
在运行`git config`后,`git diff`命令将会自动使用`difft`。其他情况则需要使用`--ext-diff`来使用`diff.externel`。
```
$ git diff
$ git log -p --ext-diff
$ git show e96a7241760319 --ext-diff
```
## git-difftool
[git difftool](https://git-scm.com/docs/git-difftool) 是一款使用不同diff工具来查看当前修改的git命令。如果你想要偶尔使用Difftastic的话这将会非常有用。
添加下列内容到你的`.gitconfig`中就会让Difftastic作为你的difftool工具。
```ini
[diff]
tool = difftastic
[difftool]
prompt = false
[difftool "difftastic"]
cmd = difft "$LOCAL" "$REMOTE"
```
然后你可以使用`git difftool`来用Difftastic查看当前修改。
```
$ git difftool
```
我们还推荐使用下列设置来获得最好的difftool体验。
```ini
# Use a pager for large output, just like other git commands.
[pager]
difftool = true
# `git dft` is less to type than `git difftool`.
[alias]
dft = difftool
```

@ -0,0 +1,20 @@
# 词典
**原子**: 原子是Difftastic语法树结构中的一个项目没有子项。它代表着字面量、变量名以及注释。 另见'list'。
**分隔符**: 即一个成对的语法。一个列表有一个开放定界符和一个封闭定界符,例如`[`和`]`。分隔符不可以是标点符号(例如,`begin`和`end`以及空字符串例如infix语法转换为Difftastic的语法树
**LHS**: 即Left-hand side。Difftastic会对比两个文件而LHS是指第一个文件。另见'RHS'。
**列表**: 列表是Difftastic语法树结构中的一个项目它有一个开放定界符、子项和一个封闭定界符。它代表表达式和函数定义等东西。另见'atom'。
**注意项**: 一个增加或一个减少。如果语法只出现在被比较的两个项目中的一个,那么它就是一个注意项。
**RHS**: 即Right-hand side。Difftastic会对比两个文件而RHS是指第一个文件。另见'LHS'。
**根**: 一个没有父节点的语法树。根代表被差异的文件中的顶级定义。
**语法节点**: Difftastic的语法树结构中的一个项目。可以是一个原子或一个列表。
**字符**: 一小段由Difftastic跟踪的语法例如`$x`,
`function``]`),用于高亮显示和对齐显示。它是原子或者是一个非空的分隔符。

@ -0,0 +1,51 @@
# 安装
## 从二进制安装
Difftastic以预先编译好的二进制的形式[提供Github realease](https://github.com/Wilfred/difftastic/releases) 。
在以下平台上也可以使用软件包。
[![Packaging status](https://repology.org/badge/vertical-allrepos/difftastic.svg)](https://repology.org/project/difftastic/versions)
## 通过homebrew安装在macos或者Linux平台
Difftastic可以用[Homebrew](https://formulae.brew.sh/formula/difftastic)安装在macOS或Linux上。
```
$ brew install difftastic
```
## 从源码安装
### 编译要求
Difftasitc是使用Rust编写的所以你需要安装Rust。我推荐使用[rustup](https://rustup.rs/)来安装Rust。
同时你也需要一个支持C++14的C++编译器。如果你正在使用GCC则GCC版本至少为8。
### Build编译
你可以下载并通过Cargo它是Rust的一部分来编译[difftastic on
crates.io](https://crates.io/crates/difftastic) 。
```
$ cargo install difftastic
```
Difftastic使用`cc`程序箱来构建C/C++的依赖关系。这使得你可以通过环境变量`CC`和`CXX`来控制使用的编译器参照see [the cc
docs](https://github.com/alexcrichton/cc-rs#external-configuration-via-environment-variables))。
参考[contributing](./contributing.md)来查看有关构建的说明。
## 可选安装MINE数据库
如果有一个MIME数据库Difftastic将使用它来更准确地检测二进制文件。这个也是使用`file`命令时所调用的同一个数据库,你可能已经安装了它。
MIME数据库的路径[是在XDG的规定下](https://specifications.freedesktop.org/shared-mime-info-spec/0.11/ar01s03.html)
* `/usr/share/mime/magic`
* `/usr/local/share/mime/magic`
* `$HOME/.local/share/mime/magic`

@ -0,0 +1,52 @@
# 简介
Difftastic是一个根据文件的语法的结构化比较工具。它支持[超过20款编程语言](./languages_supported.html),当使用它的时候,就会知道它有多么的*棒*。
Difftastic是一款开源软件使用MIT许可证并且可以[在Github上获得](https://github.com/wilfred/difftastic)。
该说明书会表明当前版本DFT_VERSION_HERE。[变更记录](https://github.com/Wilfred/difftastic/blob/master/CHANGELOG.md)会记录每个版本的特性增加和bug的修复。
## 语法差异分析
Difftastic会[检测编程语言](./usage.html#language-detection),爬取代码,随后比较句法树。见例子:
```
// old.rs
let ts_lang = guess(path, guess_src).map(tsp::from_language);
```
```
// new.rs
let ts_lang = language_override
.or_else(|| guess(path, guess_src))
.map(tsp::from_language);
```
<pre><code style="display:block">$ difft old.rs new.rs
1 <span style="background-color: PaleGreen">1</span> let ts_lang = <span style="background-color: PaleGreen">language_override</span>
. <span style="background-color: PaleGreen">2</span> <span style="background-color: PaleGreen">.or_else(||</span> guess(path, guess_src)<span style="background-color: PaleGreen">)</span>
. 3 .map(tsp::from_language);
</code>
</pre>
注意Difftastic是如何识别`.map`那段没有发生变化的,尽管它是在新的一行上以空格开头的。
如果是以前那种面对行的差异分析表现会不理想。
<pre><code style="display:block">$ diff -u old.rs new.rs
@@ -1 +1,3 @@
<span style="background-color: #fbbd98">-let ts_lang = guess(path, guess_src).map(tsp::from_language);</span>
<span style="background-color: PaleGreen">+let ts_lang = language_override
+ .or_else(|| guess(path, guess_src))
+ .map(tsp::from_language);</span>
</code>
</pre>
一些文本差异分析工具也会突出单词的变化例如GitHub或者是git的`--word-diff`但是它们无法做到理解代码本身。Difftastic永远会找到匹配的定界符你可以看到`or_else`结尾出的`)`已经被突出显示。
## 另一种文本差异分析
如果输入的文件格式Difftastic无法理解他就会使用传统的以行为单位的文本差异分析并且会将单词高亮显示。
同时当输入的文件较大时Difftastic也会使用以行为单位的文本差异分析。

@ -0,0 +1,55 @@
# 支持语言
本页列出了 difftastic 支持的所有语言。你也可以用`difft --list-languages`查看你当前安装的版本所支持的语言。
## 编程语言
| 语言 | 使用的解析器 |
|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Bash | [tree-sitter/tree-sitter-bash](https://github.com/tree-sitter/tree-sitter-bash) |
| C | [tree-sitter/tree-sitter-c](https://github.com/tree-sitter/tree-sitter-c) |
| C++ | [tree-sitter/tree-sitter-cpp](https://github.com/tree-sitter/tree-sitter-cpp) |
| C# | [tree-sitter/tree-sitter-c-sharp](https://github.com/tree-sitter/tree-sitter-c-sharp) |
| Clojure | [sogaiu/tree-sitter-clojure](https://github.com/sogaiu/tree-sitter-clojure) ([branched](https://github.com/sogaiu/tree-sitter-clojure/tree/issue-21)) |
| CMake | [uyha/tree-sitter-cmake](https://github.com/uyha/tree-sitter-cmake) |
| Common Lisp | [theHamsta/tree-sitter-commonlisp](https://github.com/theHamsta/tree-sitter-commonlisp) |
| Dart | [UserNobody14/tree-sitter-dart](https://github.com/UserNobody14/tree-sitter-dart) |
| Elixir | [elixir-lang/tree-sitter-elixir](https://github.com/elixir-lang/tree-sitter-elixir) |
| Elm | [elm-tooling/tree-sitter-elm](https://github.com/elm-tooling/tree-sitter-elm) |
| Elvish | [ckafi/tree-sitter-elvish](https://github.com/ckafi/tree-sitter-elvish) |
| Emacs Lisp | [wilfred/tree-sitter-elisp](https://github.com/Wilfred/tree-sitter-elisp) |
| Gleam | [gleam-lang/tree-sitter-gleam](https://github.com/gleam-lang/tree-sitter-gleam) |
| Go | [tree-sitter/tree-sitter-go](https://github.com/tree-sitter/tree-sitter-go) |
| Hack | [slackhq/tree-sitter-hack](https://github.com/slackhq/tree-sitter-hack) |
| Haskell | [tree-sitter/tree-sitter-haskell](https://github.com/tree-sitter/tree-sitter-haskell) |
| Janet | [sogaiu/tree-sitter-janet-simple](https://github.com/sogaiu/tree-sitter-janet-simple) |
| Java | [tree-sitter/tree-sitter-java](https://github.com/tree-sitter/tree-sitter-java) |
| JavaScript, JSX | [tree-sitter/tree-sitter-javascript](https://github.com/tree-sitter/tree-sitter-javascript) |
| Julia | [tree-sitter/tree-sitter-julia](https://github.com/tree-sitter/tree-sitter-julia) |
| Kotlin | [fwcd/tree-sitter-kotlin](https://github.com/fwcd/tree-sitter-kotlin) |
| Lua | [nvim-treesitter/tree-sitter-lua](https://github.com/nvim-treesitter/tree-sitter-lua) |
| Make | [alemuller/tree-sitter-make](https://github.com/alemuller/tree-sitter-make) |
| Nix | [cstrahan/tree-sitter-nix](https://github.com/cstrahan/tree-sitter-nix) |
| OCaml | [tree-sitter/tree-sitter-ocaml](https://github.com/tree-sitter/tree-sitter-ocaml) |
| Perl | [ganezdragon/tree-sitter-perl](https://github.com/ganezdragon/tree-sitter-perl) |
| PHP | [tree-sitter/tree-sitter-php](https://github.com/tree-sitter/tree-sitter-php) |
| Python | [tree-sitter/tree-sitter-python](https://github.com/tree-sitter/tree-sitter-python) |
| Ruby | [tree-sitter/tree-sitter-ruby](https://github.com/tree-sitter/tree-sitter-ruby) |
| Rust | [tree-sitter/tree-sitter-rust](https://github.com/tree-sitter/tree-sitter-rust) ([forked](https://github.com/Wilfred/tree-sitter-rust/tree/non_special_token)) |
| Scala | [tree-sitter/tree-sitter-scala](https://github.com/tree-sitter/tree-sitter-scala) |
| SQL | [m-novikov/tree-sitter-sql](https://github.com/m-novikov/tree-sitter-sql) |
| Swift | [alex-pinkus/tree-sitter-swift](https://github.com/alex-pinkus/tree-sitter-swift) |
| TypeScript, TSX | [tree-sitter/tree-sitter-typescript](https://github.com/tree-sitter/tree-sitter-typescript) |
| Zig | [maxxnino/tree-sitter-zig](https://github.com/maxxnino/tree-sitter-zig) |
## 结构化文本格式
| 语言 | 使用的解析器 |
|----------|-----------------------------------------------------------------------------------|
| CSS | [tree-sitter/tree-sitter-css](https://github.com/tree-sitter/tree-sitter-css) |
| HCL | [MichaHoffmann/tree-sitter-hcl](https://github.com/MichaHoffmann/tree-sitter-hcl) |
| HTML | [tree-sitter/tree-sitter-html](https://github.com/tree-sitter/tree-sitter-html) |
| JSON | [tree-sitter/tree-sitter-json](https://github.com/tree-sitter/tree-sitter-json) |
| TOML | [ikatyang/tree-sitter-toml](https://github.com/ikatyang/tree-sitter-toml) |
| YAML | [ikatyang/tree-sitter-yaml](https://github.com/ikatyang/tree-sitter-yaml) |

@ -0,0 +1,30 @@
# Mercurial
Mercurial[支持另外的diff工具](https://www.mercurial-scm.org/wiki/ExtdiffExtension)与Extdiff扩展。通过在你的`.hgrc`中添加`extensions`条目来启用它。
```
[extensions]
extdiff =
```
然后你可以运行`hg extdiff -p difft`命令(假定`difft`二进制文件存放在你的`$PATH`中。
你也可以为带有hg的difftastic的语句定义一个别名。在你的`.hgrc`中添加以下内容,以便用`hg dft`运行Difftastic。
```
[extdiff]
cmd.dft = difft
opts.dft = --missing-as-empty
```
## hg log -p
Mercurial没有办法改变默认的差异工具至少就作者所知。
如果你只想查看最近的一次提交的差异,你可以使用下面的方法。
```
GIT_PAGER_IN_USE=1 hg dft -r .^ -r . | less
```
这就等同于`hg log -l 1 -p`,尽管它不显示提交信息。

@ -0,0 +1,19 @@
# 包管理
## Git Subtrees
Tree-sitter有时被打包在npm上有时被打包在crates.io上并且它们的发布频率不一样。Difftastic使用git subtrees而不是git submodules来追踪解析器。
## 升级解析器
如果要更新解析器可以从上游的git仓库拉取提交。例如下面的命令将更新Java解析器
```
$ git subtree pull --prefix=vendor/tree-sitter-java git@github.com:tree-sitter/tree-sitter-java.git master
```
如果要查看每个解析器最后一次更新的时间请使用以下的Shell命令
```
$ for d in $(git log | grep git-subtree-dir | tr -d ' ' | cut -d ":" -f2 | sort); do echo "$d"; git log --pretty=" %cs" -n 1 $d; done
```

@ -0,0 +1,70 @@
# 解析代码
Difftastic会使用[tree-sitter](https://tree-sitter.github.io/tree-sitter/) 来建立一个语法树。然后,该语法树被转换为一个可以用来对比差异的简化版语法树。
## 使用Tree-sitter解析代码
Difftastic依靠tree-sitter来理解语法。你可以使用`--dump-ts`来查看tree-sitter的语法树。
```
$ difft --dump-ts sample_files/javascript_simple_before.js | head
program (0, 0) - (7, 0)
comment (0, 0) - (0, 8) "// hello"
expression_statement (1, 0) - (1, 6)
call_expression (1, 0) - (1, 5)
identifier (1, 0) - (1, 3) "foo"
arguments (1, 3) - (1, 5)
( (1, 3) - (1, 4) "("
) (1, 4) - (1, 5) ")"
; (1, 5) - (1, 6) ";"
expression_statement (2, 0) - (2, 6)
```
## 简化的语法
Difftastic将tree-sitter语法树转换为简化版的语法树。语法树是一种统一的表示方式其中所有东西都是原子例如整数、注释、变量名或者是一个列表由开放分界符、子句和关闭分界符组成以及分隔符。
`--dump-syntax`将显示出当前文件所对应的语法树。
```
$ difft --dump-syntax sample_files/before.js
[
Atom id:1 {
content: "// hello",
position: "0:0-8",
},
List id:2 {
open_content: "",
open_position: "1:0-0",
children: [
...
```
### 转换过程
Difftastic语法树的简单表达方式使得差异分析变得更加容易。Difftastic是通过一种递归树的行走方式来将tree-sitter树进行简化将tree-sitter的节点视作原子来处理。但有两个例外。
(1) Tree-sitter语法树有时会包括不需要的一些结构有些语法会认为字符串是一种单一的字符而有些则会将字符串视作为复杂的结构此时的分隔符就会将字符串分割开。
`tree-sitter_parser.rs`使用`atom_nodes`来标记特定的tree-sitter节点为平原子即使该节点存在子节点。
(2) Tree-sitter分析树包括开放和关闭定界符作为其代码。列表`[1]`将有一个包括`[`和`]`的节点的语法树。
```
$ echo '[1]' > example.js
$ difft --dump-ts example.js
program (0, 0) - (1, 0)
expression_statement (0, 0) - (0, 3)
array (0, 0) - (0, 3)
[ (0, 0) - (0, 1) "["
number (0, 1) - (0, 2) "1"
] (0, 2) - (0, 3) "]"
```
`tree_sitter_parser.rs`使用`open_delimiter_tokens`来确保`[`和`]`被用作包围列表内容的分隔符,而不会将其转换为原子。
Difftastic可以将出现简化语法树中不同部分的原子进行匹配。例如如果一个`[`被当作一个原子Difftastic可能会在其他地方将其与另一个`]`进行匹配。如果开放和关闭分界符的数量不同,最终的差异分析结果将会是不平衡的。
### Lossy Syntax Trees简化的语法树
简化的语法树只存储节点内容与节点的位置,不会存储节点之间的空白,而且在差异分析的过程中,空格将会被忽略。

@ -0,0 +1,84 @@
# 树状差异分析
本页总结了一些其他可用的树形差异分析工具。
如果你很着急可以先看看Autochrome。它的能力很强并且对设计有着很好的描述。
如果你对学术文献的摘要感兴趣,[这个帖子](http://useless-factor.blogspot.com/2008/01/matching-diffing-and-merging-xml.html)(和它[附带的论文](http://useless-factor.blogspot.com/2008/01/matching-diffing-and-merging-xml.html)--在CC BY-NC的许可下可以被复制将是一个很好的资源。
## json-diff (2012)
语言JSON
算法Pairwise comparison
输出CLI colours
[json-diff](https://github.com/andreyvit/json-diff)展示了JSON文件的结构层面的差异分析。如果两者是不完全匹配的那么它们的子树将是完全不同。例如`"foo"`和`["foo"]`是完全不同的。
可以注意的是json-diff的结果显示十分方便查看。
## GumTree (2014)
语言:[约有10种编程语言](https://github.com/GumTreeDiff/gumtree/wiki/Languages)
分析器:多种,包括 [srcML](https://www.srcml.org/)
算法Top-down随后bottom-up
输出HTMLSwing GUI或者text
[GumTree](https://github.com/GumTreeDiff/gumtree)可以分析多种编程语言并且进行基于树结构的差异分析输出一个HTML的结果界面。
GumTree算法在Falleri等人的相关论文《细粒度源码差异分析》中有所描述[DOI](http://doi.acm.org/10.1145/2642937.2642982),
[PDF](https://hal.archives-ouvertes.fr/hal-01054552/document))。它对相同的子树进行贪婪的自下而上的搜索,随后进行自下而上的搜索来匹配其余的子树。
## Tree Diff (2017)
语言S-表达式数据格式
算法A*搜索
输出合并后的S-表达式文件
Tristan Hume在他2017年实习期间和在Jane Street期间写了一个树状差分算法。源代码是不可以用的但是[他写了一篇博客](https://thume.ca/2017/06/17/tree-diffing/)来对该设计进行了深入讨论。
该项目找到了Jane Street用作配置文件的s-表达式文件之间的最小差异。它使用了A*搜索来找到他们之间最小的差异,兵建立一个具有`:date-switch`进行标记差异的新的s-表达式文件。
Jane Street一样有patdiff但那似乎是一个面向行的差异分析并不带着一些空格及整数差异显示。这个工具它并不理解在`"foo "`中的空格是具有意义的。)
## Autochrome (2017)
语言Clojure
分析器Custom并保留注释
算法Dijkstra算法A*搜索的先前版本)
输出HTML
[Autochrome](https://fazzone.github.io/autochrome.html)使用了一个定制的、保留注释的解析器来分析Clojure。Autochrome使用Dijkstra算法来比较语法树之间的差异。
Auto chrome的网页包括该算法的工作实例以及对该设计权衡的讨论。这是一个用来了解树形差异分析的重要资源。
## graphtage (2020)
语言JSON, XML, HTML, YAML, plist, and CSS
解析器json5, pyYAML, ignores comments
算法Levenshtein距离
输出CLI colours
[graphtage](https://blog.trailofbits.com/2020/08/28/graphtage/)通过将结构化数据解析为通用文件格式随后进行差异分析。它甚至允许比较JSON文件和YAML文件之间的区别。
与json-diff一样它不认为 `["foo"]`和`"foo"`之间有任何相似之处。
## Diffsitter (2020)
解析器:[Tree-sitter](https://tree-sitter.github.io/tree-sitter/)
算法LCSLongest-common-subsequence
输出CLI colours
[Diffsitter](https://github.com/afnanenayet/diffsitter)是另一个使用了tree-sitter解析器的差异分析工具。它使用了[LCS分析语法树中的子树](https://github.com/afnanenayet/diffsitter/blob/b0fd72612c6fcfdb8c061d3afa3bea2b0b754f33/src/ast.rs#L310-L313)。
## sdiff (2021)
语言Scheme
解析器Scheme内置的`read`,并忽略注释
算法Chawathe论文中的MH-Diff
输出CLI colours
[Semantically meaningful S-expression diff: Tree-diff for lisp source
code](https://archive.fosdem.org/2021/schedule/event/sexpressiondiff/)
在FOSDEM 2021中被发表。

@ -0,0 +1,330 @@
# 棘手的例子
在某些情况下,树状图的差异分析是具有挑战性的。本页展示了在开发过程中所观察到的困难情况。
并非所有这些情况在Difftastic中都能很好地工作。
## 添加定界符
```
;; Before
x
;; After
(x)
```
理想输出: <code><span style="background-color: PaleGreen">(</span>x<span style="background-color: PaleGreen">)</span></code>
这个是十分棘手,因为`x`已经改变了它在树中的深度,但`x`本身却未发生改变。
并不是所有的树形差异分析算法可以处理这个例子。同时仔细地展示出范例是具有挑战性的:我们希望去高亮出已改变的定界符,但不是他们的内容。这同样在更大的表达式是具有挑战性的。
## 改变定界符
```
;; Before
(x)
;; After
[x]
```
正如这个包裹的例子,我们想要去高亮出定界符而不是`x`这个内容。
## 拓展定界符
```
;; Before
(x) y
;; After
(x y)
```
理想输出:<code>(x <span style="background-color: PaleGreen">y</span>)</code>
在这个例子下,我们想要去高亮`y`。高亮显示定界符的话可能会让`x`看起来有所变化。
## 缩小定界符
```
;; Before
(x y)
;; After
(x) y
```
这应该与扩展定界符的情况类似,去高亮定界符。
## 使定界符不连贯
```
;; Before
(foo (bar))
;; After
(foo (novel) (bar))
```
理想输出:<code>(foo <span style="background-color:PaleGreen">(novel)</span> (bar)</code>
很容易会变成:<code>(foo (<span style="background-color:PaleGreen">novel</span>) <span style="background-color:PaleGreen">(</span>bar<span style="background-color:PaleGreen">)</span>)</code>,
其中后一组的定界符会被选中。
## 重新组织大节点
```
;; Before
[[foo]]
(x y)
;; After
([[foo]] x y)
```
我们想高亮`[[foo]]`被移到括号内了。然而,一个简单的语法差异者更倾向于认为在前面删除`()`,在后面增加`()`,是最小的差异表现。
(见[issue 44](https://github.com/Wilfred/difftastic/issues/44)。)
## 在列表内重新排列
```
;; Before
(x y)
;; After
(y x)
```
理想输出:<code>(<span style="background-color: PaleGreen">y</span> <span style="background-color: PaleGreen">x</span>)</code>
我们想突出显示列表的内容,而不是定界符。
## 中间插入
```
// Before
foo(bar(123))
// After
foo(extra(bar(123)))
```
理想输出:<code>foo(<span style="background-color: PaleGreen">extra(</span>bar(123)<span style="background-color: PaleGreen">)</span>)</code>
我们想把`foo`和`bar`都看作是不变的。这种情况对于对树进行自下而上然后自上而下匹配的衍合算法来说是具有挑战性的。
## 滑块(平移)
在基于文本的差异分析中,滑块是一个常见的问题,即行与行之间以混乱的方式进行匹配。
它们通常看起来像这样。差异分析必须任意选择一个包含分隔符的行,但它选择了错误的行。
```
+ }
+
+ function foo () {
}
```
git-diff有一些启发式方法来减少这种风险比如说"patience diff"),但这个问题仍然可能发生。
接下来是一个在树状差异分析时常见的问题。
```
;; Before
A B
C D
;; After
A B
A B
C D
```
理想情况下,我们更愿意将连续的节点标记为新的,所以我们强调`A B`而不是`B/nA`。从最长公序算法的角度来看,这两种选择是等价的。
## 滑块(嵌套)
```
// Before
old1(old2)
// After
old1(new1(old2))
```
这个应该是 <code>old1(<span style="background-color: PaleGreen">new1(</span>old2<span style="background-color: PaleGreen">)</span>)</code> 还是
<code>old1<span style="background-color: PaleGreen">(new1</span>(old2)<span style="background-color: PaleGreen">)</span></code>?
正确的答案是取决于语言。大多数语言希望优先使用内部分隔符而Lisps和JSON则喜欢使用外部分隔符。
## 最小化深度改变
```
// Before
if true {
foo(123);
}
foo(456);
// After
foo(789);
```
我们认为`foo(123)`还是`foo(456)`与`foo(789)`匹配?
Difftastic优先考虑`foo(456)`,通过优先考虑相同嵌套深度的节点。
## 有少量相似处的替代做法
```
// Before
function foo(x) { return x + 1; }
// After
function bar(y) { baz(y); }
```
在这个例子中,我们删除了一个函数,写了一个完全不同的函数。基于树状结构的差异可能会匹配 "函数 "和外部定界符,从而导致显示出许多令人困惑的小的变化。
与滑块一样,替换问题也可能发生在基于文本的行差中。如果有少量的共同行,行差就会陷入困境。但树形差分的更精确、更细化的行为使这个问题更加普遍。
## 匹配注释中的子字符串
```
// Before
/* The quick brown fox. */
foobar();
// After
/* The slow brown fox. */
foobaz();
```
`foobar`和`foobaz`是完全不同的,它们的共同前缀`fooba`不应该被匹配起来。然而,为注释匹配共同的前缀或后缀是可取的。
## 多行注释
```
// Before
/* Hello
* World. */
// After
if (x) {
/* Hello
* World. */
}
```
这两个注释的内部内容在技术上是不同的。然而,我们想把它们当作是相同的。
## 文档注释的换行
块状评论的前缀并没有什么意义。
```
// Before
/* The quick brown fox jumps
* over the lazy dog. */
// After
/* The quick brown fox immediately
* jumps over the lazy dog. */
```
里面的内容已经从 `jumps * over`变成了`immediately * jumps over`。然而,`*`是装饰性的,我们并不关心它的移动。
## 长字符串的小变化
```
// Before
"""A very long string
with lots of words about
lots of stuff."""
// After
"""A very long string
with lots of NOVEL words about
lots of stuff."""
```
将整个字符串字头突出显示为被删除并被一个新的字符串字头取代是正确的。然而,这让人很难看出实际改变了什么。
很明显,变量名应该被原子化处理,并且 注释是安全的可以显示子字的变化。但不清楚如何处理一个20行字符串字面的小变化。
在空格上分割字符串并加以区别是很具有挑战的,但用户仍然想知道字符串内部的空白何时改变。`" "`和`" "`是不一样的。
## 自动格式化工具的拼写
```
// Before
foo("looooong", "also looooong");
// Before
foo(
"looooong",
"novel",
"also looooong",
);
```
自动格式化(例如[prettier](https://prettier.io/))有时会在格式化时添加或删除标点符号。逗号和括号是最常见的。
语法差异可以忽略空白处的变化,但它必须假设标点符号是有意义的。这可能导致标点符号的变化被突出显示,而这可能与相关的内容变化相差甚远。
## 新空行
空行对于句法差异来说是一种挑战。我们要比较的是语法标记,所以我们不会看到空行。
```
// Before
A
B
// After
A
B
```
一般来说,我们希望语法差异能够忽略空行。在第一个例子中,这应该不会显示任何变化。
这有时是有问题的,因为它可以会意外地隐藏被重新格式化地代码。
```
// Before
A
B
// After
A
X
Y
B
```
在这第二个例子中我们插入了X和Y以及一个空行。我们想把空行作为一个补充来高亮。
```
// Before
A
B
// After
A
X
B
```
在这第三个例子中,语法上的差异只看到了一个增加。从用户的角度来看,也有两个空行被删除。
## 无效语法
我们不能保证我们得到的输入是有效的语法。即使代码是有效的,它也可能使用解析器不支持的语法。
Tree-sitter可以显示出显式的错误节点而Difftastic会将它们视为原子因此它可以不顾一切地运行相同的树形差异算法。

@ -0,0 +1,36 @@
# 使用方法
## 差异比较文件
```
$ difft sample_files/before.js sample_files/after.js
```
## 差异比较文件夹
```
$ difft sample_files/dir_before/ sample_files/dir_after/
```
Difftastic会递归地浏览这两个文件对同名的文件进行差异分析。
当对比的文件夹之间许多未改变的文件时,`--skip-unchanged`选项将会十分有用。
## 语言检测
Difftastic根据文件的扩展名、文件名和第一行的内容来猜测所使用的语言。
你可以通过`--language`选项来覆盖语言检测。如果输入的文件有所设定的后缀Difftastic将会处理它们并且忽略其他语言。
```
$ difft --language cpp before.c after.c
```
## Options选项
Difftastic包括一系列的命令行选项见`difft --help`获得完整列表。
Difftastic也可以用环境变量进行配置。这些可以在`--help`中看到。
例如,`DFT_BACKGROUND=light`就相当于`--background=light`。这在使用VCS工具例如git的时候会很有用因为此时无法直接调用`difft`二进制文件。