Repository: dandavison/delta
Pull request #1185 (2022-09-12 09:28:08)

Fixed broken link to README
The "Configuration" chapter has been removed in 6f0bcbd504b397479eab97a5026ef96f3ad10eb3, so I replace the link.


Patch:
diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml
index 175c2c9..c80d811 100644
--- a/.github/workflows/audit.yml
+++ b/.github/workflows/audit.yml
@@ -15,7 +15,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout repository
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
       - uses: actions-rs/audit-check@v1
         with:
           token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index b5c8b04..1c8a906 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -12,31 +12,53 @@ jobs:
     strategy:
       matrix:
         job:
-          - { os: macos-latest,   target: x86_64-apple-darwin,         use-cross: false }
-          - { os: windows-latest, target: x86_64-pc-windows-msvc,      use-cross: false }
-          - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu,    use-cross: false }
-          - { os: ubuntu-latest,  target: x86_64-unknown-linux-musl,   use-cross: true }
-          - { os: ubuntu-latest,  target: i686-unknown-linux-gnu,      use-cross: true }
-          - { os: ubuntu-latest,  target: arm-unknown-linux-gnueabihf, use-cross: true }
-          - { os: ubuntu-latest,  target: aarch64-unknown-linux-gnu,   use-cross: true }
+          - os: macos-latest
+            target: x86_64-apple-darwin
+            use-cross: false
+          - os: macos-latest
+            target: aarch64-apple-darwin
+            use-cross: false
+          - os: windows-latest
+            target: x86_64-pc-windows-msvc
+            use-cross: false
+          - os: ubuntu-latest
+            target: x86_64-unknown-linux-gnu
+            use-cross: false
+          - os: ubuntu-latest
+            target: x86_64-unknown-linux-musl
+            use-cross: true
+          - os: ubuntu-latest
+            target: i686-unknown-linux-gnu
+            use-cross: true
+          - os: ubuntu-latest
+            target: arm-unknown-linux-gnueabihf
+            use-cross: true
+          - os: ubuntu-latest
+            target: aarch64-unknown-linux-gnu
+            use-cross: true
 
     steps:
       - name: Installing Rust toolchain
-        uses: actions-rs/toolchain@v1
+        uses: dtolnay/rust-toolchain@stable
         with:
-          toolchain: stable
-          profile: minimal
-          override: true
+          targets: ${{ matrix.job.target }}
       - name: Checkout repository
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
+      - name: Install cross
+        if: matrix.job.use-cross
+        uses: taiki-e/install-action@v2
+        with:
+          tool: cross
       - name: Cargo build
         env:
           MACOSX_DEPLOYMENT_TARGET: 10.7
-        uses: actions-rs/cargo@v1
-        with:
-          command: build
-          use-cross: ${{ matrix.job.use-cross }}
-          args: --release --target ${{ matrix.job.target }}
+        shell: bash
+        run: |
+          if [[ "${{ matrix.job.cross }}" == "true" ]]; then
+            cross build --release --target ${{ matrix.job.target }}
+          else
+            cargo build --release --target ${{ matrix.job.target }}
+          fi
 
       - name: Install required dependencies
         shell: bash
@@ -72,22 +94,15 @@ jobs:
     name: Publishing to Cargo
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@master
-      - uses: actions-rs/toolchain@v1
-        with:
-          toolchain: stable
-          profile: minimal
-          override: true
-      - uses: actions-rs/cargo@v1
-        with:
-          command: publish
-          args: --token ${{ secrets.CARGO_API_KEY }} --allow-dirty
-          
+      - uses: actions/checkout@v3
+      - uses: dtolnay/rust-toolchain@stable
+      - run: cargo publish --token ${{ secrets.CARGO_API_KEY }} --allow-dirty
+
   bump-homebrew-formula:
     runs-on: macos-latest
     steps:
       - name: Checkout repository
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
       - name: Bump Homebrew formulae
         uses: dawidd6/action-homebrew-bump-formula@v3
         with:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c78ae78..e257f89 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,29 +14,47 @@ jobs:
     strategy:
       matrix:
         job:
-          - { os: macos-latest,   target: x86_64-apple-darwin,         use-cross: false }
-          - { os: windows-latest, target: x86_64-pc-windows-msvc,      use-cross: false }
-          - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu,    use-cross: false }
-          - { os: ubuntu-latest,  target: x86_64-unknown-linux-musl,   use-cross: true }
-          - { os: ubuntu-latest,  target: i686-unknown-linux-gnu,      use-cross: true }
-          - { os: ubuntu-latest,  target: arm-unknown-linux-gnueabihf, use-cross: true }
-          - { os: ubuntu-latest,  target: aarch64-unknown-linux-gnu,   use-cross: true }
+          - os: macos-latest
+            target: x86_64-apple-darwin
+            use-cross: false
+          - os: windows-latest
+            target: x86_64-pc-windows-msvc
+            use-cross: false
+          - os: ubuntu-latest
+            target: x86_64-unknown-linux-gnu
+            use-cross: false
+          - os: ubuntu-latest
+            target: x86_64-unknown-linux-musl
+            use-cross: true
+          - os: ubuntu-latest
+            target: i686-unknown-linux-gnu
+            use-cross: true
+          - os: ubuntu-latest
+            target: arm-unknown-linux-gnueabihf
+            use-cross: true
+          - os: ubuntu-latest
+            target: aarch64-unknown-linux-gnu
+            use-cross: true
     steps:
       - name: Checkout repository
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
       - name: Install Rust
-        uses: actions-rs/toolchain@v1
+        uses: dtolnay/rust-toolchain@stable
         with:
-          toolchain: stable
-          target: ${{ matrix.job.target }}
-          profile: minimal
-          override: true
-      - name: Unit tests
-        uses: actions-rs/cargo@v1
+          targets: ${{ matrix.job.target }}
+      - name: Install cross
+        if: matrix.job.use-cross
+        uses: taiki-e/install-action@v2
         with:
-          command: test
-          use-cross: ${{ matrix.job.use-cross }}
-          args: --target ${{ matrix.job.target }} --verbose -- --nocapture
+          tool: cross
+      - name: Unit tests
+        shell: bash
+        run: |
+          if [[ "${{ matrix.job.use-cross }}" == "true" ]]; then
+            cross test --target ${{ matrix.job.target }} --verbose -- --nocapture
+          else
+            cargo test --target ${{ matrix.job.target }} --verbose -- --nocapture
+          fi
 
   integration_tests:
     name: Integration tests
@@ -51,18 +69,11 @@ jobs:
             target: x86_64-unknown-linux-gnu
     steps:
       - name: Checkout repository
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
       - name: Install Rust
-        uses: actions-rs/toolchain@v1
-        with:
-          toolchain: stable
-          profile: minimal
-          override: true
+        uses: dtolnay/rust-toolchain@stable
       - name: Build for release
-        uses: actions-rs/cargo@v1
-        with:
-          command: build
-          args: --target ${{ matrix.target }} --release
+        run: cargo build --target ${{ matrix.target }} --release
       - name: End to end tests
         run: |
           DELTA_BIN=target/${{ matrix.target }}/release/delta
@@ -76,54 +87,41 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout repository
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
       - name: Install Rust
-        uses: actions-rs/toolchain@v1
+        uses: dtolnay/rust-toolchain@stable
         with:
-          toolchain: stable
-          profile: minimal
-          override: true
           components: rustfmt
       - name: Check formatting
-        uses: actions-rs/cargo@v1
-        with:
-          command: fmt
-          args: --all -- --check
+        run: cargo fmt --all --check
 
   clippy:
     name: Clippy
     runs-on: ubuntu-latest
     steps:
       - name: Checkout repository
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
       - name: Install Rust
-        uses: actions-rs/toolchain@v1
+        uses: dtolnay/rust-toolchain@stable
         with:
-          toolchain: stable
-          profile: minimal
-          override: true
           components: clippy
       - name: Clippy Check
-        uses: actions-rs/cargo@v1
-        with:
-          command: clippy
-          args: -- -D warnings
+        run: cargo clippy -- -D warnings
 
   coverage:
     name: Code coverage
     runs-on: ubuntu-latest
     steps:
       - name: Checkout repository
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
       - name: Install stable toolchain
-        uses: actions-rs/toolchain@v1
+        uses: dtolnay/rust-toolchain@stable
+      - name: Install cargo-tarpaulin
+        uses: taiki-e/install-action@v2
         with:
-          toolchain: stable
-          override: true
+          tool: cargo-tarpaulin
       - name: Run cargo-tarpaulin
-        uses: actions-rs/tarpaulin@v0.1
-        with:
-          args: '--ignore-tests --out Lcov --output-dir target/tarpaulin'
+        run: cargo tarpaulin --ignore-tests --out Lcov --output-dir target/tarpaulin
       - name: Upload to Coveralls
         # upload only if push
         if: ${{ github.event_name == 'push' }}
diff --git a/.github/workflows/manual.yml b/.github/workflows/manual.yml
index 09afae0..d4bf570 100644
--- a/.github/workflows/manual.yml
+++ b/.github/workflows/manual.yml
@@ -11,16 +11,10 @@ jobs:
     concurrency:
       group: ${{ github.workflow }}-${{ github.ref }}
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
 
-      - uses: actions-rs/toolchain@v1
-        with:
-          profile: minimal
-          toolchain: stable
-          override: true
-      - uses: actions-rs/cargo@v1
-        with:
-          command: doc
+      - uses: dtolnay/rust-toolchain@stable
+      - run: cargo doc
 
       - name: Setup mdBook
         uses: peaceiris/actions-mdbook@v1
diff --git a/Cargo.lock b/Cargo.lock
index 087de87..f411266 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -81,32 +81,22 @@ checksum = "fd1212d80800b3d7614b3725e0b2ee3b45b2b7484805d54b5660c8fa6f706305"
 dependencies = [
  "ansi_colours",
  "ansi_term",
- "atty",
  "bincode",
- "bugreport",
  "bytesize",
- "clap 2.34.0",
  "clircle",
  "console",
  "content_inspector",
- "dirs-next",
  "encoding",
  "flate2",
- "git2",
  "globset",
- "grep-cli",
  "once_cell",
  "path_abs",
- "regex",
  "semver",
  "serde",
  "serde_yaml",
- "shell-words",
  "syntect",
  "thiserror",
  "unicode-width",
- "walkdir",
- "wild",
 ]
 
 [[package]]
@@ -142,17 +132,6 @@ dependencies = [
  "regex-automata",
 ]
 
-[[package]]
-name = "bugreport"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "535120b8182547808081a66f1f77a64533c780b23da26763e0ee34dfb94f98c9"
-dependencies = [
- "git-version",
- "shell-escape",
- "sys-info",
-]
-
 [[package]]
 name = "bytelines"
 version = "2.4.0"
@@ -230,22 +209,6 @@ dependencies = [
  "chrono",
 ]
 
-[[package]]
-name = "clap"
-version = "2.34.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
-dependencies = [
- "ansi_term",
- "atty",
- "bitflags",
- "strsim 0.8.0",
- "term_size",
- "textwrap 0.11.0",
- "unicode-width",
- "vec_map",
-]
-
 [[package]]
 name = "clap"
 version = "3.2.8"
@@ -258,9 +221,9 @@ dependencies = [
  "clap_lex",
  "indexmap",
  "once_cell",
- "strsim 0.10.0",
+ "strsim",
  "termcolor",
- "textwrap 0.15.0",
+ "textwrap",
 ]
 
 [[package]]
@@ -622,7 +585,7 @@ dependencies = [
 
 [[package]]
 name = "git-delta"
-version = "0.14.0"
+version = "0.15.1"
 dependencies = [
  "ansi_colours",
  "ansi_term",
@@ -633,7 +596,7 @@ dependencies = [
  "bytelines",
  "chrono",
  "chrono-humanize",
- "clap 3.2.8",
+ "clap",
  "console",
  "ctrlc",
  "dirs-next",
@@ -657,28 +620,6 @@ dependencies = [
  "xdg",
 ]
 
-[[package]]
-name = "git-version"
-version = "0.3.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6b0decc02f4636b9ccad390dcbe77b722a77efedfa393caf8379a51d5c61899"
-dependencies = [
- "git-version-macro",
- "proc-macro-hack",
-]
-
-[[package]]
-name = "git-version-macro"
-version = "0.3.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe69f1cbdb6e28af2bac214e943b99ce8a0a06b447d15d3e61161b0423139f3f"
-dependencies = [
- "proc-macro-hack",
- "proc-macro2",
- "quote",
- "syn",
-]
-
 [[package]]
 name = "git2"
 version = "0.14.2"
@@ -692,12 +633,6 @@ dependencies = [
  "url",
 ]
 
-[[package]]
-name = "glob"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
-
 [[package]]
 name = "globset"
 version = "0.4.8"
@@ -1313,12 +1248,6 @@ dependencies = [
  "yaml-rust",
 ]
 
-[[package]]
-name = "shell-escape"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f"
-
 [[package]]
 name = "shell-words"
 version = "1.1.0"
@@ -1352,12 +1281,6 @@ version = "0.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8207e78455ffdf55661170876f88daf85356e4edd54e0a3dbc79586ca1e50cbe"
 
-[[package]]
-name = "strsim"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
-
 [[package]]
 name = "strsim"
 version = "0.10.0"
@@ -1398,16 +1321,6 @@ dependencies = [
  "yaml-rust",
 ]
 
-[[package]]
-name = "sys-info"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b3a0d0aba8bf96a0e1ddfdc352fc53b3df7f39318c71854910c3c4b024ae52c"
-dependencies = [
- "cc",
- "libc",
-]
-
 [[package]]
 name = "sysinfo"
 version = "0.24.5"
@@ -1422,16 +1335,6 @@ dependencies = [
  "winapi",
 ]
 
-[[package]]
-name = "term_size"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9"
-dependencies = [
- "libc",
- "winapi",
-]
-
 [[package]]
 name = "termcolor"
 version = "1.1.2"
@@ -1451,16 +1354,6 @@ dependencies = [
  "winapi",
 ]
 
-[[package]]
-name = "textwrap"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
-dependencies = [
- "term_size",
- "unicode-width",
-]
-
 [[package]]
 name = "textwrap"
 version = "0.15.0"
@@ -1604,12 +1497,6 @@ version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
 
-[[package]]
-name = "vec_map"
-version = "0.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
-
 [[package]]
 name = "version_check"
 version = "0.9.2"
@@ -1660,15 +1547,6 @@ version = "0.10.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
 
-[[package]]
-name = "wild"
-version = "2.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "035793abb854745033f01a07647a79831eba29ec0be377205f2a25b0aa830020"
-dependencies = [
- "glob",
-]
-
 [[package]]
 name = "winapi"
 version = "0.3.9"
diff --git a/Cargo.toml b/Cargo.toml
index 24f762f..46fd969 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,14 +8,14 @@ edition = "2018"
 homepage = "https://github.com/dandavison/delta"
 license = "MIT"
 repository = "https://github.com/dandavison/delta"
-version = "0.14.0"
+version = "0.15.1"
 
 [[bin]]
 name = "delta"
 path = "src/main.rs"
 
 [dependencies]
-bat = "0.21.0"
+bat = { version = "0.21.0", default-features = false, features = ["regex-onig"] }
 chrono = "0.4.19"
 chrono-humanize = "0.2.1"
 ansi_colours = "1.0.4"
diff --git a/HomeBrewFormula/git-delta.rb b/HomeBrewFormula/git-delta.rb
index da7b686..e1e6a3b 100644
--- a/HomeBrewFormula/git-delta.rb
+++ b/HomeBrewFormula/git-delta.rb
@@ -1,5 +1,5 @@
 class GitDelta < Formula
-  version "0.14.0"
+  version "0.15.0"
   desc "A viewer for git and diff output"
   homepage "https://github.com/dandavison/delta"
 
@@ -7,10 +7,10 @@ class GitDelta < Formula
 
   if OS.mac?
       url "https://github.com/dandavison/delta/releases/download/#{version}/delta-#{version}-x86_64-apple-darwin.tar.gz"
-      sha256 "7dfa3b25c793de6cf58316c3c3aa90cdd4f856da837c35775e1e49b753deba53"
+      sha256 "ca97e1216f30f31ca08e4dafa879e03aa8d0f5761d851c47b2c42295953e045f"
   elsif OS.linux?
       url "https://github.com/dandavison/delta/releases/download/#{version}/delta-#{version}-x86_64-unknown-linux-musl.tar.gz"
-      sha256 "ecfbc3e4508646ec985fbcb5a3a89480f25f0c044d02b2308c2053aba8f42ef9"
+      sha256 "0887113c9f49ccc93d8afaa5cf0e33b8d95c5d5183a4552c5899b1c36c6ec520"
   end
 
   conflicts_with "delta"
diff --git a/README.md b/README.md
index 6ac1a81..9582598 100644
--- a/README.md
+++ b/README.md
@@ -23,8 +23,6 @@
 
 [interactive]
     diffFilter = delta --color-only
-[add.interactive]
-    useBuiltin = false # required for git 2.37.0
 
 [delta]
     navigate = true    # use n and N to move between diff sections
@@ -47,12 +45,12 @@ Delta has many features and is very customizable; please see the [user manual](h
 - Line numbering
 - `n` and `N` keybindings to move between files in large diffs, and between diffs in `log -p` views (`--navigate`)
 - Improved merge conflict display
-- Improved `git blame` display (syntax highlighting; `--hyperlinks` formats commits as links to GitHub/GitLab/Bitbucket etc)
+- Improved `git blame` display (syntax highlighting; `--hyperlinks` formats commits as links to hosting provider etc. Supported hosting providers are: GitHub, GitLab, SourceHut, Codeberg)
 - Syntax-highlights grep output from `rg`, `git grep`, `grep`, etc
 - Support for Git's `--color-moved` feature.
 - Code can be copied directly from the diff (`-/+` markers are removed by default).
 - `diff-highlight` and `diff-so-fancy` emulation modes
-- Commit hashes can be formatted as terminal [hyperlinks](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda) to the GitHub/GitLab/Bitbucket page (`--hyperlinks`).
+- Commit hashes can be formatted as terminal [hyperlinks](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda) to the hosting provider page (`--hyperlinks`).
   File paths can also be formatted as hyperlinks for opening in your OS.
 - Stylable box/line decorations to draw attention to commit, file and hunk header sections.
 - Style strings (foreground color, background color, font attributes) are supported for >20 stylable elements, using the same color/style language as git
diff --git a/etc/ci/before_deploy.sh b/etc/ci/before_deploy.sh
index 8e188ed..b80b339 100755
--- a/etc/ci/before_deploy.sh
+++ b/etc/ci/before_deploy.sh
@@ -156,14 +156,13 @@ Priority: optional
 Maintainer: Dan Davison <dandavison7@gmail.com>
 Architecture: $architecture
 Depends: $depends
-Provides: $PROJECT_NAME
 Conflicts: $conflictname
-Description: Syntax highlighter for git.
+Description: Syntax highlighter for git
  Delta provides language syntax-highlighting, within-line insertion/deletion
  detection, and restructured diff output for git on the command line.
 EOF
 
-    fakeroot dpkg-deb --build "$tempdir" "${dpkgname}_${version}_${architecture}.deb"
+    fakeroot dpkg-deb -Zxz --build "$tempdir" "${dpkgname}_${version}_${architecture}.deb"
 }
 
 
diff --git a/etc/release.Makefile b/etc/release.Makefile
index dc60173..747160e 100644
--- a/etc/release.Makefile
+++ b/etc/release.Makefile
@@ -48,6 +48,8 @@ BUMP_VERSION_IN_DOCUMENTATION_LINKS_SENTINEL=.make-sentinels/bump-version-in-doc
 bump-version-in-documentation-links: $(BUMP_VERSION_IN_DOCUMENTATION_LINKS_SENTINEL)
 $(BUMP_VERSION_IN_DOCUMENTATION_LINKS_SENTINEL):
 	sed -i -E "s,$$DELTA_OLD_VERSION,$$DELTA_NEW_VERSION,g" manual/src/full---help-output.md manual/src/installation.md
+	rg -qF "$$DELTA_NEW_VERSION" manual/src/full---help-output.md
+	rg -qF "$$DELTA_NEW_VERSION" manual/src/installation.md
 	git add manual/src/full---help-output.md manual/src/installation.md
 	git commit -m "Bump version in links to executables"
 	touch $(BUMP_VERSION_IN_DOCUMENTATION_LINKS_SENTINEL)
diff --git a/manual/src/full---help-output.md b/manual/src/full---help-output.md
index cf105e1..10d48a8 100644
--- a/manual/src/full---help-output.md
+++ b/manual/src/full---help-output.md
@@ -1,7 +1,7 @@
 # Full --help output
 
 ```
-delta 0.14.0
+delta 0.15.0
 A viewer for git and diff output
 
 USAGE:
diff --git a/manual/src/grep.md b/manual/src/grep.md
index 7f18708..cd1e129 100644
--- a/manual/src/grep.md
+++ b/manual/src/grep.md
@@ -1,8 +1,6 @@
 # Grep
 
-Delta applies syntax-highlighting and other enhancements to standard grep output such as from `git grep`, [ripgrep](https://github.com/BurntSushi/ripgrep/) (aka `rg`), grep, etc.
-To use with `git grep`, set delta as the pager for `grep` in the `[pager]` section of your gitconfig. See the example at the [top of the page](./get-started.md).
-Output from other grep tools can be piped to delta: e.g. `rg -Hn --color=always`, `grep -Hn --color=always`, etc.
+Delta applies syntax-highlighting and other enhancements to standard grep output such as from [ripgrep](https://github.com/BurntSushi/ripgrep/) (aka `rg`), `git grep`, grep, etc.
+If you don't need special features of `git grep`, then for best results pipe `rg --json` output to delta: this avoids parsing ambiguities that are inevitable with the output of `git grep` and `grep`.
 To customize the colors and syntax highlighting, see `grep-match-line-style`, `grep-match-word-style`, `grep-context-line-style`, `grep-file-style`, `grep-line-number-style`.
-Ripgrep's `rg --json` output format is supported; this avoids certain file name parsing ambiguities that are inevitable with the standard grep output formats.
 Note that `git grep` can display the "function context" for matches and that delta handles this output specially: see the `-p` and `-W` options of `git grep`.
diff --git a/manual/src/installation.md b/manual/src/installation.md
index 7123005..6c72269 100644
--- a/manual/src/installation.md
+++ b/manual/src/installation.md
@@ -1,13 +1,13 @@
 # Installation
 
 You can download an executable for your system:
-[Linux (glibc)](https://github.com/dandavison/delta/releases/download/0.12.1/delta-0.12.1-x86_64-unknown-linux-gnu.tar.gz)
+[Linux (glibc)](https://github.com/dandavison/delta/releases/download/0.15.0/delta-0.15.0-x86_64-unknown-linux-gnu.tar.gz)
 |
-[Linux (musl)](https://github.com/dandavison/delta/releases/download/0.12.1/delta-0.12.1-x86_64-unknown-linux-musl.tar.gz)
+[Linux (musl)](https://github.com/dandavison/delta/releases/download/0.15.0/delta-0.15.0-x86_64-unknown-linux-musl.tar.gz)
 |
-[MacOS](https://github.com/dandavison/delta/releases/download/0.12.1/delta-0.12.1-x86_64-apple-darwin.tar.gz)
+[MacOS](https://github.com/dandavison/delta/releases/download/0.15.0/delta-0.15.0-x86_64-apple-darwin.tar.gz)
 |
-[Windows](https://github.com/dandavison/delta/releases/download/0.12.1/delta-0.12.1-x86_64-pc-windows-msvc.zip)
+[Windows](https://github.com/dandavison/delta/releases/download/0.15.0/delta-0.15.0-x86_64-pc-windows-msvc.zip)
 |
 [All](https://github.com/dandavison/delta/releases)
 
diff --git a/manual/src/side-by-side-view.md b/manual/src/side-by-side-view.md
index 3e6cb70..51802aa 100644
--- a/manual/src/side-by-side-view.md
+++ b/manual/src/side-by-side-view.md
@@ -9,6 +9,13 @@ By default, side-by-side view has line-numbers activated, and has syntax highlig
 
 <table><tr><td><img width=800px src="https://user-images.githubusercontent.com/52205/87230973-412eb900-c381-11ea-8aec-cc200290bd1b.png" alt="image" /></td></tr></table>
 
+To activate and deactivate side-by-side view from the command line, consider using the [`DELTA_FEATURES`](./features-named-groups-of-settings.md) environment variable. For example:
+
+```
+export DELTA_FEATURES=+side-by-side # activate
+export DELTA_FEATURES=+             # deactivate
+```
+
 To disable the line numbers in side-by-side view, but keep a vertical delimiter line between the left and right panels, use the line-numbers format options. For example:
 
 ```gitconfig
diff --git a/src/align.rs b/src/align.rs
index 92262cb..90491ce 100644
--- a/src/align.rs
+++ b/src/align.rs
@@ -1,14 +1,14 @@
 use std::cmp::max;
 use std::collections::VecDeque;
 
-const SUBSTITUTION_COST: usize = 1;
-const DELETION_COST: usize = 1;
-const INSERTION_COST: usize = 1;
+const DELETION_COST: usize = 2;
+const INSERTION_COST: usize = 2;
+// extra cost for starting a new group of changed tokens
+const INITIAL_MISMATCH_PENALITY: usize = 1;
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum Operation {
     NoOp,
-    Substitution,
     Deletion,
     Insertion,
 }
@@ -61,14 +61,14 @@ impl<'a> Alignment<'a> {
             self.table[i] = Cell {
                 parent: 0,
                 operation: Deletion,
-                cost: i,
+                cost: i * DELETION_COST + INITIAL_MISMATCH_PENALITY,
             };
         }
         for j in 1..self.dim[0] {
             self.table[j * self.dim[1]] = Cell {
                 parent: 0,
                 operation: Insertion,
-                cost: j,
+                cost: j * INSERTION_COST + INITIAL_MISMATCH_PENALITY,
             };
         }
 
@@ -76,22 +76,32 @@ impl<'a> Alignment<'a> {
             for (j, y_j) in self.y.iter().enumerate() {
                 let (left, diag, up) =
                     (self.index(i, j + 1), self.index(i, j), self.index(i + 1, j));
+                // The order of the candidates matters if two of them have the
+                // same cost as in that case we choose the first one. Consider
+                // insertions and deletions before matches in order to group
+                // changes together. Insertions are preferred to deletions in
+                // order to highlight moved tokens as a deletion followed by an
+                // insertion (as the edit sequence is read backwards we need to
+                // choose the insertion first)
                 let candidates = [
+                    Cell {
+                        parent: up,
+                        operation: Insertion,
+                        cost: self.mismatch_cost(up, INSERTION_COST),
+                    },
                     Cell {
                         parent: left,
                         operation: Deletion,
-                        cost: self.table[left].cost + DELETION_COST,
+                        cost: self.mismatch_cost(left, DELETION_COST),
                     },
                     Cell {
                         parent: diag,
-                        operation: if x_i == y_j { NoOp } else { Substitution },
-                        cost: self.table[diag].cost
-                            + if x_i == y_j { 0 } else { SUBSTITUTION_COST },
-                    },
-                    Cell {
-                        parent: up,
-                        operation: Insertion,
-                        cost: self.table[up].cost + INSERTION_COST,
+                        operation: NoOp,
+                        cost: if x_i == y_j {
+                            self.table[diag].cost
+                        } else {
+                            usize::MAX
+                        },
                     },
                 ];
                 let index = self.index(i + 1, j + 1);
@@ -104,6 +114,16 @@ impl<'a> Alignment<'a> {
         }
     }
 
+    fn mismatch_cost(&self, parent: usize, basic_cost: usize) -> usize {
+        self.table[parent].cost
+            + basic_cost
+            + if self.table[parent].operation == NoOp {
+                INITIAL_MISMATCH_PENALITY
+            } else {
+                0
+            }
+    }
+
     /// Read edit operations from the table.
     pub fn operations(&self) -> Vec<Operation> {
         let mut ops = VecDeque::with_capacity(max(self.x.len(), self.y.len()));
@@ -161,7 +181,6 @@ impl<'a> Alignment<'a> {
         let parent = &self.table[cell.parent];
         let op = match cell.operation {
             Deletion => "-",
-            Substitution => "*",
             Insertion => "+",
             NoOp => ".",
         };
@@ -240,68 +259,176 @@ mod tests {
 
     #[test]
     fn test_0() {
-        let (before, after) = ("aaa", "aba");
-        assert_string_distance_parts(before, after, (1, 3));
-        assert_eq!(operations(before, after), vec![NoOp, Substitution, NoOp,]);
+        TestCase {
+            before: "aaa",
+            after: "aba",
+            distance: 5,
+            parts: (2, 4),
+            operations: vec![NoOp, Deletion, Insertion, NoOp],
+        }
+        .run();
     }
 
     #[test]
     fn test_0_nonascii() {
-        let (before, after) = ("ááb", "áaa");
-        assert_string_distance_parts(before, after, (2, 3));
-        assert_eq!(
-            operations(before, after),
-            vec![NoOp, Substitution, Substitution,]
-        );
+        TestCase {
+            before: "ááb",
+            after: "áaa",
+            distance: 9,
+            parts: (4, 5),
+            operations: vec![NoOp, Deletion, Deletion, Insertion, Insertion],
+        }
+        .run();
     }
 
     #[test]
     fn test_1() {
-        let (before, after) = ("kitten", "sitting");
-        assert_string_distance_parts(before, after, (3, 7));
-        assert_eq!(
-            operations(before, after),
-            vec![
-                Substitution, // K S
-                NoOp,         // I I
-                NoOp,         // T T
-                NoOp,         // T T
-                Substitution, // E I
-                NoOp,         // N N
-                Insertion     // - G
-            ]
-        );
+        TestCase {
+            before: "kitten",
+            after: "sitting",
+            distance: 13,
+            parts: (5, 9),
+            operations: vec![
+                Deletion,  // K -
+                Insertion, // - S
+                NoOp,      // I I
+                NoOp,      // T T
+                NoOp,      // T T
+                Deletion,  // E -
+                Insertion, // - I
+                NoOp,      // N N
+                Insertion, // - G
+            ],
+        }
+        .run();
     }
 
     #[test]
     fn test_2() {
-        let (before, after) = ("saturday", "sunday");
-        assert_string_distance_parts(before, after, (3, 8));
-        assert_eq!(
-            operations(before, after),
-            vec![
-                NoOp,         // S S
-                Deletion,     // A -
-                Deletion,     // T -
-                NoOp,         // U U
-                Substitution, // R N
-                NoOp,         // D D
-                NoOp,         // A A
-                NoOp          // Y Y
-            ]
-        );
+        TestCase {
+            before: "saturday",
+            after: "sunday",
+            distance: 10,
+            parts: (4, 9),
+            operations: vec![
+                NoOp,      // S S
+                Deletion,  // A -
+                Deletion,  // T -
+                NoOp,      // U U
+                Deletion,  // R -
+                Insertion, // - N
+                NoOp,      // D D
+                NoOp,      // A A
+                NoOp,      // Y Y
+            ],
+        }
+        .run();
     }
 
-    fn assert_string_distance_parts(s1: &str, s2: &str, parts: (usize, usize)) {
-        let (numer, _) = parts;
-        assert_string_levenshtein_distance(s1, s2, numer);
-        assert_eq!(string_distance_parts(s1, s2), parts);
-        assert_eq!(string_distance_parts(s2, s1), parts);
+    #[test]
+    fn test_3() {
+        TestCase {
+            // Prefer [Deletion NoOp Insertion] over [Insertion NoOp Deletion]
+            before: "ab",
+            after: "ba",
+            distance: 6,
+            parts: (2, 3),
+            operations: vec![
+                Deletion,  // a -
+                NoOp,      // b b
+                Insertion, // - a
+            ],
+        }
+        .run();
+    }
+
+    #[test]
+    fn test_4() {
+        // Deletions are grouped together.
+        TestCase {
+            before: "AABB",
+            after: "AB",
+            distance: 5,
+            parts: (2, 4),
+            operations: vec![
+                NoOp,     // A A
+                Deletion, // A -
+                Deletion, // B -
+                NoOp,     // B B
+            ],
+        }
+        .run();
     }
 
-    fn assert_string_levenshtein_distance(s1: &str, s2: &str, d: usize) {
-        assert_eq!(string_levenshtein_distance(s1, s2), d);
-        assert_eq!(string_levenshtein_distance(s2, s1), d);
+    #[test]
+    fn test_5() {
+        // Insertions are grouped together.
+        TestCase {
+            before: "AB",
+            after: "AABB",
+            distance: 5,
+            parts: (2, 4),
+            operations: vec![
+                NoOp,      // A A
+                Insertion, // - A
+                Insertion, // - B
+                NoOp,      // B B
+            ],
+        }
+        .run();
+    }
+
+    #[test]
+    fn test_6() {
+        // Insertion and Deletion are grouped together.
+        TestCase {
+            before: "AAABBB",
+            after: "ACB",
+            distance: 11,
+            parts: (5, 7),
+            operations: vec![
+                NoOp,      // A A
+                Deletion,  // A -
+                Deletion,  // A -
+                Deletion,  // B -
+                Deletion,  // B -
+                Insertion, // - C
+                NoOp,      // B B
+            ],
+        }
+        .run();
+    }
+
+    struct TestCase<'a> {
+        before: &'a str,
+        after: &'a str,
+        distance: usize,
+        parts: (usize, usize),
+        operations: Vec<Operation>,
+    }
+
+    impl<'a> TestCase<'a> {
+        pub fn run(&self) {
+            self.assert_string_distance_parts();
+            assert_eq!(operations(self.before, self.after), self.operations);
+        }
+
+        fn assert_string_distance_parts(&self) {
+            self.assert_string_levenshtein_distance();
+            assert_eq!(string_distance_parts(self.before, self.after), self.parts);
+            assert_eq!(string_distance_parts(self.after, self.before), self.parts);
+        }
+
+        fn assert_string_levenshtein_distance(&self) {
+            assert_eq!(
+                string_levenshtein_distance(self.before, self.after),
+                self.distance
+            );
+            assert_eq!(
+                string_levenshtein_distance(self.after, self.before),
+                self.distance
+            );
+        }
     }
 
     fn string_distance_parts(x: &str, y: &str) -> (usize, usize) {
diff --git a/src/ansi/iterator.rs b/src/ansi/iterator.rs
index f995216..6318fd5 100644
--- a/src/ansi/iterator.rs
+++ b/src/ansi/iterator.rs
@@ -24,6 +24,7 @@ pub struct AnsiElementIterator<'a> {
     pos: usize,
 }
 
+#[derive(Default)]
 struct Performer {
     // Becomes non-None when the parser finishes parsing an ANSI sequence.
     // This is never Element::Text.
@@ -42,6 +43,21 @@ pub enum Element {
     Text(usize, usize),
 }
 
+impl Element {
+    fn set_range(&mut self, start: usize, end: usize) {
+        let (from, to) = match self {
+            Element::Sgr(_, from, to) => (from, to),
+            Element::Csi(from, to) => (from, to),
+            Element::Esc(from, to) => (from, to),
+            Element::Osc(from, to) => (from, to),
+            Element::Text(from, to) => (from, to),
+        };
+
+        *from = start;
+        *to = end;
+    }
+}
+
 impl<'a> AnsiElementIterator<'a> {
     pub fn new(s: &'a str) -> Self {
         Self {
@@ -54,17 +70,12 @@ impl<'a> AnsiElementIterator<'a> {
         }
     }
 
-    #[allow(dead_code)]
-    pub fn dbg(s: &str) {
-        for el in AnsiElementIterator::new(s) {
-            match el {
-                Element::Sgr(_, i, j) => println!("SGR({}, {}, {:?})", i, j, &s[i..j]),
-                Element::Csi(i, j) => println!("CSI({}, {}, {:?})", i, j, &s[i..j]),
-                Element::Esc(i, j) => println!("ESC({}, {}, {:?})", i, j, &s[i..j]),
-                Element::Osc(i, j) => println!("OSC({}, {}, {:?})", i, j, &s[i..j]),
-                Element::Text(i, j) => println!("Text({}, {}, {:?})", i, j, &s[i..j]),
-            }
-        }
+    fn advance_vte(&mut self, byte: u8) {
+        let mut performer = Performer::default();
+        self.machine.advance(&mut performer, byte);
+        self.element = performer.element;
+        self.text_length += performer.text_length;
+        self.pos += 1;
     }
 }
 
@@ -72,53 +83,39 @@ impl<'a> Iterator for AnsiElementIterator<'a> {
     type Item = Element;
 
     fn next(&mut self) -> Option<Element> {
-        loop {
-            // If the last element emitted was text, then there may be a non-text element waiting
-            // to be emitted. In that case we do not consume a new byte.
-            let byte = if self.element.is_some() {
-                None
-            } else {
-                self.bytes.next()
-            };
-            if byte.is_some() || self.element.is_some() {
-                if let Some(byte) = byte {
-                    let mut performer = Performer {
-                        element: None,
-                        text_length: 0,
-                    };
-                    self.machine.advance(&mut performer, byte);
-                    self.element = performer.element;
-                    self.text_length += performer.text_length;
-                    self.pos += 1;
-                }
-                if self.element.is_some() {
-                    // There is a non-text element waiting to be emitted, but it may have preceding
-                    // text, which must be emitted first.
-                    if self.text_length > 0 {
-                        let start = self.start;
-                        self.start += self.text_length;
-                        self.text_length = 0;
-                        return Some(Element::Text(start, self.start));
-                    }
-                    let start = self.start;
-                    self.start = self.pos;
-                    let element = match self.element.as_ref().unwrap() {
-                        Element::Sgr(style, _, _) => Element::Sgr(*style, start, self.pos),
-                        Element::Csi(_, _) => Element::Csi(start, self.pos),
-                        Element::Esc(_, _) => Element::Esc(start, self.pos),
-                        Element::Osc(_, _) => Element::Osc(start, self.pos),
-                        Element::Text(_, _) => unreachable!(),
-                    };
-                    self.element = None;
-                    return Some(element);
-                }
-            } else if self.text_length > 0 {
+        // If the last element emitted was text, then there may be a non-text element waiting
+        // to be emitted. In that case we do not consume a new byte.
+        while self.element.is_none() {
+            match self.bytes.next() {
+                Some(b) => self.advance_vte(b),
+                None => break,
+            }
+        }
+
+        if let Some(mut element) = self.element.take() {
+            // There is a non-text element waiting to be emitted, but it may have preceding
+            // text, which must be emitted first.
+            if self.text_length > 0 {
+                let start = self.start;
+                self.start += self.text_length;
                 self.text_length = 0;
-                return Some(Element::Text(self.start, self.pos));
-            } else {
-                return None;
+                self.element = Some(element);
+                return Some(Element::Text(start, self.start));
             }
+
+            let start = self.start;
+            self.start = self.pos;
+            element.set_range(start, self.pos);
+
+            return Some(element);
         }
+
+        if self.text_length > 0 {
+            self.text_length = 0;
+            return Some(Element::Text(self.start, self.pos));
+        }
+
+        None
     }
 }
 
@@ -129,20 +126,21 @@ impl vte::Perform for Performer {
             return;
         }
 
-        if let ('m', None) = (c, intermediates.first()) {
+        let is_sgr = c == 'm' && intermediates.first().is_none();
+        let element = if is_sgr {
             if params.is_empty() {
                 // Attr::Reset
                 // Probably doesn't need to be handled: https://github.com/dandavison/delta/pull/431#discussion_r536883568
+                None
             } else {
-                self.element = Some(Element::Sgr(
-                    ansi_term_style_from_sgr_parameters(&mut params.iter()),
-                    0,
-                    0,
-                ));
+                let style = ansi_term_style_from_sgr_parameters(&mut params.iter());
+                Some(Element::Sgr(style, 0, 0))
             }
         } else {
-            self.element = Some(Element::Csi(0, 0));
-        }
+            Some(Element::Csi(0, 0))
+        };
+
+        self.element = element;
     }
 
     fn print(&mut self, c: char) {
@@ -311,7 +309,7 @@ mod tests {
                     style,
                     style::Style::from_git_str(git_style_string).ansi_term_style
                 )),
-                _ => assert!(false),
+                _ => unreachable!(),
             }
 
             // Second element should be text: "+"
@@ -331,7 +329,7 @@ mod tests {
                     style,
                     style::Style::from_git_str(git_style_string).ansi_term_style
                 )),
-                _ => assert!(false),
+                _ => unreachable!(),
             }
 
             // Fifth element should be text: "text"
diff --git a/src/ansi/mod.rs b/src/ansi/mod.rs
index 90eac1d..64b51c8 100644
--- a/src/ansi/mod.rs
+++ b/src/ansi/mod.rs
@@ -21,7 +21,9 @@ pub fn strip_ansi_codes(s: &str) -> String {
 
 pub fn measure_text_width(s: &str) -> usize {
     // TODO: how should e.g. '\n' be handled?
-    strip_ansi_codes(s).width()
+    ansi_strings_iterator(s).fold(0, |acc, (element, is_ansi)| {
+        acc + if is_ansi { 0 } else { element.width() }
+    })
 }
 
 /// Truncate string such that `tail` is present as a suffix, preceded by as much of `s` as can be
@@ -34,7 +36,7 @@ pub fn measure_text_width(s: &str) -> usize {
 //
 // 3. If tail was exhausted, then contribute graphemes and ANSI escape sequences from `s` until the
 //    display_width of the result would exceed `display_width`.
-pub fn truncate_str<'a, 'b>(s: &'a str, display_width: usize, tail: &'b str) -> Cow<'a, str> {
+pub fn truncate_str<'a>(s: &'a str, display_width: usize, tail: &str) -> Cow<'a, str> {
     let items = ansi_strings_iterator(s).collect::<Vec<(&str, bool)>>();
     let width = strip_ansi_codes_from_strings_iterator(items.iter().copied()).width();
     if width <= display_width {
@@ -52,6 +54,10 @@ pub fn truncate_str<'a, 'b>(s: &'a str, display_width: usize, tail: &'b str) ->
             for g in t.graphemes(true) {
                 let w = g.width();
                 if used + w > display_width {
+                    // use space to fill the gap left by truncate. Need another
+                    // option to custom this or just use `WrapOption.wrap_right_prefix_symbol`
+                    // in the future.
+                    result.push_str(&" ".repeat(display_width.saturating_sub(used)));
                     break;
                 }
                 result.push_str(g);
@@ -62,7 +68,7 @@ pub fn truncate_str<'a, 'b>(s: &'a str, display_width: usize, tail: &'b str) ->
         }
     }
 
-    Cow::from(format!("{}{}", result, result_tail))
+    Cow::from(format!("{result}{result_tail}"))
 }
 
 pub fn parse_style_sections(s: &str) -> Vec<(ansi_term::Style, &str)> {
@@ -170,7 +176,7 @@ pub fn explain_ansi(line: &str, colorful: bool) -> String {
             if colorful {
                 format!("({}){}", style.to_painted_string(), style.paint(s))
             } else {
-                format!("({}){}", style, s)
+                format!("({style}){s}")
             }
         })
         .collect()
diff --git a/src/cli.rs b/src/cli.rs
index 4e5e881..a0431a2 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -3,7 +3,7 @@ use std::ffi::OsString;
 use std::path::PathBuf;
 
 use bat::assets::HighlightingAssets;
-use clap::{AppSettings, ColorChoice, FromArgMatches, IntoApp, Parser};
+use clap::{AppSettings, ColorChoice, CommandFactory, FromArgMatches, Parser};
 use lazy_static::lazy_static;
 use syntect::highlighting::Theme as SyntaxTheme;
 use syntect::parsing::SyntaxSet;
@@ -1065,13 +1065,13 @@ pub struct Opt {
     /// Deprecated: use --true-color.
     pub _24_bit_color: Option<String>,
 
-    #[clap(parse(from_os_str))]
+    #[clap(value_parser)]
     /// First file to be compared when delta is being used in diff mode
     ///
     /// `delta file_1 file_2` is equivalent to `diff -u file_1 file_2 | delta`.
     pub minus_file: Option<PathBuf>,
 
-    #[clap(parse(from_os_str))]
+    #[clap(value_parser)]
     /// Second file to be compared when delta is being used in diff mode.
     pub plus_file: Option<PathBuf>,
 
@@ -1138,7 +1138,7 @@ impl Opt {
         git_config: Option<GitConfig>,
         assets: HighlightingAssets,
     ) -> Self {
-        Self::from_clap_and_git_config(env, Self::into_app().get_matches(), git_config, assets)
+        Self::from_clap_and_git_config(env, Self::command().get_matches(), git_config, assets)
     }
 
     pub fn from_iter_and_git_config<I>(
@@ -1153,7 +1153,7 @@ impl Opt {
         let assets = utils::bat::assets::load_highlighting_assets();
         Self::from_clap_and_git_config(
             env,
-            Self::into_app().get_matches_from(iter),
+            Self::command().get_matches_from(iter),
             git_config,
             assets,
         )
@@ -1174,21 +1174,18 @@ impl Opt {
     }
 
     pub fn get_argument_and_option_names<'a>() -> HashMap<&'a str, &'a str> {
-        itertools::chain(
-            Self::into_app().get_opts(),
-            Self::into_app().get_arguments(),
-        )
-        .filter_map(|arg| match (arg.get_name(), arg.get_long()) {
-            (name, Some(long)) => {
-                if IGNORED_OPTION_NAMES.contains(name) {
-                    None
-                } else {
-                    Some((name, long))
+        itertools::chain(Self::command().get_opts(), Self::command().get_arguments())
+            .filter_map(|arg| match (arg.get_id(), arg.get_long()) {
+                (name, Some(long)) => {
+                    if IGNORED_OPTION_NAMES.contains(name) {
+                        None
+                    } else {
+                        Some((name, long))
+                    }
                 }
-            }
-            _ => None,
-        })
-        .collect()
+                _ => None,
+            })
+            .collect()
     }
 }
 
diff --git a/src/color.rs b/src/color.rs
index 52904b6..a4b96b5 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -14,7 +14,7 @@ pub fn parse_color(s: &str, true_color: bool, git_config: Option<&GitConfig>) ->
         return None;
     }
     let die = || {
-        fatal(format!("Invalid color or style attribute: {}", s));
+        fatal(format!("Invalid color or style attribute: {s}"));
     };
     let syntect_color = if s.starts_with('#') {
         SyntectColor::from_str(s).unwrap_or_else(|_| die())
@@ -27,7 +27,7 @@ pub fn parse_color(s: &str, true_color: bool, git_config: Option<&GitConfig>) ->
             .or_else(|| utils::syntect::syntect_color_from_name(s));
         if syntect_color.is_none() {
             if let Some(git_config) = git_config {
-                if let Some(val) = git_config.get::<String>(&format!("delta.{}", s)) {
+                if let Some(val) = git_config.get::<String>(&format!("delta.{s}")) {
                     return parse_color(&val, true_color, None);
                 }
             }
@@ -41,8 +41,8 @@ pub fn parse_color(s: &str, true_color: bool, git_config: Option<&GitConfig>) ->
 pub fn color_to_string(color: Color) -> String {
     match color {
         Color::Fixed(n) if n < 16 => ansi_16_color_number_to_name(n).unwrap().to_string(),
-        Color::Fixed(n) => format!("{}", n),
-        Color::RGB(r, g, b) => format!("\"#{:02x?}{:02x?}{:02x?}\"", r, g, b),
+        Color::Fixed(n) => format!("{n}"),
+        Color::RGB(r, g, b) => format!("\"#{r:02x?}{g:02x?}{b:02x?}\""),
         Color::Black => "black".to_string(),
         Color::Red => "red".to_string(),
         Color::Green => "green".to_string(),
diff --git a/src/colors.rs b/src/colors.rs
index 00e826a..5c9d5c9 100644
--- a/src/colors.rs
+++ b/src/colors.rs
@@ -1,7 +1,5 @@
-use std::collections::HashMap;
-
-pub fn color_groups() -> HashMap<String, Vec<(String, String)>> {
-    [
+pub fn color_groups() -> Vec<(&'static str, Vec<(&'static str, &'static str)>)> {
+    vec![
         (
             "Blue",
             vec![
@@ -199,15 +197,4 @@ pub fn color_groups() -> HashMap<String, Vec<(String, String)>> {
             ],
         ),
     ]
-    .iter()
-    .map(|(name, colors)| {
-        (
-            name.to_string(),
-            colors
-                .iter()
-                .map(|(color, hex)| (color.to_string(), hex.to_string()))
-                .collect(),
-        )
-    })
-    .collect()
 }
diff --git a/src/config.rs b/src/config.rs
index 4da616a..3856f66 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,6 +1,7 @@
 use std::collections::HashMap;
 use std::path::PathBuf;
 
+use clap::ValueSource;
 use regex::Regex;
 use syntect::highlighting::Style as SyntectStyle;
 use syntect::highlighting::Theme as SyntaxTheme;
@@ -384,14 +385,13 @@ fn make_blame_palette(blame_palette: Option<String>, is_light_mode: bool) -> Vec
 
 /// Did the user supply `option` on the command line?
 pub fn user_supplied_option(option: &str, arg_matches: &clap::ArgMatches) -> bool {
-    arg_matches.occurrences_of(option) > 0
+    arg_matches.value_source(option) == Some(ValueSource::CommandLine)
 }
 
 pub fn delta_unreachable(message: &str) -> ! {
     fatal(format!(
-        "{} This should not be possible. \
+        "{message} This should not be possible. \
          Please report the bug at https://github.com/dandavison/delta/issues.",
-        message
     ));
 }
 
diff --git a/src/delta.rs b/src/delta.rs
index 741d3c6..1b0b298 100644
--- a/src/delta.rs
+++ b/src/delta.rs
@@ -202,7 +202,7 @@ impl<'a> StateMachine<'a> {
         // \r and \n, in which case byte_lines does not remove the \r. Remove it now.
         // TODO: Limit the number of characters we examine when looking for the \r?
         if let Some(cr_index) = self.raw_line.rfind('\r') {
-            if ansi::strip_ansi_codes(&self.raw_line[cr_index + 1..]).is_empty() {
+            if ansi::measure_text_width(&self.raw_line[cr_index + 1..]) == 0 {
                 self.raw_line = format!(
                     "{}{}",
                     &self.raw_line[..cr_index],
diff --git a/src/edits.rs b/src/edits.rs
index 9ee4d19..eb6a310 100644
--- a/src/edits.rs
+++ b/src/edits.rs
@@ -191,23 +191,11 @@ where
         *substrings_offset += n;
         &line[old_offset..*line_offset]
     };
-    let mut minus_section = |n| {
-        get_section(
-            n,
-            &mut minus_line_offset,
-            &mut x_offset,
-            &alignment.x,
-            minus_line,
-        )
+    let mut minus_section = |n: usize, offset: &mut usize| {
+        get_section(n, &mut minus_line_offset, offset, &alignment.x, minus_line)
     };
-    let mut plus_section = |n| {
-        get_section(
-            n,
-            &mut plus_line_offset,
-            &mut y_offset,
-            &alignment.y,
-            plus_line,
-        )
+    let mut plus_section = |n: usize, offset: &mut usize| {
+        get_section(n, &mut plus_line_offset, offset, &alignment.y, plus_line)
     };
     let distance_contribution = |section: &str| UnicodeWidthStr::width(section.trim());
 
@@ -215,7 +203,7 @@ where
     for (op, n) in alignment.coalesced_operations() {
         match op {
             align::Operation::Deletion => {
-                let minus_section = minus_section(n);
+                let minus_section = minus_section(n, &mut x_offset);
                 let n_d = distance_contribution(minus_section);
                 d_denom += n_d;
                 d_numer += n_d;
@@ -223,12 +211,14 @@ where
                 minus_op_prev = deletion;
             }
             align::Operation::NoOp => {
-                let minus_section = minus_section(n);
+                let minus_section = minus_section(n, &mut x_offset);
                 let n_d = distance_contribution(minus_section);
-                d_denom += n_d;
+                d_denom += 2 * n_d;
                 let is_space = minus_section.trim().is_empty();
                 let coalesce_space_with_previous = is_space
-                    && ((minus_op_prev == deletion && plus_op_prev == insertion)
+                    && ((minus_op_prev == deletion
+                        && plus_op_prev == insertion
+                        && (x_offset < alignment.x.len() - 1 || y_offset < alignment.y.len() - 1))
                         || (minus_op_prev == noop_deletion && plus_op_prev == noop_insertion));
                 annotated_minus_line.push((
                     if coalesce_space_with_previous {
@@ -244,23 +234,13 @@ where
                     } else {
                         noop_insertion
                     },
-                    plus_section(n),
+                    plus_section(n, &mut y_offset),
                 ));
                 minus_op_prev = noop_deletion;
                 plus_op_prev = noop_insertion;
             }
-            align::Operation::Substitution => {
-                let minus_section = minus_section(n);
-                let n_d = distance_contribution(minus_section);
-                d_denom += n_d;
-                d_numer += n_d;
-                annotated_minus_line.push((deletion, minus_section));
-                annotated_plus_line.push((insertion, plus_section(n)));
-                minus_op_prev = deletion;
-                plus_op_prev = insertion;
-            }
             align::Operation::Insertion => {
-                let plus_section = plus_section(n);
+                let plus_section = plus_section(n, &mut y_offset);
                 let n_d = distance_contribution(plus_section);
                 d_denom += n_d;
                 d_numer += n_d;
@@ -490,7 +470,7 @@ mod tests {
     }
 
     fn assert_tokenize(text: &str, expected_tokens: &[&str]) {
-        let actual_tokens = tokenize(text, &*DEFAULT_TOKENIZATION_REGEXP);
+        let actual_tokens = tokenize(text, &DEFAULT_TOKENIZATION_REGEXP);
         assert_eq!(text, expected_tokens.iter().join(""));
         // tokenize() guarantees that the first element of the token stream is "".
         // See comment in Alignment::new()
@@ -631,13 +611,13 @@ mod tests {
             vec!["fn coalesce_edits<'a, 'b, EditOperation>("],
             (
                 vec![vec![
-                    (MinusNoop, "fn coalesce_edits<'a"),
-                    (MinusNoop, ", EditOperation>("),
+                    (MinusNoop, "fn coalesce_edits<'a, "),
+                    (MinusNoop, "EditOperation>("),
                 ]],
                 vec![vec![
-                    (PlusNoop, "fn coalesce_edits<'a"),
-                    (Insertion, ", 'b"),
-                    (PlusNoop, ", EditOperation>("),
+                    (PlusNoop, "fn coalesce_edits<'a, "),
+                    (Insertion, "'b, "),
+                    (PlusNoop, "EditOperation>("),
                 ]],
             ),
             0.66,
@@ -652,15 +632,15 @@ mod tests {
             (
                 vec![vec![
                     (MinusNoop, "for _ in range(0, "),
-                    (MinusNoop, "options[\"count\"]"),
-                    (MinusNoop, "):"),
+                    (MinusNoop, "options[\"count\"])"),
+                    (MinusNoop, ":"),
                 ]],
                 vec![vec![
                     (PlusNoop, "for _ in range(0, "),
                     (Insertion, "int("),
-                    (PlusNoop, "options[\"count\"]"),
+                    (PlusNoop, "options[\"count\"])"),
                     (Insertion, ")"),
-                    (PlusNoop, "):"),
+                    (PlusNoop, ":"),
                 ]],
             ),
             0.3,
@@ -673,8 +653,8 @@ mod tests {
             vec!["a a"],
             vec!["a b a"],
             (
-                vec![vec![(MinusNoop, "a"), (MinusNoop, " a")]],
-                vec![vec![(PlusNoop, "a"), (Insertion, " b"), (PlusNoop, " a")]],
+                vec![vec![(MinusNoop, "a "), (MinusNoop, "a")]],
+                vec![vec![(PlusNoop, "a "), (Insertion, "b "), (PlusNoop, "a")]],
             ),
             1.0,
         );
@@ -682,8 +662,8 @@ mod tests {
             vec!["a a"],
             vec!["a b b a"],
             (
-                vec![vec![(MinusNoop, "a"), (MinusNoop, " a")]],
-                vec![vec![(PlusNoop, "a"), (Insertion, " b b"), (PlusNoop, " a")]],
+                vec![vec![(MinusNoop, "a "), (MinusNoop, "a")]],
+                vec![vec![(PlusNoop, "a "), (Insertion, "b b "), (PlusNoop, "a")]],
             ),
             1.0,
         );
@@ -698,20 +678,17 @@ mod tests {
                 // TODO: Coalesce runs of the same operation.
                 vec![vec![
                     (MinusNoop, "so it is safe to read "),
-                    (Deletion, "the"),
+                    (Deletion, "the commit"),
                     (Deletion, " "),
-                    (Deletion, "commit"),
-                    (Deletion, " "),
-                    (Deletion, "number "),
-                    (MinusNoop, "from any one of them."),
+                    (Deletion, "number"),
+                    (MinusNoop, " from any one of them."),
                 ]],
                 vec![vec![
                     (PlusNoop, "so it is safe to read "),
                     (Insertion, "build"),
                     (Insertion, " "),
                     (Insertion, "info"),
-                    (Insertion, " "),
-                    (PlusNoop, "from any one of them."),
+                    (PlusNoop, " from any one of them."),
                 ]],
             ),
             1.0,
@@ -735,7 +712,6 @@ mod tests {
     }
 
     #[test]
-    #[ignore]
     fn test_infer_edits_12() {
         assert_edits(
             vec!["                     (xxxxxxxxx, \"build info\"),"],
@@ -755,6 +731,119 @@ mod tests {
         );
     }
 
+    #[test]
+    fn test_infer_edits_13() {
+        assert_paired_edits(
+            vec!["'b '", "[element,]"],
+            vec!["' b'", "[element],"],
+            (
+                vec![
+                    vec![
+                        (MinusNoop, "'"),
+                        (Deletion, "b"),
+                        (MinusNoop, " "),
+                        (MinusNoop, "'"),
+                    ],
+                    vec![(MinusNoop, "[element"), (Deletion, ","), (MinusNoop, "]")],
+                ],
+                vec![
+                    vec![
+                        (PlusNoop, "'"),
+                        (PlusNoop, " "),
+                        (Insertion, "b"),
+                        (PlusNoop, "'"),
+                    ],
+                    vec![(PlusNoop, "[element"), (PlusNoop, "]"), (Insertion, ",")],
+                ],
+            ),
+        );
+    }
+
+    #[test]
+    fn test_infer_edits_14() {
+        assert_paired_edits(
+            vec!["a b c d ", "p "],
+            vec!["x y c z ", "q r"],
+            (
+                vec![
+                    vec![
+                        (MinusNoop, ""),
+                        (Deletion, "a"),
+                        (Deletion, " "),
+                        (Deletion, "b"),
+                        (MinusNoop, " c "),
+                        (Deletion, "d"),
+                        (MinusNoop, " "),
+                    ],
+                    vec![(MinusNoop, ""), (Deletion, "p"), (Deletion, " ")],
+                ],
+                vec![
+                    vec![
+                        (PlusNoop, ""),
+                        (Insertion, "x"),
+                        (Insertion, " "),
+                        (Insertion, "y"),
+                        (PlusNoop, " c "),
+                        (Insertion, "z"),
+                        (PlusNoop, " "),
+                    ],
+                    vec![
+                        (PlusNoop, ""),
+                        (Insertion, "q"),
+                        (Insertion, " "),
+                        (Insertion, "r"),
+                    ],
+                ],
+            ),
+        );
+    }
+
+    #[test]
+    fn test_infer_edits_15() {
+        assert_paired_edits(
+            vec![r#"printf "%s\n" s y y | git add -p &&"#],
+            vec!["test_write_lines s y y | git add -p &&"],
+            (
+                vec![vec![
+                    (MinusNoop, ""),
+                    (Deletion, r#"printf "%s\n""#),
+                    (MinusNoop, " s y y | git add -p &&"),
+                ]],
+                vec![vec![
+                    (PlusNoop, ""),
+                    (Insertion, "test_write_lines"),
+                    (PlusNoop, " s y y | git add -p &&"),
+                ]],
+            ),
+        );
+    }
+
+    #[test]
+    fn test_infer_edits_16() {
+        assert_edits(
+            vec!["a a a a a a b b b"],
+            vec!["c a a a a a a c c"],
+            (
+                vec![vec![
+                    (MinusNoop, ""),
+                    (MinusNoop, "a a a a a a "),
+                    (Deletion, "b b"),
+                    (Deletion, " "),
+                    (Deletion, "b"),
+                ]],
+                vec![vec![
+                    (PlusNoop, ""),
+                    (Insertion, "c "),
+                    (PlusNoop, "a a a a a a "),
+                    (Insertion, "c"),
+                    (Insertion, " "),
+                    (Insertion, "c"),
+                ]],
+            ),
+            1.0,
+        );
+    }
+
     fn assert_edits(
         minus_lines: Vec<&str>,
         plus_lines: Vec<&str>,
@@ -772,7 +861,7 @@ mod tests {
             Deletion,
             noop_insertions,
             Insertion,
-            &*DEFAULT_TOKENIZATION_REGEXP,
+            &DEFAULT_TOKENIZATION_REGEXP,
             max_line_distance,
             0.0,
         );
diff --git a/src/features/diff_highlight.rs b/src/features/diff_highlight.rs
index fc4544c..ccc39f9 100644
--- a/src/features/diff_highlight.rs
+++ b/src/features/diff_highlight.rs
@@ -7,10 +7,7 @@ pub fn make_feature() -> Vec<(String, OptionValueFunction)> {
 
 pub fn _make_feature(bold: bool) -> Vec<(String, OptionValueFunction)> {
     let mut feature = raw::make_feature();
-    feature = feature
-        .into_iter()
-        .filter(|(s, _)| s != "keep-plus-minus-markers" && s != "tabs")
-        .collect();
+    feature.retain(|(s, _)| s != "keep-plus-minus-markers" && s != "tabs");
     feature.extend(builtin_feature!([
         (
             "commit-style",
diff --git a/src/features/hyperlinks.rs b/src/features/hyperlinks.rs
index b45de0f..78be83e 100644
--- a/src/features/hyperlinks.rs
+++ b/src/features/hyperlinks.rs
@@ -31,7 +31,7 @@ pub fn format_commit_line_with_osc8_commit_hyperlink<'a>(
             let suffix = captures.get(3).map(|m| m.as_str()).unwrap_or("");
             let formatted_commit =
                 format_osc8_hyperlink(&commit_link_format.replace("{commit}", commit), commit);
-            format!("{}{}{}", prefix, formatted_commit, suffix)
+            format!("{prefix}{formatted_commit}{suffix}")
         })
     } else if let Some(GitConfigEntry::GitRemote(repo)) =
         config.git_config.as_ref().and_then(get_remote_url)
@@ -74,7 +74,7 @@ where
         .hyperlinks_file_link_format
         .replace("{path}", &absolute_path.as_ref().to_string_lossy());
     if let Some(n) = line_number {
-        url = url.replace("{line}", &format!("{}", n))
+        url = url.replace("{line}", &format!("{n}"))
     } else {
         url = url.replace("{line}", "")
     };
@@ -138,8 +138,7 @@ pub mod tests {
         ] {
             run_test(FilePathsTestCase {
                 name: &format!(
-                    "delta relative_paths={} calling_cmd={:?}",
-                    delta_relative_paths_option, calling_cmd
+                    "delta relative_paths={delta_relative_paths_option} calling_cmd={calling_cmd:?}",
                 ),
                 true_location_of_file_relative_to_repo_root:
                     true_location_of_file_relative_to_repo_root.as_path(),
@@ -452,7 +451,7 @@ __path__:  some matching line
 
     fn run_test(test_case: FilePathsTestCase) {
         let mut config = integration_test_utils::make_config_from_args(
-            &test_case
+            test_case
                 .get_args()
                 .iter()
                 .map(|s| s.as_str())
@@ -483,12 +482,11 @@ __path__:  some matching line
                     test_case.path_in_delta_input,
                     test_case.path_in_grep_output()
                 );
-                delta_test.with_input(
-                    &GIT_GREP_OUTPUT.replace("__path__", &test_case.path_in_delta_input),
-                )
+                delta_test
+                    .with_input(&GIT_GREP_OUTPUT.replace("__path__", test_case.path_in_delta_input))
             }
             CallingProcess::OtherGrep => delta_test
-                .with_input(&GIT_GREP_OUTPUT.replace("__path__", &test_case.path_in_delta_input)),
+                .with_input(&GIT_GREP_OUTPUT.replace("__path__", test_case.path_in_delta_input)),
         };
         let make_expected_hyperlink = |text| {
             format_osc8_hyperlink(
diff --git a/src/features/line_numbers.rs b/src/features/line_numbers.rs
index 8885043..99a9777 100644
--- a/src/features/line_numbers.rs
+++ b/src/features/line_numbers.rs
@@ -167,12 +167,12 @@ impl<'a> LineNumbersData<'a> {
             format_data: MinusPlus::new(
                 format::parse_line_number_format(
                     &format[Left],
-                    &*LINE_NUMBERS_PLACEHOLDER_REGEX,
+                    &LINE_NUMBERS_PLACEHOLDER_REGEX,
                     false,
                 ),
                 format::parse_line_number_format(
                     &format[Right],
-                    &*LINE_NUMBERS_PLACEHOLDER_REGEX,
+                    &LINE_NUMBERS_PLACEHOLDER_REGEX,
                     insert_center_space_on_odd_width,
                 ),
             ),
@@ -198,7 +198,7 @@ impl<'a> LineNumbersData<'a> {
             format_data: if insert_center_space_on_odd_width {
                 let format_left = vec![format::FormatStringPlaceholderData::default()];
                 let format_right = vec![format::FormatStringPlaceholderData {
-                    prefix: format!("{}", ODD_PAD_CHAR).into(),
+                    prefix: format!("{ODD_PAD_CHAR}").into(),
                     prefix_len: 1,
                     ..Default::default()
                 }];
@@ -327,9 +327,9 @@ pub mod tests {
 
     use super::*;
 
-    pub fn parse_line_number_format_with_default_regex<'a>(
-        format_string: &'a str,
-    ) -> FormatStringData<'a> {
+    pub fn parse_line_number_format_with_default_regex(
+        format_string: &str,
+    ) -> FormatStringData<'_> {
         format::parse_line_number_format(format_string, &LINE_NUMBERS_PLACEHOLDER_REGEX, false)
     }
 
@@ -460,7 +460,7 @@ pub mod tests {
         assert_eq!(
             format::parse_line_number_format("|{nm:<4}|", &LINE_NUMBERS_PLACEHOLDER_REGEX, true),
             vec![format::FormatStringPlaceholderData {
-                prefix: format!("{}|", ODD_PAD_CHAR).into(),
+                prefix: format!("{ODD_PAD_CHAR}|").into(),
                 placeholder: Some(Placeholder::NumberMinus),
                 alignment_spec: Some(Align::Left),
                 width: Some(4),
@@ -483,7 +483,7 @@ pub mod tests {
             ),
             vec![
                 format::FormatStringPlaceholderData {
-                    prefix: format!("{}|", ODD_PAD_CHAR).into(),
+                    prefix: format!("{ODD_PAD_CHAR}|").into(),
                     placeholder: Some(Placeholder::NumberMinus),
                     alignment_spec: Some(Align::Left),
                     width: Some(4),
@@ -512,7 +512,7 @@ pub mod tests {
         assert_eq!(
             format::parse_line_number_format("|++|", &LINE_NUMBERS_PLACEHOLDER_REGEX, true),
             vec![format::FormatStringPlaceholderData {
-                prefix: format!("{}", ODD_PAD_CHAR).into(),
+                prefix: format!("{ODD_PAD_CHAR}").into(),
                 placeholder: None,
                 alignment_spec: None,
                 width: None,
@@ -530,10 +530,7 @@ pub mod tests {
         let long = "line number format which is too large for SSO";
         assert!(long.len() > std::mem::size_of::<smol_str::SmolStr>());
         assert_eq!(
-            parse_line_number_format_with_default_regex(&format!(
-                "{long}{{nm}}{long}",
-                long = long
-            ),),
+            parse_line_number_format_with_default_regex(&format!("{long}{{nm}}{long}")),
             vec![format::FormatStringPlaceholderData {
                 prefix: long.into(),
                 prefix_len: long.len(),
@@ -615,7 +612,7 @@ pub mod tests {
         assert_eq!(data.formatted_width(), MinusPlus::new(32, 0));
 
         let format = MinusPlus::new("│{np:^3}│ │{nm:<12}│ │{np}│".into(), "".into());
-        let mut data = LineNumbersData::from_format_strings(&format, w.clone());
+        let mut data = LineNumbersData::from_format_strings(&format, w);
 
         data.initialize_hunk(&[(10, 11), (10000, 100001)], "a".into());
         assert_eq!(data.formatted_width(), MinusPlus::new(32, 0));
@@ -797,6 +794,8 @@ pub mod tests {
             "@",
             "--wrap-right-symbol",
             "@",
+            "--wrap-right-prefix-symbol",
+            ">",
         ])
         .with_input(DIFF_PLUS_MINUS_WITH_1_CONTEXT_DIFF)
         .expect_after_header(
@@ -816,6 +815,8 @@ pub mod tests {
             "@",
             "--wrap-right-symbol",
             "@",
+            "--wrap-right-prefix-symbol",
+            ">",
         ];
 
         DeltaTest::with_args(cfg)
@@ -824,7 +825,7 @@ pub mod tests {
                 r#"
                 │  1 │abc            │  1 │abc
                 │  2 │a = one side   │  2 │a = one longer@
-                │    │               │    │ side
+                │    │               │    │         > side
                 │  3 │xyz            │  3 │xyz"#,
             );
 
@@ -834,7 +835,7 @@ pub mod tests {
                 r#"
                 │  1 │abc            │  1 │abc
                 │  2 │a = one longer@│  2 │a = one side
-                │    │ side          │    │
+                │    │         > side│    │
                 │  3 │xyz            │  3 │xyz"#,
             );
 
@@ -975,4 +976,13 @@ index 223ca50..367a6f6 100644
 -a = other one
 +a = right side which is longer
  xyz";
+
+    pub const TWO_MINUS_LINES_UNICODE_DIFF: &str = "\
+diff --git a/a.py b/a.py
+index 8b0d958..e69de29 100644
+--- a/a.txt
++++ b/b.txt
+@@ -1,1 +0,0 @@
+-一二三
+";
 }
diff --git a/src/features/mod.rs b/src/features/mod.rs
index 8234d62..dfc3582 100644
--- a/src/features/mod.rs
+++ b/src/features/mod.rs
@@ -102,7 +102,7 @@ pub mod tests {
     fn test_builtin_features_have_flags_and_these_set_features() {
         let builtin_features = make_builtin_features();
         let mut args = vec!["delta".to_string()];
-        args.extend(builtin_features.keys().map(|s| format!("--{}", s)));
+        args.extend(builtin_features.keys().map(|s| format!("--{s}")));
         let opt = cli::Opt::from_iter_and_git_config(DeltaEnv::default(), args, None);
         let features: HashSet<&str> = opt
             .features
diff --git a/src/features/navigate.rs b/src/features/navigate.rs
index dc31e4a..f78a675 100644
--- a/src/features/navigate.rs
+++ b/src/features/navigate.rs
@@ -77,7 +77,7 @@ pub fn copy_less_hist_file_and_append_navigate_regex(config: &Config) -> std::io
         initial_contents
     };
     if !contents.ends_with(".search\n") {
-        contents = format!("{}.search\n", contents);
+        contents = format!("{contents}.search\n");
     }
     writeln!(
         std::fs::File::create(&delta_less_hist_file)?,
diff --git a/src/features/side_by_side.rs b/src/features/side_by_side.rs
index f175fa2..02f3eeb 100644
--- a/src/features/side_by_side.rs
+++ b/src/features/side_by_side.rs
@@ -1,6 +1,6 @@
 use itertools::Itertools;
 use syntect::highlighting::Style as SyntectStyle;
-use unicode_segmentation::UnicodeSegmentation;
+use unicode_width::UnicodeWidthStr;
 
 use crate::ansi;
 use crate::cli;
@@ -75,12 +75,10 @@ pub fn available_line_width(
 }
 
 pub fn line_is_too_long(line: &str, line_width: usize) -> bool {
-    let line_sum = line.graphemes(true).count();
-
     debug_assert!(line.ends_with('\n'));
-    // `line_sum` is too large because a trailing newline is present,
-    // so allow one more character.
-    line_sum > line_width + 1
+
+    // graphemes will take care of newlines
+    line.width() > line_width
 }
 
 /// Return whether any of the input lines is too long, and a data
@@ -350,10 +348,10 @@ fn paint_right_panel_plus_line<'a>(
 }
 
 #[allow(clippy::too_many_arguments)]
-fn get_right_fill_style_for_panel<'a>(
+fn get_right_fill_style_for_panel(
     line_is_empty: bool,
     line_index: Option<usize>,
-    diff_style_sections: &[LineSections<'a, Style>],
+    diff_style_sections: &[LineSections<'_, Style>],
     lines_have_homolog: Option<&[bool]>,
     state: &State,
     panel_side: PanelSide,
@@ -470,11 +468,11 @@ fn paint_minus_or_plus_panel_line<'a>(
 /// done with spaces. The right panel can be filled with spaces or using ANSI sequences
 /// instructing the terminal emulator to fill the background color rightwards.
 #[allow(clippy::too_many_arguments, clippy::comparison_chain)]
-fn pad_panel_line_to_width<'a>(
+fn pad_panel_line_to_width(
     panel_line: &mut String,
     panel_line_is_empty: bool,
     line_index: Option<usize>,
-    diff_style_sections: &[LineSections<'a, Style>],
+    diff_style_sections: &[LineSections<'_, Style>],
     lines_have_homolog: Option<&[bool]>,
     state: &State,
     panel_side: PanelSide,
@@ -715,4 +713,58 @@ pub mod tests {
             │  2 │b = 2         │  2 │bb = 2        "#,
         );
     }
+
+    #[test]
+    fn test_two_minus_lines_unicode_truncated() {
+        DeltaTest::with_args(&[
+            "--side-by-side",
+            "--wrap-max-lines",
+            "2",
+            "--width",
+            "16",
+            "--line-fill-method=spaces",
+        ])
+        .set_config(|cfg| cfg.truncation_symbol = ">".into())
+        .with_input(TWO_MINUS_LINES_UNICODE_DIFF)
+        .expect_after_header(
+            r#"
+            │  1 │↵ │    │
+            │    │↵ │    │
+            │    │ >│    │"#,
+        );
+
+        DeltaTest::with_args(&[
+            "--side-by-side",
+            "--wrap-max-lines",
+            "2",
+            "--width",
+            "17",
+            "--line-fill-method=spaces",
+        ])
+        .set_config(|cfg| cfg.truncation_symbol = ">".into())
+        .with_input(TWO_MINUS_LINES_UNICODE_DIFF)
+        .expect_after_header(
+            r#"
+            │  1 │↵ │    │
+            │    │↵ │    │
+            │    │ >│    │"#,
+        );
+
+        DeltaTest::with_args(&[
+            "--side-by-side",
+            "--wrap-max-lines",
+            "2",
+            "--width",
+            "18",
+            "--line-fill-method=spaces",
+        ])
+        .set_config(|cfg| cfg.truncation_symbol = ">".into())
+        .with_input(TWO_MINUS_LINES_UNICODE_DIFF)
+        .expect_after_header(
+            r#"
+            │  1 │一↵│    │
+            │    │二↵│    │
+            │    │三 │    │"#,
+        );
+    }
 }
diff --git a/src/format.rs b/src/format.rs
index d40e417..0dc05fa 100644
--- a/src/format.rs
+++ b/src/format.rs
@@ -35,6 +35,8 @@ pub enum Align {
 impl TryFrom<Option<&str>> for Align {
     type Error = ();
     fn try_from(from: Option<&str>) -> Result<Self, Self::Error> {
+        // inlined format args are not supported for `debug_assert` with edition 2018.
+        #[allow(clippy::uninlined_format_args)]
         match from {
             Some(alignment) if alignment == "<" => Ok(Align::Left),
             Some(alignment) if alignment == ">" => Ok(Align::Right),
@@ -162,7 +164,7 @@ pub fn parse_line_number_format<'a>(
     let mut expand_first_prefix = |prefix: SmolStr| {
         // Only prefix the first placeholder with a space, also see `UseFullPanelWidth`
         if prefix_with_space {
-            let prefix = SmolStr::new(format!("{}{}", ODD_PAD_CHAR, prefix));
+            let prefix = SmolStr::new(format!("{ODD_PAD_CHAR}{prefix}"));
             prefix_with_space = false;
             prefix
         } else {
@@ -300,14 +302,14 @@ pub fn pad<T: std::fmt::Display + CenterRightNumbers>(
     let space = s.center_right_space(alignment, width);
     let mut result = match precision {
         None => match alignment {
-            Align::Left => format!("{0}{1:<2$}", space, s, width),
-            Align::Center => format!("{0}{1:^2$}", space, s, width),
-            Align::Right => format!("{0}{1:>2$}", space, s, width),
+            Align::Left => format!("{space}{s:<width$}"),
+            Align::Center => format!("{space}{s:^width$}"),
+            Align::Right => format!("{space}{s:>width$}"),
         },
         Some(precision) => match alignment {
-            Align::Left => format!("{0}{1:<2$.3$}", space, s, width, precision),
-            Align::Center => format!("{0}{1:^2$.3$}", space, s, width, precision),
-            Align::Right => format!("{0}{1:>2$.3$}", space, s, width, precision),
+            Align::Left => format!("{space}{s:<width$.precision$}"),
+            Align::Center => format!("{space}{s:^width$.precision$}"),
+            Align::Right => format!("{space}{s:>width$.precision$}"),
         },
     };
     if space == " " {
diff --git a/src/git_config/git_config_entry.rs b/src/git_config/git_config_entry.rs
index 66a9f84..c07fc0a 100644
--- a/src/git_config/git_config_entry.rs
+++ b/src/git_config/git_config_entry.rs
@@ -14,18 +14,26 @@ pub enum GitConfigEntry {
 
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum GitRemoteRepo {
-    GitHubRepo { repo_slug: String },
-    GitLabRepo { repo_slug: String },
+    GitHub { slug: String },
+    GitLab { slug: String },
+    SourceHut { slug: String },
+    Codeberg { slug: String },
 }
 
 impl GitRemoteRepo {
     pub fn format_commit_url(&self, commit: &str) -> String {
         match self {
-            Self::GitHubRepo { repo_slug } => {
-                format!("https://github.com/{}/commit/{}", repo_slug, commit)
+            Self::GitHub { slug } => {
+                format!("https://github.com/{slug}/commit/{commit}")
             }
-            Self::GitLabRepo { repo_slug } => {
-                format!("https://gitlab.com/{}/-/commit/{}", repo_slug, commit)
+            Self::GitLab { slug } => {
+                format!("https://gitlab.com/{slug}/-/commit/{commit}")
+            }
+            Self::SourceHut { slug } => {
+                format!("https://git.sr.ht/{slug}/commit/{commit}")
+            }
+            Self::Codeberg { slug } => {
+                format!("https://codeberg.org/{slug}/commit/{commit}")
             }
         }
     }
@@ -61,30 +69,73 @@ lazy_static! {
         "
     )
     .unwrap();
+    static ref SOURCEHUT_REMOTE_URL: Regex = Regex::new(
+        r"(?x)
+        ^
+        (?:https://|git@)? # Support both HTTPS and SSH URLs, SSH URLs optionally omitting the git@
+        git\.sr\.ht
+        [:/]              # This separator differs between SSH and HTTPS URLs
+        ~([^/]+)          # Capture the username
+        /
+        (.+)             # Capture the repo name
+        $
+        "
+    )
+    .unwrap();
+    static ref CODEBERG_REMOTE_URL: Regex = Regex::new(
+        r"(?x)
+        ^
+        (?:https://|git@)? # Support both HTTPS and SSH URLs, SSH URLs optionally omitting the git@
+        codeberg\.org
+        [:/]              # This separator differs between SSH and HTTPS URLs
+        ([^/]+)           # Capture the user/org name
+        /
+        (.+?)             # Capture the repo name (lazy to avoid consuming '.git' if present)
+        (?:\.git)?        # Non-capturing group to consume '.git' if present
+        $
+        "
+    )
+    .unwrap();
 }
 
 impl FromStr for GitRemoteRepo {
     type Err = Error;
     fn from_str(s: &str) -> Result<Self, Self::Err> {
         if let Some(caps) = GITHUB_REMOTE_URL.captures(s) {
-            Ok(Self::GitHubRepo {
-                repo_slug: format!(
+            Ok(Self::GitHub {
+                slug: format!(
                     "{user}/{repo}",
                     user = caps.get(1).unwrap().as_str(),
                     repo = caps.get(2).unwrap().as_str()
                 ),
             })
         } else if let Some(caps) = GITLAB_REMOTE_URL.captures(s) {
-            Ok(Self::GitLabRepo {
-                repo_slug: format!(
+            Ok(Self::GitLab {
+                slug: format!(
                     "{user}{groups}/{repo}",
                     user = caps.get(1).unwrap().as_str(),
                     groups = caps.get(2).map(|x| x.as_str()).unwrap_or_default(),
                     repo = caps.get(3).unwrap().as_str()
                 ),
             })
+        } else if let Some(caps) = SOURCEHUT_REMOTE_URL.captures(s) {
+            Ok(Self::SourceHut {
+                slug: format!(
+                    "~{user}/{repo}",
+                    user = caps.get(1).unwrap().as_str(),
+                    repo = caps.get(2).unwrap().as_str()
+                ),
+            })
+        } else if let Some(caps) = CODEBERG_REMOTE_URL.captures(s) {
+            Ok(Self::Codeberg {
+                slug: format!(
+                    "{user}/{repo}",
+                    user = caps.get(1).unwrap().as_str(),
+                    repo = caps.get(2).unwrap().as_str()
+                ),
+            })
         } else {
-            Err("Not a GitHub or GitLab repo.".into())
+            Err("Not a GitHub, GitLab, SourceHut or Codeberg repo.".into())
         }
     }
 }
@@ -108,8 +159,8 @@ mod tests {
             assert!(parsed.is_ok());
             assert_eq!(
                 parsed.unwrap(),
-                GitRemoteRepo::GitHubRepo {
-                    repo_slug: "dandavison/delta".to_string()
+                GitRemoteRepo::GitHub {
+                    slug: "dandavison/delta".to_string()
                 }
             );
         }
@@ -117,13 +168,13 @@ mod tests {
 
     #[test]
     fn test_format_github_commit_link() {
-        let repo = GitRemoteRepo::GitHubRepo {
-            repo_slug: "dandavison/delta".to_string(),
+        let repo = GitRemoteRepo::GitHub {
+            slug: "dandavison/delta".to_string(),
         };
         let commit_hash = "d3b07384d113edec49eaa6238ad5ff00";
         assert_eq!(
             repo.format_commit_url(commit_hash),
-            format!("https://github.com/dandavison/delta/commit/{}", commit_hash)
+            format!("https://github.com/dandavison/delta/commit/{commit_hash}")
         )
     }
 
@@ -153,8 +204,8 @@ mod tests {
             assert!(parsed.is_ok());
             assert_eq!(
                 parsed.unwrap(),
-                GitRemoteRepo::GitLabRepo {
-                    repo_slug: expected.to_string()
+                GitRemoteRepo::GitLab {
+                    slug: expected.to_string()
                 }
             );
         }
@@ -162,13 +213,78 @@ mod tests {
 
     #[test]
     fn test_format_gitlab_commit_link() {
-        let repo = GitRemoteRepo::GitLabRepo {
-            repo_slug: "proj/grp/repo".to_string(),
+        let repo = GitRemoteRepo::GitLab {
+            slug: "proj/grp/repo".to_string(),
         };
         let commit_hash = "d3b07384d113edec49eaa6238ad5ff00";
         assert_eq!(
             repo.format_commit_url(commit_hash),
-            format!("https://gitlab.com/proj/grp/repo/-/commit/{}", commit_hash)
+            format!("https://gitlab.com/proj/grp/repo/-/commit/{commit_hash}")
+        )
+    }
+
+    #[test]
+    fn test_parse_sourcehut_urls() {
+        let urls = &[
+            "https://git.sr.ht/~someuser/somerepo",
+            "git@git.sr.ht:~someuser/somerepo",
+            "git.sr.ht:~someuser/somerepo",
+        ];
+        for url in urls {
+            let parsed = GitRemoteRepo::from_str(url);
+            assert!(parsed.is_ok());
+            assert_eq!(
+                parsed.unwrap(),
+                GitRemoteRepo::SourceHut {
+                    slug: "~someuser/somerepo".to_string()
+                }
+            );
+        }
+    }
+
+    #[test]
+    fn test_format_sourcehut_commit_link() {
+        let repo = GitRemoteRepo::SourceHut {
+            slug: "~someuser/somerepo".to_string(),
+        };
+        let commit_hash = "df41ac86f08a40e64c76062fd67e238522c14990";
+        assert_eq!(
+            repo.format_commit_url(commit_hash),
+            format!("https://git.sr.ht/~someuser/somerepo/commit/{commit_hash}")
+        )
+    }
+
+    #[test]
+    fn test_parse_codeberg_urls() {
+        let urls = &[
+            "https://codeberg.org/someuser/somerepo.git",
+            "https://codeberg.org/someuser/somerepo",
+            "git@codeberg.org:someuser/somerepo.git",
+            "git@codeberg.org:someuser/somerepo",
+            "codeberg.org:someuser/somerepo.git",
+            "codeberg.org:someuser/somerepo",
+        ];
+        for url in urls {
+            let parsed = GitRemoteRepo::from_str(url);
+            assert!(parsed.is_ok());
+            assert_eq!(
+                parsed.unwrap(),
+                GitRemoteRepo::Codeberg {
+                    slug: "someuser/somerepo".to_string()
+                }
+            );
+        }
+    }
+
+    #[test]
+    fn test_format_codeberg_commit_link() {
+        let repo = GitRemoteRepo::Codeberg {
+            slug: "dnkl/foot".to_string(),
+        };
+        let commit_hash = "1c072856ebf12419378c5098ad543c497197c6da";
+        assert_eq!(
+            repo.format_commit_url(commit_hash),
+            format!("https://codeberg.org/dnkl/foot/commit/{commit_hash}")
         )
     }
 }
diff --git a/src/git_config/mod.rs b/src/git_config/mod.rs
index 6b87444..228fd4d 100644
--- a/src/git_config/mod.rs
+++ b/src/git_config/mod.rs
@@ -51,7 +51,7 @@ impl GitConfig {
         match config {
             Some(mut config) => {
                 let config = config.snapshot().unwrap_or_else(|err| {
-                    fatal(format!("Failed to read git config: {}", err));
+                    fatal(format!("Failed to read git config: {err}"));
                 });
                 Some(Self {
                     config,
diff --git a/src/handlers/blame.rs b/src/handlers/blame.rs
index 0e21f6b..3890b7c 100644
--- a/src/handlers/blame.rs
+++ b/src/handlers/blame.rs
@@ -43,7 +43,7 @@ impl<'a> StateMachine<'a> {
                 // Format blame metadata
                 let format_data = format::parse_line_number_format(
                     &self.config.blame_format,
-                    &*BLAME_PLACEHOLDER_REGEX,
+                    &BLAME_PLACEHOLDER_REGEX,
                     false,
                 );
                 let mut formatted_blame_metadata =
@@ -363,8 +363,7 @@ pub fn parse_blame_line_numbers(arg: &str) -> BlameLineNumbers {
                 .parse::<usize>()
                 .unwrap_or_else(|err| {
                     fatal(format!(
-                        "Invalid number for blame-line-numbers in every-N argument: {}",
-                        err
+                        "Invalid number for blame-line-numbers in every-N argument: {err}",
                     ))
                 });
 
@@ -375,8 +374,7 @@ pub fn parse_blame_line_numbers(arg: &str) -> BlameLineNumbers {
             }
         }
         t => fatal(format!(
-            "Invalid format type \"{}\" for blame-line-numbers",
-            t
+            "Invalid format type \"{t}\" for blame-line-numbers",
         )),
     }
 }
@@ -529,13 +527,13 @@ mod tests {
     }
 
     fn make_blame_line_with_time(timestamp: &str) -> BlameLine {
-        let time = chrono::DateTime::parse_from_rfc3339(&timestamp).unwrap();
-        return BlameLine {
+        let time = chrono::DateTime::parse_from_rfc3339(timestamp).unwrap();
+        BlameLine {
             commit: "",
             author: "",
-            time: time,
+            time,
             line_number: 0,
             code: "",
-        };
+        }
     }
 }
diff --git a/src/handlers/diff_header.rs b/src/handlers/diff_header.rs
index 5038efd..b07e04c 100644
--- a/src/handlers/diff_header.rs
+++ b/src/handlers/diff_header.rs
@@ -88,7 +88,7 @@ impl<'a> StateMachine<'a> {
             parse_diff_header_line(&self.line, self.source == Source::GitDiff);
 
         self.minus_file = utils::path::relativize_path_maybe(&path_or_mode, self.config)
-            .map(|p| p.to_string_lossy().to_owned().to_string())
+            .map(|p| p.to_string_lossy().into_owned())
             .unwrap_or(path_or_mode);
         self.minus_file_event = file_event;
 
@@ -125,7 +125,7 @@ impl<'a> StateMachine<'a> {
             parse_diff_header_line(&self.line, self.source == Source::GitDiff);
 
         self.plus_file = utils::path::relativize_path_maybe(&path_or_mode, self.config)
-            .map(|p| p.to_string_lossy().to_owned().to_string())
+            .map(|p| p.to_string_lossy().into_owned())
             .unwrap_or(path_or_mode);
         self.plus_file_event = file_event;
         self.painter
@@ -162,8 +162,7 @@ impl<'a> StateMachine<'a> {
         let mut handled_line = false;
         let (_mode_info, file_event) =
             parse_diff_header_line(&self.line, self.source == Source::GitDiff);
-        let name = get_repeated_file_path_from_diff_line(&self.diff_line)
-            .unwrap_or_else(|| "".to_string());
+        let name = get_repeated_file_path_from_diff_line(&self.diff_line).unwrap_or_default();
         match file_event {
             FileEvent::Removed => {
                 self.minus_file = name;
@@ -224,7 +223,7 @@ impl<'a> StateMachine<'a> {
         if !self.mode_info.is_empty() {
             let format_label = |label: &str| {
                 if !label.is_empty() {
-                    format!("{} ", label)
+                    format!("{label} ")
                 } else {
                     "".to_string()
                 }
@@ -242,8 +241,7 @@ impl<'a> StateMachine<'a> {
                 _ => Cow::from(file),
             };
             let label = format_label(&self.config.file_modified_label);
-            let name = get_repeated_file_path_from_diff_line(&self.diff_line)
-                .unwrap_or_else(|| "".to_string());
+            let name = get_repeated_file_path_from_diff_line(&self.diff_line).unwrap_or_default();
             let line = format!("{}{}", label, format_file(&name));
             write_generic_diff_header_header_line(
                 &line,
@@ -374,6 +372,15 @@ fn get_repeated_file_path_from_diff_line(line: &str) -> Option<String> {
     None
 }
 
+fn remove_surrounding_quotes(path: &str) -> &str {
+    if path.starts_with('"') && path.ends_with('"') {
+        // Indexing into the UTF-8 string is safe because of the previous test
+        &path[1..path.len() - 1]
+    } else {
+        path
+    }
+}
+
 fn _parse_file_path(s: &str, git_diff_name: bool) -> String {
     // It appears that, if the file name contains a space, git appends a tab
     // character in the diff metadata lines, e.g.
@@ -382,13 +389,15 @@ fn _parse_file_path(s: &str, git_diff_name: bool) -> String {
     // index·d00491f..0cfbf08·100644␊
     // ---·a/a·b├──┤␊
     // +++·b/c·d├──┤␊
-    match s.strip_suffix('\t').unwrap_or(s) {
+    let path = match s.strip_suffix('\t').unwrap_or(s) {
         path if path == "/dev/null" => "/dev/null",
         path if git_diff_name && DIFF_PREFIXES.iter().any(|s| path.starts_with(s)) => &path[2..],
         path if git_diff_name => path,
         path => path.split('\t').next().unwrap_or(""),
-    }
-    .to_string()
+    };
+    // When a path contains non-ASCII characters, a backslash, or a quote then it is quoted,
+    // so remove these quotes. Characters may also be escaped, but these are left as-is.
+    remove_surrounding_quotes(path).to_string()
 }
 
 pub fn get_file_change_description_from_file_paths(
@@ -401,7 +410,7 @@ pub fn get_file_change_description_from_file_paths(
 ) -> String {
     let format_label = |label: &str| {
         if !label.is_empty() {
-            format!("{} ", label)
+            format!("{label} ")
         } else {
             "".to_string()
         }
@@ -539,7 +548,7 @@ mod tests {
         );
         for prefix in &DIFF_PREFIXES {
             assert_eq!(
-                parse_diff_header_line(&format!("--- {}src/delta.rs", prefix), true),
+                parse_diff_header_line(&format!("--- {prefix}src/delta.rs"), true),
                 ("src/delta.rs".to_string(), FileEvent::Change)
             );
         }
@@ -551,6 +560,11 @@ mod tests {
             parse_diff_header_line("+++ src/delta.rs", true),
             ("src/delta.rs".to_string(), FileEvent::Change)
         );
+
+        assert_eq!(
+            parse_diff_header_line("+++ \".\\delta.rs\"", true),
+            (".\\delta.rs".to_string(), FileEvent::Change)
+        );
     }
 
     #[test]
diff --git a/src/handlers/diff_stat.rs b/src/handlers/diff_stat.rs
index 321eccd..4c8a8b6 100644
--- a/src/handlers/diff_stat.rs
+++ b/src/handlers/diff_stat.rs
@@ -25,7 +25,7 @@ impl<'a> StateMachine<'a> {
                     relativize_path_in_diff_stat_line(&self.raw_line, cwd, self.config)
                 {
                     self.painter.emit()?;
-                    writeln!(self.painter.writer, "{}", replacement_line)?;
+                    writeln!(self.painter.writer, "{replacement_line}")?;
                     handled_line = true
                 }
             }
@@ -71,7 +71,7 @@ pub fn relativize_path_in_diff_stat_line(
         .diff_stat_align_width
         .saturating_sub(relative_path.len());
     let padding = " ".repeat(pad_width);
-    Some(format!(" {}{}{}", formatted_path, padding, suffix))
+    Some(format!(" {formatted_path}{padding}{suffix}"))
 }
 
 #[cfg(test)]
diff --git a/src/handlers/draw.rs b/src/handlers/draw.rs
index d73a31f..2e66558 100644
--- a/src/handlers/draw.rs
+++ b/src/handlers/draw.rs
@@ -62,7 +62,7 @@ fn write_no_decoration(
     _decoration_style: ansi_term::Style,
 ) -> std::io::Result<()> {
     if text_style.is_raw {
-        writeln!(writer, "{}", raw_text)?;
+        writeln!(writer, "{raw_text}")?;
     } else {
         writeln!(writer, "{}", paint_text(text_style, text, addendum))?;
     }
@@ -233,7 +233,7 @@ fn _write_under_or_over_lined(
         _ => write_line(writer)?,
     }
     if text_style.is_raw {
-        writeln!(writer, "{}", raw_text)?;
+        writeln!(writer, "{raw_text}")?;
     } else {
         writeln!(writer, "{}", paint_text(text_style, text, addendum))?;
     }
@@ -319,7 +319,7 @@ fn write_boxed_partial(
         decoration_style.paint(down_left),
     )?;
     if text_style.is_raw {
-        write!(writer, "{}", raw_text)?;
+        write!(writer, "{raw_text}")?;
     } else {
         write!(writer, "{}", paint_text(text_style, text, addendum))?;
     }
diff --git a/src/handlers/grep.rs b/src/handlers/grep.rs
index 986de38..2356f9e 100644
--- a/src/handlers/grep.rs
+++ b/src/handlers/grep.rs
@@ -421,8 +421,6 @@ fn make_grep_line_regex(regex_variant: GrepLineRegex) -> Regex {
 (.*)                     # 8. code (i.e. line contents)
 $
 ",
-        file_path = file_path,
-        separator = separator,
     ))
     .unwrap()
 }
@@ -439,7 +437,7 @@ pub fn parse_grep_line(line: &str) -> Option<GrepLine> {
                 &*GREP_LINE_REGEX_ASSUMING_NO_INTERNAL_SEPARATOR_CHARS,
             ]
             .iter()
-            .find_map(|regex| _parse_grep_line(*regex, line)),
+            .find_map(|regex| _parse_grep_line(regex, line)),
             _ => None,
         }
     }
diff --git a/src/handlers/hunk.rs b/src/handlers/hunk.rs
index 2abd418..b5c777c 100644
--- a/src/handlers/hunk.rs
+++ b/src/handlers/hunk.rs
@@ -196,8 +196,7 @@ fn new_line_state(
             Combined(Number(*n), in_merge_conflict.clone())
         }
         _ => delta_unreachable(&format!(
-            "Unexpected state in new_line_state: {:?}",
-            prev_state
+            "Unexpected state in new_line_state: {prev_state:?}",
         )),
     };
 
diff --git a/src/handlers/hunk_header.rs b/src/handlers/hunk_header.rs
index add903d..251e341 100644
--- a/src/handlers/hunk_header.rs
+++ b/src/handlers/hunk_header.rs
@@ -215,7 +215,7 @@ pub fn write_hunk_header(
     let line = if config.color_only {
         line.to_string()
     } else if !code_fragment.is_empty() {
-        format!("{} ", code_fragment)
+        format!("{code_fragment} ")
     } else {
         "".to_string()
     };
@@ -292,8 +292,7 @@ fn write_to_output_buffer(
         let space = if line.is_empty() { " " } else { "" };
         let _ = write!(
             &mut painter.output_buffer,
-            "{}:{}",
-            file_with_line_number, space
+            "{file_with_line_number}:{space}",
         );
     }
     if !line.is_empty() {
diff --git a/src/handlers/merge_conflict.rs b/src/handlers/merge_conflict.rs
index d7c4524..f06364b 100644
--- a/src/handlers/merge_conflict.rs
+++ b/src/handlers/merge_conflict.rs
@@ -125,7 +125,7 @@ impl<'a> StateMachine<'a> {
             self.painter.merge_conflict_lines[commit].push((line, state));
             true
         } else {
-            delta_unreachable(&format!("Invalid state: {:?}", state))
+            delta_unreachable(&format!("Invalid state: {state:?}"))
         }
     }
 
diff --git a/src/main.rs b/src/main.rs
index 625b513..a8a08c1 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -44,7 +44,7 @@ where
 {
     #[cfg(not(test))]
     {
-        eprintln!("{}", errmsg);
+        eprintln!("{errmsg}");
         // As in Config::error_exit_code: use 2 for error
         // because diff uses 0 and 1 for non-error.
         process::exit(2);
@@ -74,7 +74,7 @@ fn main() -> std::io::Result<()> {
     // Ignore ctrl-c (SIGINT) to avoid leaving an orphaned pager process.
     // See https://github.com/dandavison/delta/issues/681
     ctrlc::set_handler(|| {})
-        .unwrap_or_else(|err| eprintln!("Failed to set ctrl-c handler: {}", err));
+        .unwrap_or_else(|err| eprintln!("Failed to set ctrl-c handler: {err}"));
     let exit_code = run_app()?;
     // when you call process::exit, no destructors are called, so we want to do it only once, here
     process::exit(exit_code);
@@ -116,7 +116,7 @@ fn run_app() -> std::io::Result<i32> {
         if let Err(error) = result {
             match error.kind() {
                 ErrorKind::BrokenPipe => {}
-                _ => fatal(format!("{}", error)),
+                _ => fatal(format!("{error}")),
             }
         }
         return Ok(0);
@@ -145,7 +145,7 @@ fn run_app() -> std::io::Result<i32> {
         eprintln!(
             "\
     The main way to use delta is to configure it as the pager for git: \
-    see https://github.com/dandavison/delta#configuration. \
+    see https://github.com/dandavison/delta#get-started. \
     You can also use delta to diff two files: `delta file_A file_B`."
         );
         return Ok(config.error_exit_code);
@@ -154,7 +154,7 @@ fn run_app() -> std::io::Result<i32> {
     if let Err(error) = delta(io::stdin().lock().byte_lines(), &mut writer, &config) {
         match error.kind() {
             ErrorKind::BrokenPipe => return Ok(0),
-            _ => eprintln!("{}", error),
+            _ => eprintln!("{error}"),
         }
     };
     Ok(0)
diff --git a/src/options/get.rs b/src/options/get.rs
index a09d166..3258a10 100644
--- a/src/options/get.rs
+++ b/src/options/get.rs
@@ -75,7 +75,7 @@ pub trait GetOptionValue {
         Self: Into<OptionValue>,
     {
         if let Some(git_config) = git_config {
-            if let Some(value) = git_config.get::<Self>(&format!("delta.{}", option_name)) {
+            if let Some(value) = git_config.get::<Self>(&format!("delta.{option_name}")) {
                 return Some(value);
             }
         }
@@ -114,9 +114,7 @@ pub trait GetOptionValue {
         Self: Into<OptionValue>,
     {
         if let Some(git_config) = git_config {
-            if let Some(value) =
-                git_config.get::<Self>(&format!("delta.{}.{}", feature, option_name))
-            {
+            if let Some(value) = git_config.get::<Self>(&format!("delta.{feature}.{option_name}")) {
                 return Some(GitConfigValue(value.into()));
             }
         }
diff --git a/src/options/set.rs b/src/options/set.rs
index 7cb8b0a..1f0370b 100644
--- a/src/options/set.rs
+++ b/src/options/set.rs
@@ -432,7 +432,7 @@ fn gather_features_recursively(
     } else {
         features.push_front(feature.to_string());
     }
-    if let Some(child_features) = git_config.get::<String>(&format!("delta.{}.features", feature)) {
+    if let Some(child_features) = git_config.get::<String>(&format!("delta.{feature}.features")) {
         for child_feature in split_feature_string(&child_features) {
             if !features.contains(&child_feature.to_string()) {
                 gather_features_recursively(
@@ -446,7 +446,7 @@ fn gather_features_recursively(
         }
     }
     gather_builtin_features_from_flags_in_gitconfig(
-        &format!("delta.{}", feature),
+        &format!("delta.{feature}"),
         features,
         builtin_features,
         opt,
@@ -464,8 +464,7 @@ fn gather_builtin_features_from_flags_in_gitconfig(
     git_config: &GitConfig,
 ) {
     for child_feature in builtin_features.keys() {
-        if let Some(true) = git_config.get::<bool>(&format!("{}.{}", git_config_key, child_feature))
-        {
+        if let Some(true) = git_config.get::<bool>(&format!("{git_config_key}.{child_feature}")) {
             gather_builtin_features_recursively(child_feature, features, builtin_features, opt);
         }
     }
@@ -532,8 +531,7 @@ impl FromStr for cli::InspectRawLines {
             "false" => Ok(Self::False),
             _ => {
                 fatal(format!(
-                    r#"Invalid value for inspect-raw-lines option: {}. Valid values are "true", and "false"."#,
-                    s
+                    r#"Invalid value for inspect-raw-lines option: {s}. Valid values are "true", and "false"."#,
                 ));
             }
         }
@@ -547,8 +545,7 @@ fn parse_paging_mode(paging_mode_string: &str) -> PagingMode {
         "auto" => PagingMode::QuitIfOneScreen,
         _ => {
             fatal(format!(
-                "Invalid value for --paging option: {} (valid values are \"always\", \"never\", and \"auto\")",
-                paging_mode_string
+                "Invalid value for --paging option: {paging_mode_string} (valid values are \"always\", \"never\", and \"auto\")",
             ));
         }
     }
@@ -567,16 +564,11 @@ fn parse_width_specifier(width_arg: &str, terminal_width: usize) -> Result<usize
         .map_err(|_| {
             let pos = if must_be_negative { " negative" } else { "n" };
             let subexpr = if subexpression {
-                format!(" (from {:?})", width_arg)
+                format!(" (from {width_arg:?})")
             } else {
                 "".into()
             };
-            format!(
-                "{:?}{subexpr} is not a{pos} integer",
-                width,
-                subexpr = subexpr,
-                pos = pos
-            )
+            format!("{width:?}{subexpr} is not a{pos} integer")
         })
     };
 
@@ -596,7 +588,7 @@ fn parse_width_specifier(width_arg: &str, terminal_width: usize) -> Result<usize
             let b = parse(&width_arg[index..], true, true)?;
             (a + b)
                 .try_into()
-                .map_err(|_| format!("expression {:?} is not positive", width_arg))?
+                .map_err(|_| format!("expression {width_arg:?} is not positive"))?
         }
     };
 
@@ -609,14 +601,15 @@ fn set_widths_and_isatty(opt: &mut cli::Opt) {
 
     // If one extra character for e.g. `less --status-column` is required use "-1"
     // as an argument, also see #41, #10, #115 and #727.
-    opt.computed.available_terminal_width = term_stdout.size().1 as usize;
+    opt.computed.available_terminal_width =
+        crate::utils::workarounds::windows_msys2_width_fix(term_stdout.size(), &term_stdout);
 
     let (decorations_width, background_color_extends_to_terminal_width) = match opt.width.as_deref()
     {
         Some("variable") => (cli::Width::Variable, false),
         Some(width) => {
             let width = parse_width_specifier(width, opt.computed.available_terminal_width)
-                .unwrap_or_else(|err| fatal(format!("Invalid value for width: {}", err)));
+                .unwrap_or_else(|err| fatal(format!("Invalid value for width: {err}")));
             (cli::Width::Fixed(width), true)
         }
         None => (
@@ -775,7 +768,7 @@ pub mod tests {
         assert_eq!(opt.line_numbers_right_format, "xxxyyyzzz");
         assert_eq!(opt.line_numbers_right_style, "black black");
         assert_eq!(opt.line_numbers_zero_style, "black black");
-        assert_eq!(opt.max_line_distance, 77 as f64);
+        assert_eq!(opt.max_line_distance, 77.0);
         assert_eq!(opt.max_line_length, 77);
         assert_eq!(opt.minus_emph_style, "black black");
         assert_eq!(opt.minus_empty_line_marker_style, "black black");
diff --git a/src/paint.rs b/src/paint.rs
index 3ad20f2..6ae67d5 100644
--- a/src/paint.rs
+++ b/src/paint.rs
@@ -806,7 +806,7 @@ pub fn paint_file_path_with_line_number(
             if !file_with_line_number.is_empty() {
                 file_with_line_number.push(ansi_term::ANSIString::from(separator));
             }
-            file_with_line_number.push(line_number_style.paint(format!("{}", line_number)))
+            file_with_line_number.push(line_number_style.paint(format!("{line_number}")))
         }
     }
     if terminate_with_separator {
diff --git a/src/parse_style.rs b/src/parse_style.rs
index eaeba24..f6e6d25 100644
--- a/src/parse_style.rs
+++ b/src/parse_style.rs
@@ -214,8 +214,7 @@ fn parse_ansi_term_style(
             seen_background = true;
         } else {
             fatal(format!(
-                "Invalid style string: {}. See the STYLES section of delta --help.",
-                s
+                "Invalid style string: {s}. See the STYLES section of delta --help.",
             ));
         }
     }
diff --git a/src/parse_styles.rs b/src/parse_styles.rs
index 4736db3..d13dd0a 100644
--- a/src/parse_styles.rs
+++ b/src/parse_styles.rs
@@ -68,7 +68,7 @@ fn resolve_style_references(
         loop {
             if !visited.insert(node) {
                 #[cfg(not(test))]
-                fatal(format!("Your delta styles form a cycle! {:?}", visited));
+                fatal(format!("Your delta styles form a cycle! {visited:?}"));
                 #[cfg(test)]
                 return [("__cycle__", Style::default())]
                     .iter()
@@ -100,18 +100,16 @@ fn parse_as_style_or_reference_to_git_config(style_string: &str, opt: &cli::Opt)
 
 fn parse_as_reference_to_git_config(style_string: &str, opt: &cli::Opt) -> Style {
     if let Some(git_config) = &opt.git_config {
-        let git_config_key = format!("delta.{}", style_string);
+        let git_config_key = format!("delta.{style_string}");
         match git_config.get::<String>(&git_config_key) {
             Some(s) => Style::from_git_str(&s),
             _ => fatal(format!(
-                "Style key not found in git config: {}",
-                git_config_key
+                "Style key not found in git config: {git_config_key}",
             )),
         }
     } else {
         fatal(format!(
-            "Style not found (git config unavailable): {}",
-            style_string
+            "Style not found (git config unavailable): {style_string}",
         ));
     }
 }
diff --git a/src/style.rs b/src/style.rs
index 925d7e7..aaf4273 100644
--- a/src/style.rs
+++ b/src/style.rs
@@ -189,7 +189,7 @@ impl fmt::Display for Style {
             words.push(color::color_to_string(color))
         }
         let style_str = words.join(" ");
-        write!(f, "{}", style_str)
+        write!(f, "{style_str}")
     }
 }
 
@@ -479,23 +479,23 @@ pub mod tests {
     #[test]
     fn test_style_compact_debug_fmt() {
         let mut s = Style::new();
-        assert_eq!(format!("{:?}", s), "Style { <aeorsd> }");
+        assert_eq!(format!("{s:?}"), "Style { <aeorsd> }");
         s.is_emph = true;
-        assert_eq!(format!("{:?}", s), "Style { <aEorsd> }");
+        assert_eq!(format!("{s:?}"), "Style { <aEorsd> }");
         s.ansi_term_style = ansi_term::Style::new().bold();
         assert_eq!(
-            format!("{:?}", s),
+            format!("{s:?}"),
             "Style { ansi_term_style: Style { bold }, <Eorsd> }"
         );
-        s.decoration_style = DecorationStyle::Underline(s.ansi_term_style.clone());
+        s.decoration_style = DecorationStyle::Underline(s.ansi_term_style);
         assert_eq!(
-            format!("{:?}", s),
+            format!("{s:?}"),
             "Style { ansi_term_style: Style { bold }, <Eors>, \
                   decoration_style: Underline(Style { bold }) }"
         );
         s.ansi_term_style = ansi_term::Style::default();
         assert_eq!(
-            format!("{:?}", s),
+            format!("{s:?}"),
             "Style { <aEors>, decoration_style: Underline(Style { bold }) }"
         );
     }
diff --git a/src/subcommands/diff.rs b/src/subcommands/diff.rs
index 6c7fbf0..ddaf44d 100644
--- a/src/subcommands/diff.rs
+++ b/src/subcommands/diff.rs
@@ -33,19 +33,19 @@ pub fn diff(
     let diff_path = match grep_cli::resolve_binary(PathBuf::from(diff_bin)) {
         Ok(path) => path,
         Err(err) => {
-            eprintln!("Failed to resolve command '{}': {}", diff_bin, err);
+            eprintln!("Failed to resolve command '{diff_bin}': {err}");
             return config.error_exit_code;
         }
     };
 
     let diff_process = process::Command::new(diff_path)
         .args(&diff_cmd[1..])
-        .args(&[minus_file, plus_file])
+        .args([minus_file, plus_file])
         .stdout(process::Stdio::piped())
         .spawn();
 
     if let Err(err) = diff_process {
-        eprintln!("Failed to execute the command '{}': {}", diff_bin, err);
+        eprintln!("Failed to execute the command '{diff_bin}': {err}");
         return config.error_exit_code;
     }
     let mut diff_process = diff_process.unwrap();
@@ -58,7 +58,7 @@ pub fn diff(
         match error.kind() {
             ErrorKind::BrokenPipe => return 0,
             _ => {
-                eprintln!("{}", error);
+                eprintln!("{error}");
                 return config.error_exit_code;
             }
         }
@@ -70,18 +70,18 @@ pub fn diff(
     diff_process
         .wait()
         .unwrap_or_else(|_| {
-            delta_unreachable(&format!("'{}' process not running.", diff_bin));
+            delta_unreachable(&format!("'{diff_bin}' process not running."));
         })
         .code()
         .unwrap_or_else(|| {
-            eprintln!("'{}' process terminated without exit status.", diff_bin);
+            eprintln!("'{diff_bin}' process terminated without exit status.");
             config.error_exit_code
         })
 }
 
 #[cfg(test)]
 mod main_tests {
-    use std::io::{Cursor, Read, Seek, SeekFrom};
+    use std::io::{Cursor, Read, Seek};
     use std::path::PathBuf;
 
     use super::diff;
@@ -125,7 +125,7 @@ mod main_tests {
 
     fn _read_to_string(cursor: &mut Cursor<Vec<u8>>) -> String {
         let mut s = String::new();
-        cursor.seek(SeekFrom::Start(0)).unwrap();
+        cursor.rewind().unwrap();
         cursor.read_to_string(&mut s).unwrap();
         s
     }
diff --git a/src/subcommands/list_syntax_themes.rs b/src/subcommands/list_syntax_themes.rs
index 34a7b0b..621932e 100644
--- a/src/subcommands/list_syntax_themes.rs
+++ b/src/subcommands/list_syntax_themes.rs
@@ -19,12 +19,12 @@ pub fn _list_syntax_themes_for_humans(writer: &mut dyn Write) -> std::io::Result
     let assets = utils::bat::assets::load_highlighting_assets();
 
     writeln!(writer, "Light syntax themes:")?;
-    for theme in assets.themes().filter(|t| is_light_syntax_theme(*t)) {
-        writeln!(writer, "    {}", theme)?;
+    for theme in assets.themes().filter(|t| is_light_syntax_theme(t)) {
+        writeln!(writer, "    {theme}")?;
     }
     writeln!(writer, "\nDark syntax themes:")?;
-    for theme in assets.themes().filter(|t| !is_light_syntax_theme(*t)) {
-        writeln!(writer, "    {}", theme)?;
+    for theme in assets.themes().filter(|t| !is_light_syntax_theme(t)) {
+        writeln!(writer, "    {theme}")?;
     }
     writeln!(
         writer,
@@ -35,7 +35,7 @@ pub fn _list_syntax_themes_for_humans(writer: &mut dyn Write) -> std::io::Result
 
 pub fn _list_syntax_themes_for_machines(writer: &mut dyn Write) -> std::io::Result<()> {
     let assets = utils::bat::assets::load_highlighting_assets();
-    for theme in assets.themes().sorted_by_key(|t| is_light_syntax_theme(*t)) {
+    for theme in assets.themes().sorted_by_key(|t| is_light_syntax_theme(t)) {
         writeln!(
             writer,
             "{}\t{}",
@@ -52,7 +52,7 @@ pub fn _list_syntax_themes_for_machines(writer: &mut dyn Write) -> std::io::Resu
 
 #[cfg(test)]
 mod tests {
-    use std::io::{Cursor, Read, Seek, SeekFrom};
+    use std::io::{Cursor, Read, Seek};
 
     use super::*;
 
@@ -61,7 +61,7 @@ mod tests {
         let mut writer = Cursor::new(vec![0; 512]);
         _list_syntax_themes_for_humans(&mut writer).unwrap();
         let mut s = String::new();
-        writer.seek(SeekFrom::Start(0)).unwrap();
+        writer.rewind().unwrap();
         writer.read_to_string(&mut s).unwrap();
         assert!(s.contains("Light syntax themes:\n"));
         assert!(s.contains("    GitHub\n"));
@@ -74,7 +74,7 @@ mod tests {
         let mut writer = Cursor::new(vec![0; 512]);
         _list_syntax_themes_for_machines(&mut writer).unwrap();
         let mut s = String::new();
-        writer.seek(SeekFrom::Start(0)).unwrap();
+        writer.rewind().unwrap();
         writer.read_to_string(&mut s).unwrap();
         assert!(s.contains("light	GitHub\n"));
         assert!(s.contains("dark	Dracula\n"));
diff --git a/src/subcommands/show_colors.rs b/src/subcommands/show_colors.rs
index 7aaa716..4ad1958 100644
--- a/src/subcommands/show_colors.rs
+++ b/src/subcommands/show_colors.rs
@@ -12,8 +12,6 @@ use crate::utils::bat::output::{OutputType, PagingMode};
 
 #[cfg(not(tarpaulin_include))]
 pub fn show_colors() -> std::io::Result<()> {
-    use itertools::Itertools;
-
     use crate::{delta::DiffType, utils};
 
     let assets = utils::bat::assets::load_highlighting_assets();
@@ -38,7 +36,7 @@ pub fn show_colors() -> std::io::Result<()> {
         is_syntax_highlighted: true,
         ..style::Style::default()
     };
-    for (group, color_names) in colors::color_groups().iter().sorted() {
+    for (group, color_names) in colors::color_groups() {
         writeln!(painter.writer, "\n\n{}\n", title_style.paint(group))?;
         for (color_name, hex) in color_names {
             // Two syntax-highlighted lines without background color
@@ -60,11 +58,8 @@ pub fn show_colors() -> std::io::Result<()> {
                     .unwrap();
             style.ansi_term_style.background = Some(color);
             for line in [
-                &format!(
-                    r#"export function color(): string {{ return "{}" }}"#,
-                    color_name
-                ),
-                &format!(r#"export function hex(): string {{ return "{}" }}"#, hex),
+                &format!(r#"export function color(): string {{ return "{color_name}" }}"#),
+                &format!(r#"export function hex(): string {{ return "{hex}" }}"#),
             ] {
                 painter.syntax_highlight_and_paint_line(
                     line,
diff --git a/src/subcommands/show_config.rs b/src/subcommands/show_config.rs
index 0c6cc93..e165e7c 100644
--- a/src/subcommands/show_config.rs
+++ b/src/subcommands/show_config.rs
@@ -159,7 +159,7 @@ pub fn show_config(config: &config::Config, writer: &mut dyn Write) -> std::io::
             cli::Width::Variable => "variable".to_string(),
         },
         tab_width = config.tab_width,
-        tokenization_regex = format_option_value(&config.tokenization_regex.to_string()),
+        tokenization_regex = format_option_value(config.tokenization_regex.to_string()),
     )?;
     Ok(())
 }
@@ -176,7 +176,7 @@ where
         || s.contains(&['\\', '{', '}', ':'][..])
         || s.is_empty()
     {
-        format!("'{}'", s)
+        format!("'{s}'")
     } else {
         s.to_string()
     }
@@ -188,7 +188,7 @@ mod tests {
 
     use super::*;
     use crate::ansi;
-    use std::io::{Cursor, Read, Seek, SeekFrom};
+    use std::io::{Cursor, Read, Seek};
 
     #[test]
     fn test_show_config() {
@@ -196,7 +196,7 @@ mod tests {
         let mut writer = Cursor::new(vec![0; 1024]);
         show_config(&config, &mut writer).unwrap();
         let mut s = String::new();
-        writer.seek(SeekFrom::Start(0)).unwrap();
+        writer.rewind().unwrap();
         writer.read_to_string(&mut s).unwrap();
         let s = ansi::strip_ansi_codes(&s);
         assert!(s.contains("    commit-style                  = raw\n"));
diff --git a/src/subcommands/show_syntax_themes.rs b/src/subcommands/show_syntax_themes.rs
index 15fe0e2..d3b5e19 100644
--- a/src/subcommands/show_syntax_themes.rs
+++ b/src/subcommands/show_syntax_themes.rs
@@ -100,7 +100,7 @@ index f38589a..0f1bb83 100644
         {
             match error.kind() {
                 ErrorKind::BrokenPipe => std::process::exit(0),
-                _ => eprintln!("{}", error),
+                _ => eprintln!("{error}"),
             }
         };
     }
@@ -109,7 +109,7 @@ index f38589a..0f1bb83 100644
 
 #[cfg(test)]
 mod tests {
-    use std::io::{Cursor, Seek, SeekFrom};
+    use std::io::{Cursor, Seek};
 
     use super::*;
     use crate::ansi;
@@ -123,11 +123,11 @@ mod tests {
         let mut writer = Cursor::new(vec![0; 1024]);
         _show_syntax_themes(opt, true, &mut writer, None).unwrap();
         let mut s = String::new();
-        writer.seek(SeekFrom::Start(0)).unwrap();
+        writer.rewind().unwrap();
         writer.read_to_string(&mut s).unwrap();
         let s = ansi::strip_ansi_codes(&s);
         assert!(s.contains("\nSyntax theme: gruvbox-light\n"));
-        println!("{}", s);
+        println!("{s}");
         assert!(s.contains("\nfn print_cube(num: f64) {\n"));
     }
 }
diff --git a/src/subcommands/show_themes.rs b/src/subcommands/show_themes.rs
index f168c21..ba530b6 100644
--- a/src/subcommands/show_themes.rs
+++ b/src/subcommands/show_themes.rs
@@ -29,7 +29,7 @@ pub fn show_themes(dark: bool, light: bool, computed_theme_is_light: bool) -> st
     let git_config = git_config::GitConfig::try_create(&env);
     let opt = cli::Opt::from_iter_and_git_config(
         env.clone(),
-        &["", "", "--navigate", "--show-themes"],
+        &["delta", "--navigate", "--show-themes"],
         git_config,
     );
     let mut output_type =
@@ -41,7 +41,7 @@ pub fn show_themes(dark: bool, light: bool, computed_theme_is_light: bool) -> st
         let git_config = git_config::GitConfig::try_create(&env);
         let opt = cli::Opt::from_iter_and_git_config(
             env.clone(),
-            &["", "", "--features", theme],
+            &["delta", "--features", theme],
             git_config,
         );
         let is_dark_theme = opt.dark;
@@ -59,7 +59,7 @@ pub fn show_themes(dark: bool, light: bool, computed_theme_is_light: bool) -> st
             {
                 match error.kind() {
                     ErrorKind::BrokenPipe => std::process::exit(0),
-                    _ => eprintln!("{}", error),
+                    _ => eprintln!("{error}"),
                 }
             }
         }
diff --git a/src/utils/bat/assets.rs b/src/utils/bat/assets.rs
index 0eb3049..23a2226 100644
--- a/src/utils/bat/assets.rs
+++ b/src/utils/bat/assets.rs
@@ -70,7 +70,7 @@ pub fn list_languages() -> std::io::Result<()> {
                 num_chars += new_chars;
                 write!(stdout, "{}", style.paint(&word[..]))?;
                 if extension.peek().is_some() {
-                    write!(stdout, "{}", comma_separator)?;
+                    write!(stdout, "{comma_separator}")?;
                 }
             }
             writeln!(stdout)?;
diff --git a/src/utils/bat/output.rs b/src/utils/bat/output.rs
index c3ee6a8..96baa0e 100644
--- a/src/utils/bat/output.rs
+++ b/src/utils/bat/output.rs
@@ -130,7 +130,7 @@ fn _make_process_from_less_path(
     config: &config::Config,
 ) -> Option<Command> {
     if let Ok(less_path) = grep_cli::resolve_binary(less_path) {
-        let mut p = Command::new(&less_path);
+        let mut p = Command::new(less_path);
         if args.is_empty() || replace_arguments_to_less {
             p.args(vec!["--RAW-CONTROL-CHARS"]);
 
@@ -184,7 +184,7 @@ delta is not an appropriate value for $PAGER \
         );
     }
     if let Ok(pager_path) = grep_cli::resolve_binary(pager_path) {
-        let mut p = Command::new(&pager_path);
+        let mut p = Command::new(pager_path);
         p.args(args);
         Some(p)
     } else {
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index 9cd8cfa..257c7b0 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -5,3 +5,4 @@ pub mod process;
 pub mod regex_replacement;
 pub mod round_char_boundary;
 pub mod syntect;
+pub mod workarounds;
diff --git a/src/utils/path.rs b/src/utils/path.rs
index 6891e81..524ef91 100644
--- a/src/utils/path.rs
+++ b/src/utils/path.rs
@@ -29,7 +29,7 @@ pub fn absolute_path(relative_path: &str, config: &Config) -> Option<PathBuf> {
 pub fn relativize_path_maybe(path: &str, config: &Config) -> Option<PathBuf> {
     if config.relative_paths && !calling_process().paths_in_input_are_relative_to_cwd() {
         if let Some(base) = config.cwd_relative_to_repo_root.as_deref() {
-            pathdiff::diff_paths(&path, base)
+            pathdiff::diff_paths(path, base)
         } else {
             None
         }
diff --git a/src/utils/process.rs b/src/utils/process.rs
index 1f24aa0..f319981 100644
--- a/src/utils/process.rs
+++ b/src/utils/process.rs
@@ -264,7 +264,7 @@ fn parse_command_line<'a>(args: impl Iterator<Item = &'a str>) -> CommandLine {
         } else if s.starts_with("--") {
             long_options.insert(s.split('=').next().unwrap().to_owned());
         } else if let Some(suffix) = s.strip_prefix('-') {
-            short_options.extend(suffix.chars().map(|c| format!("-{}", c)));
+            short_options.extend(suffix.chars().map(|c| format!("-{c}")));
         } else {
             last_arg = Some(s);
         }
@@ -332,14 +332,14 @@ trait ProcessInterface {
     fn refresh_processes(&mut self);
 
     fn parent_process(&mut self, pid: DeltaPid) -> Option<&Self::Out> {
-        self.refresh_process(pid).then(|| ())?;
+        self.refresh_process(pid).then_some(())?;
         let parent_pid = self.process(pid)?.parent()?;
-        self.refresh_process(parent_pid).then(|| ())?;
+        self.refresh_process(parent_pid).then_some(())?;
         self.process(parent_pid)
     }
     fn naive_sibling_process(&mut self, pid: DeltaPid) -> Option<&Self::Out> {
         let sibling_pid = pid - 1;
-        self.refresh_process(sibling_pid).then(|| ())?;
+        self.refresh_process(sibling_pid).then_some(())?;
         self.process(sibling_pid)
     }
     fn find_sibling_in_refreshed_processes<F, T>(
diff --git a/src/utils/syntect.rs b/src/utils/syntect.rs
index 99b358d..0508c8b 100644
--- a/src/utils/syntect.rs
+++ b/src/utils/syntect.rs
@@ -21,7 +21,7 @@ pub fn syntect_color_from_name(name: &str) -> Option<Color> {
 /// Convert 8-bit ANSI code to #RGBA string with ANSI code in red channel and 0 in alpha channel.
 // See https://github.com/sharkdp/bat/pull/543
 pub fn syntect_color_from_ansi_number(n: u8) -> Option<Color> {
-    Color::from_str(&format!("#{:02x}000000", n)).ok()
+    Color::from_str(&format!("#{n:02x}000000")).ok()
 }
 
 pub trait FromAnsiTermStyle {
diff --git a/src/utils/workarounds.rs b/src/utils/workarounds.rs
new file mode 100644
index 0000000..a47a1db
--- /dev/null
+++ b/src/utils/workarounds.rs
@@ -0,0 +1,60 @@
+// env var which should disable workarounds
+const NO_WORKAROUNDS: &str = "DELTA_NO_WORKAROUNDS";
+
+// Work around a bug in the 'console' crate (inherited from 'terminal-size', #25): On Windows
+// it can not determine the width of an MSYS2 / MINGW64 terminal (e.g. from Git-Bash) correctly.
+// Instead use the usually included stty util from the MSYS2 distribution.
+#[cfg(target_os = "windows")]
+pub fn windows_msys2_width_fix(height_width: (u16, u16), term_stdout: &console::Term) -> usize {
+    fn guess_real_width(current_width: u16, term_stdout: &console::Term) -> Option<u16> {
+        use std::process::{Command, Stdio};
+
+        let term_var = std::env::var("TERM").ok()?;
+        // More checks before actually calling stty.
+        if term_var.starts_with("xterm")
+            && term_stdout.is_term()
+            && term_stdout.features().is_msys_tty()
+        {
+            if std::env::var(NO_WORKAROUNDS).is_ok() {
+                return Some(current_width);
+            }
+
+            // stderr/2 is passed to the Command below.
+            let pseudo_term = "/dev/fd/2";
+
+            // Read width via stty helper program (e.g. "C:\Program Files\Git\usr\bin\stty.exe")
+            // which gets both the MSYS2 and cmd.exe width right.
+            let result = Command::new("stty")
+                .stderr(Stdio::inherit())
+                .arg("-F")
+                .arg(pseudo_term)
+                .arg("size")
+                .output()
+                .ok()?;
+
+            if result.status.success() {
+                let size = std::str::from_utf8(&result.stdout).ok()?;
+                let mut it = size.split_whitespace();
+                let _height = it.next()?;
+                return it.next().map(|width| width.parse().ok())?;
+            }
+        }
+        None
+    }
+
+    // Calling an external binary is slow, so make sure this is actually necessary.
+    // The fallback values of 25 lines by 80 columns (sometimes zero indexed) are a good
+    // indicator.
+    let (height, width) = height_width;
+    match (height, width) {
+        (24..=25, 79..=80) => guess_real_width(width, term_stdout).unwrap_or(width),
+        _ => width,
+    }
+    .into()
+}
+
+#[cfg(not(target_os = "windows"))]
+pub fn windows_msys2_width_fix(height_width: (u16, u16), _: &console::Term) -> usize {
+    let _ = NO_WORKAROUNDS;
+    height_width.1.into()
+}
diff --git a/src/wrapping.rs b/src/wrapping.rs
index 771d7f7..40eff4f 100644
--- a/src/wrapping.rs
+++ b/src/wrapping.rs
@@ -1,5 +1,6 @@
 use syntect::highlighting::Style as SyntectStyle;
 use unicode_segmentation::UnicodeSegmentation;
+use unicode_width::UnicodeWidthStr;
 
 use crate::cli;
 use crate::config::INLINE_SYMBOL_WIDTH_1;
@@ -79,7 +80,7 @@ impl WrapConfig {
             wrap_max_lines => {
                 let single_pane_width = available_terminal_width / 2;
                 let add_25_percent_or_term_width =
-                    |x| x + std::cmp::max((x * 250) / 1000, single_pane_width) as usize;
+                    |x| x + std::cmp::max((x * 250) / 1000, single_pane_width);
                 std::cmp::max(
                     max_line_length,
                     add_25_percent_or_term_width(single_pane_width * wrap_max_lines),
@@ -100,8 +101,7 @@ fn ensure_display_width_1(what: &str, arg: String) -> String {
     match arg.grapheme_indices(true).count() {
         INLINE_SYMBOL_WIDTH_1 => arg,
         width => fatal(format!(
-            "Invalid value for {}, display width of \"{}\" must be {} but is {}",
-            what, arg, INLINE_SYMBOL_WIDTH_1, width
+            "Invalid value for {what}, display width of \"{arg}\" must be {INLINE_SYMBOL_WIDTH_1} but is {width}",
         )),
     }
 }
@@ -111,7 +111,7 @@ fn adapt_wrap_max_lines_argument(arg: String) -> usize {
         0
     } else {
         arg.parse::<usize>()
-            .unwrap_or_else(|err| fatal(format!("Invalid wrap-max-lines argument: {}", err)))
+            .unwrap_or_else(|err| fatal(format!("Invalid wrap-max-lines argument: {err}")))
             + 1
     }
 }
@@ -202,11 +202,21 @@ where
 
         let (style, text, graphemes) = stack
             .pop()
-            .map(|(style, text)| (style, text, text.grapheme_indices(true).collect::<Vec<_>>()))
+            .map(|(style, text)| {
+                (
+                    style,
+                    text,
+                    text.graphemes(true)
+                        .map(|item| (item.len(), item.width()))
+                        .collect::<Vec<_>>(),
+                )
+            })
             .unwrap();
 
-        let new_len = curr_line.len + graphemes.len();
+        let graphemes_width: usize = graphemes.iter().map(|(_, w)| w).sum();
+        let new_len = curr_line.len + graphemes_width;
 
+        #[allow(clippy::comparison_chain)]
         let must_split = if new_len < line_width {
             curr_line.push_and_set_len((style, text), new_len);
             false
@@ -228,15 +238,6 @@ where
                 }
                 _ => true,
             }
-        } else if new_len == line_width + 1 && stack.is_empty() {
-            // If the one overhanging char is '\n' then keep it on the current line.
-            if text.ends_with('\n') {
-                // Do not count the included '\n': - 1
-                curr_line.push_and_set_len((style, text), new_len - 1);
-                false
-            } else {
-                true
-            }
         } else {
             true
         };
@@ -244,16 +245,29 @@ where
         // Text must be split, one part (or just `wrap_symbol`) is added to the
         // current line, the other is pushed onto the stack.
         if must_split {
-            let grapheme_split_pos = graphemes.len() - (new_len - line_width) - 1;
+            let mut width_left = graphemes_width
+                .saturating_sub(new_len - line_width)
+                .saturating_sub(wrap_config.left_symbol.width());
 
             // The length does not matter anymore and `curr_line` will be reset
             // at the end, so move the line segments out.
             let mut line_segments = curr_line.line_segments;
 
-            let next_line = if grapheme_split_pos == 0 {
+            let next_line = if width_left == 0 {
                 text
             } else {
-                let byte_split_pos = graphemes[grapheme_split_pos].0;
+                let mut byte_split_pos = 0;
+                // After loop byte_split_pos may still equal to 0. If width_left
+                // is less than the width of first character, We can't display it.
+                for &(item_len, item_width) in graphemes.iter() {
+                    if width_left >= item_width {
+                        byte_split_pos += item_len;
+                        width_left -= item_width;
+                    } else {
+                        break;
+                    }
+                }
+
                 let this_line = &text[..byte_split_pos];
                 line_segments.push((style, this_line));
                 &text[byte_split_pos..]
@@ -273,7 +287,9 @@ where
     if result.len() == 1 && curr_line.has_text() {
         let current_permille = (curr_line.text_len() * 1000) / line_width;
 
-        let pad_len = line_width.saturating_sub(curr_line.text_len());
+        // pad line will add a wrap_config.right_prefix_symbol
+        let pad_len = line_width
+            .saturating_sub(curr_line.text_len() + wrap_config.right_prefix_symbol.width());
 
         if wrap_config.use_wrap_right_permille > current_permille && pad_len > 0 {
             // The inserted spaces, which align a line to the right, point into this string.
@@ -453,8 +469,7 @@ pub fn wrap_minusplus_block<'c: 'a, 'a>(
         assert_eq!(
             (start, extended_to),
             (start2, extended_to2),
-            "syntax and diff wrapping differs {}",
-            errhint
+            "syntax and diff wrapping differs {errhint}",
         );
 
         (start, extended_to)
@@ -785,7 +800,7 @@ mod tests {
         let lines = wrap_test(&cfg, line, 11);
         assert_eq!(lines.len(), 2);
         assert_eq!(lines[0].last().unwrap().1, WR);
-        assert_eq!(lines[1], [(*SD, "         "), (*SD, ">"), (*S1, "ab")]);
+        assert_eq!(lines[1], [(*SD, "        "), (*SD, ">"), (*S1, "ab")]);
     }
 
     #[test]
@@ -801,7 +816,7 @@ mod tests {
                 lines,
                 vec![
                     vec![(*S1, "012"), (*S2, "34"), (*SD, WR)],
-                    vec![(*SD, "    "), (*SD, RA), (*S2, "56")]
+                    vec![(*SD, "   "), (*SD, RA), (*S2, "56")]
                 ]
             );
         }
@@ -828,7 +843,7 @@ mod tests {
             let s1s2 = v.iter().cycle();
             let text: Vec<_> = IN.matches(|_| true).take(len + 1).collect();
             s1s2.zip(text.iter())
-                .map(|(style, text)| (style.clone(), *text))
+                .map(|(style, text)| (*style, *text))
                 .collect()
         }
         fn mk_input_nl(len: usize) -> LineSections<'static, Style> {
@@ -843,7 +858,7 @@ mod tests {
             to: usize,
             append: Option<(Style, &'a str)>,
         ) -> LineSections<'a, Style> {
-            let mut result: Vec<_> = vec[from..to].iter().cloned().collect();
+            let mut result: Vec<_> = vec[from..to].to_vec();
             if let Some(val) = append {
                 result.push(val);
             }
@@ -940,13 +955,19 @@ mod tests {
         );
 
         // Not working: Tailored grapheme clusters: क्षि  = क् + षि
-        let line = vec![(*S1, "abc"), (*S2, "deநி"), (*S1, "ghij")];
+        //
+        // Difference compare to previous example (even they may look like the
+        // same width in text editor.) :
+        //
+        // width நி: 2
+        // width ö̲: 1
+        let line = vec![(*S1, "abc"), (*S2, "dநி"), (*S1, "ghij")];
         let lines = wrap_test(&cfg, line, 4);
         assert_eq!(
             lines,
             vec![
                 vec![(*S1, "abc"), (*SD, W)],
-                vec![(*S2, "deநி"), (*SD, W)],
+                vec![(*S2, "dநி"), (*SD, W)],
                 vec![(*S1, "ghij")]
             ]
         );
@@ -1083,8 +1104,7 @@ index 223ca50..e69de29 100644
         {
             DeltaTest::with_config(&config)
                 .with_input(&format!(
-                    "{}-{}+{}",
-                    HUNK_ALIGN_DIFF_HEADER, HUNK_ALIGN_DIFF_SHORT, HUNK_ALIGN_DIFF_LONG
+                    "{HUNK_ALIGN_DIFF_HEADER}-{HUNK_ALIGN_DIFF_SHORT}+{HUNK_ALIGN_DIFF_LONG}",
                 ))
                 .expect_after_header(
                     r#"
@@ -1098,8 +1118,7 @@ index 223ca50..e69de29 100644
         {
             DeltaTest::with_config(&config)
                 .with_input(&format!(
-                    "{}-{}+{}",
-                    HUNK_ALIGN_DIFF_HEADER, HUNK_ALIGN_DIFF_LONG, HUNK_ALIGN_DIFF_SHORT
+                    "{HUNK_ALIGN_DIFF_HEADER}-{HUNK_ALIGN_DIFF_LONG}+{HUNK_ALIGN_DIFF_SHORT}",
                 ))
                 .expect_after_header(
                     r#"
@@ -1123,8 +1142,7 @@ index 223ca50..e69de29 100644
         {
             DeltaTest::with_config(&config)
                 .with_input(&format!(
-                    "{}-{}+{}",
-                    HUNK_ALIGN_DIFF_HEADER, HUNK_ALIGN_DIFF_SHORT, HUNK_ALIGN_DIFF_LONG
+                    "{HUNK_ALIGN_DIFF_HEADER}-{HUNK_ALIGN_DIFF_SHORT}+{HUNK_ALIGN_DIFF_LONG}",
                 ))
                 .expect_after_header(
                     r#"
@@ -1137,8 +1155,7 @@ index 223ca50..e69de29 100644
         {
             DeltaTest::with_config(&config)
                 .with_input(&format!(
-                    "{}-{}+{}",
-                    HUNK_ALIGN_DIFF_HEADER, HUNK_ALIGN_DIFF_LONG, HUNK_ALIGN_DIFF_SHORT
+                    "{HUNK_ALIGN_DIFF_HEADER}-{HUNK_ALIGN_DIFF_LONG}+{HUNK_ALIGN_DIFF_SHORT}",
                 ))
                 .expect_after_header(
                     r#"
@@ -1166,8 +1183,7 @@ index 223ca50..e69de29 100644
         {
             DeltaTest::with_config(&config)
                 .with_input(&format!(
-                    "{}-{}+{}",
-                    HUNK_ALIGN_DIFF_HEADER, HUNK_ALIGN_DIFF_SHORT, HUNK_ALIGN_DIFF_LONG
+                    "{HUNK_ALIGN_DIFF_HEADER}-{HUNK_ALIGN_DIFF_SHORT}+{HUNK_ALIGN_DIFF_LONG}",
                 ))
                 .expect_after_header(
                     r#"
@@ -1181,8 +1197,7 @@ index 223ca50..e69de29 100644
             config.wrap_config.max_lines = 2;
             DeltaTest::with_config(&config)
                 .with_input(&format!(
-                    "{}-{}+{}",
-                    HUNK_ALIGN_DIFF_HEADER, HUNK_ALIGN_DIFF_SHORT, HUNK_ALIGN_DIFF_LONG
+                    "{HUNK_ALIGN_DIFF_HEADER}-{HUNK_ALIGN_DIFF_SHORT}+{HUNK_ALIGN_DIFF_LONG}",
                 ))
                 .expect_after_header(
                     r#"


Test patch:
diff --git a/src/ansi/console_tests.rs b/src/ansi/console_tests.rs
index d637231..09f5647 100644
--- a/src/ansi/console_tests.rs
+++ b/src/ansi/console_tests.rs
@@ -54,10 +54,11 @@ mod tests {
             &truncate_str(&s, 10, "..."),
             &format!("foo {}...", style("bar").red().force_styling(true))
         );
+        // `バ`(width = 2) will be truncate to 1, we use space to fill
         let s = format!("foo {}", style("バー").red().force_styling(true));
         assert_eq!(
             &truncate_str(&s, 5, ""),
-            &format!("foo {}", style("").red().force_styling(true))
+            &format!("foo {}", style(" ").red().force_styling(true))
         );
         let s = format!("foo {}", style("バー").red().force_styling(true));
         assert_eq!(
diff --git a/src/tests/ansi_test_utils.rs b/src/tests/ansi_test_utils.rs
index fef1a66..d21ab3c 100644
--- a/src/tests/ansi_test_utils.rs
+++ b/src/tests/ansi_test_utils.rs
@@ -81,7 +81,7 @@ pub mod ansi_test_utils {
     ) {
         let line = output.lines().nth(line_number).unwrap();
         let substring_end = substring_begin + expected_substring.len();
-        let substring = &ansi::strip_ansi_codes(&line)[substring_begin..substring_end];
+        let substring = &ansi::strip_ansi_codes(line)[substring_begin..substring_end];
         assert_eq!(substring, expected_substring);
         let painted_substring = paint_line(substring, language_extension, state, config);
         // remove trailing newline appended by paint::paint_lines.
@@ -103,7 +103,7 @@ pub mod ansi_test_utils {
     }
 
     pub fn get_color_variants(string: &str, config: &Config) -> (String, String) {
-        let string_without_any_color = ansi::strip_ansi_codes(string).to_string();
+        let string_without_any_color = ansi::strip_ansi_codes(string);
         let string_with_plus_color_only = config
             .plus_style
             .ansi_term_style
@@ -129,7 +129,7 @@ pub mod ansi_test_utils {
         };
         painter.set_syntax(Some(language_extension));
         painter.set_highlighter();
-        let lines = vec![(line.to_string(), state.clone())];
+        let lines = vec![(line.to_string(), state)];
         let syntax_style_sections = paint::get_syntax_style_sections_for_lines(
             &lines,
             painter.highlighter.as_mut(),
diff --git a/src/tests/integration_test_utils.rs b/src/tests/integration_test_utils.rs
index 5d997e3..764be5d 100644
--- a/src/tests/integration_test_utils.rs
+++ b/src/tests/integration_test_utils.rs
@@ -57,7 +57,7 @@ fn _make_options_from_args_and_git_config(
     honor_env_var: bool,
 ) -> cli::Opt {
     let mut args: Vec<&str> = itertools::chain(&["/dev/null", "/dev/null"], args)
-        .map(|s| *s)
+        .copied()
         .collect();
     let git_config = match (git_config_contents, git_config_path) {
         (Some(contents), Some(path)) => Some(make_git_config(&env, contents, path, honor_env_var)),
@@ -99,7 +99,7 @@ pub fn make_git_config(
     let path = Path::new(path);
     let mut file = File::create(path).unwrap();
     file.write_all(contents).unwrap();
-    GitConfig::from_path(env, &path, honor_env_var)
+    GitConfig::from_path(env, path, honor_env_var)
 }
 
 pub fn get_line_of_code_from_delta(
@@ -108,7 +108,7 @@ pub fn get_line_of_code_from_delta(
     expected_text: &str,
     config: &config::Config,
 ) -> String {
-    let output = run_delta(&input, config);
+    let output = run_delta(input, config);
     let line_of_code = output.lines().nth(line_number).unwrap();
     assert!(ansi::strip_ansi_codes(line_of_code) == expected_text);
     line_of_code.to_string()
@@ -168,7 +168,7 @@ pub fn delineated_string(txt: &str) -> String {
     let top = "▼".repeat(100);
     let btm = "▲".repeat(100);
     let nl = "\n";
-    top + &nl + txt + &nl + &btm
+    top + nl + txt + nl + &btm
 }
 
 pub struct DeltaTest<'a> {
@@ -240,7 +240,7 @@ impl DeltaTestOutput {
     /// with ASCII explanation of ANSI escape sequences.
     #[allow(unused)]
     pub fn inspect(self) -> Self {
-        eprintln!("{}", delineated_string(&self.output.as_str()));
+        eprintln!("{}", delineated_string(&self.output));
         self
     }
 
@@ -269,7 +269,7 @@ impl DeltaTestOutput {
             self.output.contains(expected),
             "Output does not contain \"{}\":\n{}\n",
             expected,
-            delineated_string(&self.output.as_str())
+            delineated_string(&self.output)
         );
         self
     }
@@ -279,7 +279,7 @@ impl DeltaTestOutput {
             self.raw_output.contains(expected),
             "Raw output does not contain \"{}\":\n{}\n",
             expected,
-            delineated_string(&self.raw_output.as_str())
+            delineated_string(&self.raw_output)
         );
         self
     }
@@ -289,7 +289,7 @@ impl DeltaTestOutput {
             test_utils::contains_once(&self.output, expected),
             "Output does not contain \"{}\" exactly once:\n{}\n",
             expected,
-            delineated_string(&self.output.as_str())
+            delineated_string(&self.output)
         );
         self
     }
@@ -301,7 +301,7 @@ pub fn run_delta(input: &str, config: &config::Config) -> String {
     delta(
         ByteLines::new(BufReader::new(input.as_bytes())),
         &mut writer,
-        &config,
+        config,
     )
     .unwrap();
     String::from_utf8(writer).unwrap()
diff --git a/src/tests/test_example_diffs.rs b/src/tests/test_example_diffs.rs
index d3800a6..6e0983c 100644
--- a/src/tests/test_example_diffs.rs
+++ b/src/tests/test_example_diffs.rs
@@ -60,7 +60,7 @@ mod tests {
         // In addition to the background color, the code has language syntax highlighting.
         let config = integration_test_utils::make_config_from_args(&[]);
         let output = integration_test_utils::get_line_of_code_from_delta(
-            &ADDED_FILE_INPUT,
+            ADDED_FILE_INPUT,
             14,
             "class X:",
             &config,